Skip to content

Commit a8c401f

Browse files
[IMP] t9n: add custom views to the existing models.
This commit adds the full flow starting from the home screen. The home screen contains a list of all projects that you can click on to take you to the list of available target languages in this project. Clicking on a language will take you to the resources in the project then to the different texts that need to be translated. Also, various features are added in each custom view of those like sorting and searching. Finally, you can add your translation for any text you want :)) Task-3783071
1 parent ee34d5b commit a8c401f

22 files changed

+637
-20
lines changed

addons/t9n/models/message.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from odoo import fields, models
1+
from odoo import api, fields, models
22

33

44
class Message(models.Model):
@@ -41,3 +41,25 @@ class Message(models.Model):
4141
"The combination of a text to translate and its context must be unique within the same resource!",
4242
),
4343
]
44+
45+
@api.model
46+
def get_message(self, message_id, target_lang_id):
47+
message_records = self.browse([message_id])
48+
message_records.ensure_one()
49+
message_record = next(iter(message_records))
50+
return {
51+
"id": message_record.id,
52+
"body": message_record.body,
53+
"context": message_record.context,
54+
"translator_comments": message_record.translator_comments,
55+
"extracted_comments": message_record.extracted_comments,
56+
"references": message_record.references,
57+
"translations": [
58+
{
59+
"id": record.id,
60+
"body": record.body,
61+
}
62+
for record in message_record.translation_ids
63+
if record.lang_id.id == target_lang_id
64+
],
65+
}

addons/t9n/models/project.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,42 @@ def _check_source_and_target_languages(self):
3636
@api.model
3737
def get_projects(self):
3838
projects_records = self.search([])
39-
return [{
39+
return [
40+
{
4041
"id": record.id,
4142
"name": record.name,
4243
"src_lang": {
4344
"id": record.src_lang_id.id,
4445
"name": record.src_lang_id.name if record.src_lang_id.name else "",
4546
},
46-
"resources": [{
47-
"id": resource.id,
48-
"file_name": resource.file_name,
49-
} for resource in record.resource_ids],
50-
"target_langs": [{
47+
"resources": [
48+
{
49+
"id": resource.id,
50+
}
51+
for resource in record.resource_ids
52+
],
53+
"target_langs": [
54+
{
5155
"id": lang.id,
5256
"name": lang.name,
53-
} for lang in record.target_lang_ids],
54-
} for record in projects_records]
57+
}
58+
for lang in record.target_lang_ids
59+
],
60+
}
61+
for record in projects_records
62+
]
63+
64+
@api.model
65+
def get_target_langs(self, id):
66+
project_records = self.browse([id])
67+
project_records.ensure_one()
68+
project_record = next(iter(project_records))
69+
return [
70+
{
71+
"id": lang.id,
72+
"name": lang.name,
73+
"code": lang.code,
74+
"native_name": lang.native_name,
75+
}
76+
for lang in project_record.target_lang_ids
77+
]

addons/t9n/models/resource.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,44 @@ def write(self, vals):
9292
+ [Command.update(id, vals) for id, vals in to_update]
9393
)
9494
return super().write(vals)
95+
96+
@api.model
97+
def get_resources(self, project_id):
98+
resources_records = self.search([("project_id.id", "=", project_id)])
99+
return [
100+
{
101+
"id": record.id,
102+
"file_name": record.file_name,
103+
}
104+
for record in resources_records
105+
]
106+
107+
@api.model
108+
def get_resource(self, id, target_lang_id):
109+
resource_records = self.browse([id])
110+
resource_records.ensure_one()
111+
resource_record = next(iter(resource_records))
112+
return {
113+
"id": resource_record.id,
114+
"file_name": resource_record.file_name,
115+
"project_id": resource_record.project_id.id,
116+
"messages": [
117+
{
118+
"id": message.id,
119+
"body": message.body,
120+
"context": message.context,
121+
"translator_comments": message.translator_comments,
122+
"extracted_comments": message.extracted_comments,
123+
"references": message.references,
124+
"translations": [
125+
{
126+
"id": lang.id,
127+
"body": lang.body,
128+
}
129+
for lang in message.translation_ids
130+
if lang.lang_id.id == target_lang_id
131+
],
132+
}
133+
for message in resource_record.message_ids
134+
],
135+
}

addons/t9n/models/translation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from odoo import fields, models
1+
from odoo import api, fields, models
22

33

44
class Translation(models.Model):
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Component } from '@odoo/owl'
2+
3+
export class CopyPopover extends Component {
4+
static props = {}
5+
static template = 't9n.copyButtonPopover'
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<templates>
3+
<t t-name="t9n.copyButtonPopover">
4+
<div class="popover-body">Copied to clipboard!</div>
5+
</t>
6+
</templates>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Component, useState } from "@odoo/owl";
2+
3+
import { useService } from "@web/core/utils/hooks";
4+
5+
export class LanguageList extends Component {
6+
static props = {};
7+
static template = "t9n.LanguageList";
8+
9+
setup() {
10+
this.action = useService("action");
11+
this.state = useState({
12+
filters: {
13+
searchText: "",
14+
},
15+
sorting: {
16+
column: "name",
17+
order: "asc",
18+
},
19+
});
20+
this.store = useState(useService("t9n.store"));
21+
this.store.fetchLanguages();
22+
}
23+
24+
get languages() {
25+
const searchTerms = this.state.filters.searchText.trim().toUpperCase();
26+
const languages = searchTerms
27+
? this.store.languages.filter((l) => l.name.toUpperCase().includes(searchTerms))
28+
: [...this.store.languages];
29+
30+
languages.sort((l1, l2) => {
31+
let l1Col = l1[this.state.sorting.column];
32+
let l2Col = l2[this.state.sorting.column];
33+
34+
l1Col = l1Col.toLowerCase();
35+
l2Col = l2Col.toLowerCase();
36+
37+
if (l1Col < l2Col) {
38+
return this.state.sorting.order === "asc" ? -1 : 1;
39+
}
40+
if (l1Col > l2Col) {
41+
return this.state.sorting.order === "asc" ? 1 : -1;
42+
}
43+
return 0;
44+
});
45+
return languages;
46+
}
47+
48+
onClickColumnName(column) {
49+
if (this.state.sorting.column === column) {
50+
this.state.sorting.order = this.state.sorting.order === "asc" ? "desc" : "asc";
51+
} else {
52+
this.state.sorting.column = column;
53+
this.state.sorting.order = "asc";
54+
}
55+
}
56+
57+
onClickLanguage(id) {
58+
this.store.setTargetLangId(id);
59+
this.action.doAction({
60+
type: "ir.actions.client",
61+
tag: "t9n.open_resource_list",
62+
target: "current",
63+
});
64+
}
65+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0"?>
2+
<templates>
3+
<t t-name="t9n.LanguageList">
4+
<div class="container mt-3">
5+
<div class="input-group">
6+
<div class="form-outline">
7+
<input type="text" class="form-control" placeholder="Search languages" t-model="state.filters.searchText"/>
8+
</div>
9+
</div>
10+
<table class="table table-hover">
11+
<thead>
12+
<tr>
13+
<th t-on-click="() => this.onClickColumnName('name')">Language Name <i t-attf-class="btn btn-link fa fa-sort-{{this.state.sorting.column === 'name' and this.state.sorting.order === 'asc' ? 'up' : 'down'}}"></i>
14+
</th>
15+
<th>Native name</th>
16+
<th>Locale</th>
17+
</tr>
18+
</thead>
19+
<tbody>
20+
<t t-foreach="this.languages" t-as="language" t-key="language.id">
21+
<tr>
22+
<td>
23+
<button class="btn btn-link " t-on-click="() => this.onClickLanguage(language.id)">
24+
<t t-esc="language.name"/>
25+
</button>
26+
</td>
27+
<td t-esc="language.native_name"/>
28+
<td t-esc="language.code"/>
29+
</tr>
30+
</t>
31+
</tbody>
32+
</table>
33+
</div>
34+
</t>
35+
</templates>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Component, useState, useRef } from "@odoo/owl";
2+
import { useService } from "@web/core/utils/hooks";
3+
import { usePopover } from "@web/core/popover/popover_hook";
4+
import { CopyPopover } from "@t9n/core/copy_button_popover";
5+
6+
export class MessageForm extends Component {
7+
static props = {};
8+
static template = "t9n.MessageForm";
9+
10+
setup() {
11+
this.state = useState({
12+
suggestedTranslationText: "",
13+
});
14+
this.store = useState(useService("t9n.store"));
15+
this.orm = useService("orm");
16+
this.popoverButtonRef = useRef("popover-button");
17+
this.copyPopover = usePopover(CopyPopover, {
18+
position: "top",
19+
animation: true,
20+
arrow: true,
21+
closeOnClickAway: true,
22+
});
23+
}
24+
25+
get message() {
26+
return this.store.active_message;
27+
}
28+
29+
get translations() {
30+
return this.store.active_message.translations;
31+
}
32+
33+
onClickClear() {
34+
this.state.suggestedTranslationText = "";
35+
}
36+
37+
async onClickCopy(ev) {
38+
try {
39+
await navigator.clipboard.writeText(this.state.suggestedTranslationText.trim());
40+
this.copyPopover.open(this.popoverButtonRef.el, {});
41+
setTimeout(() => {
42+
this.copyPopover.close();
43+
}, 3000);
44+
} catch (error) {
45+
console.error("Error copying text:", error);
46+
}
47+
}
48+
49+
async onClickSuggest() {
50+
await this.orm.create("t9n.translation", [
51+
{
52+
body: this.state.suggestedTranslationText.trim(),
53+
source_id: this.store.active_message.id,
54+
lang_id: this.store.target_lang_id,
55+
},
56+
]);
57+
this.store.fetchActiveMessage();
58+
this.onClickClear();
59+
}
60+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?xml version="1.0"?>
2+
<templates>
3+
<t t-name="t9n.MessageForm">
4+
<div class="card mx-0 border-light">
5+
<div class="card-header">
6+
<t t-esc="this.message.body"/>
7+
</div>
8+
<div class="card-body">
9+
<div class="card-text">
10+
<p t-if="this.message.translator_comments">
11+
<span>
12+
<strong>
13+
<em>TRANSLATOR COMMENT </em>
14+
</strong>
15+
</span>
16+
<span>
17+
<t t-esc="this.message.translator_comments"/>
18+
</span>
19+
</p>
20+
<p t-if="this.message.extracted_comments">
21+
<span>
22+
<strong>
23+
<em>RESOURCE COMMENT </em>
24+
</strong>
25+
</span>
26+
<span>
27+
<t t-esc="this.message.extracted_comments"/>
28+
</span>
29+
</p>
30+
<p t-if="this.message.context">
31+
<span>
32+
<strong>
33+
<em>CONTEXT </em>
34+
</strong>
35+
</span>
36+
<span>
37+
38+
<li class="list-group-item">Cras justo odio</li>
39+
</span>
40+
</p>
41+
<p t-if="this.message.references">
42+
<span>
43+
<strong>
44+
<em>REFERENCES </em>
45+
</strong>
46+
</span>
47+
<span>
48+
<t t-esc="this.message.references"/>
49+
</span>
50+
</p>
51+
</div>
52+
<div class="form-group">
53+
<textarea class="form-control" rows="3" placeholder="Write your translation" t-model="state.suggestedTranslationText" t-ref="popover-button"></textarea>
54+
</div>
55+
<div class="d-flex justify-content-end my-3">
56+
<button class="btn btn-outline-primary mx-1" t-on-click="onClickClear">Clear</button>
57+
<button class="btn btn-outline-primary mx-1" data-toggle="popover" t-on-click="onClickCopy">Copy</button>
58+
<button class="btn btn-primary mx-1" t-on-click="onClickSuggest">Suggest</button>
59+
</div>
60+
</div>
61+
</div>
62+
<ul class="list-group">
63+
<t t-foreach="this.translations" t-as="translation" t-key="translation.id">
64+
<li class="list-group-item">
65+
<t t-esc="translation.body"/>
66+
</li>
67+
</t>
68+
</ul>
69+
</t>
70+
</templates>

0 commit comments

Comments
 (0)