Skip to content

Commit

Permalink
[WIP][ADD] translation module
Browse files Browse the repository at this point in the history
  • Loading branch information
oomsveta committed Apr 18, 2024
1 parent f9085b2 commit 8ede015
Show file tree
Hide file tree
Showing 18 changed files with 1,152 additions and 0 deletions.
1 change: 1 addition & 0 deletions addons/t9n/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
23 changes: 23 additions & 0 deletions addons/t9n/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "Translations",
"version": "1.0",
"category": "TODO: find the appropriate category",
"description": "TODO: write a description of the module",
"depends": ["base", "web"],
"application": True,
"assets": {
"web.assets_backend": [
"t9n/static/src/**/*",
],
},
"data": [
"data/t9n.language.csv",
"security/ir.model.access.csv",
"views/t9n_project_views.xml",
"views/t9n_menu_views.xml",
"views/t9n_language_views.xml",
"views/t9n_resource_views.xml",
"views/t9n_message_views.xml",
],
"license": "LGPL-3",
}
723 changes: 723 additions & 0 deletions addons/t9n/data/t9n.language.csv

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions addons/t9n/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import language
from . import message
from . import project
from . import resource
from . import translation
22 changes: 22 additions & 0 deletions addons/t9n/models/language.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from odoo import fields, models


class Language(models.Model):
_name = "t9n.language"
_description = "Language"

name = fields.Char("Formal Name", required=True, readonly=True)
code = fields.Char("Code", required=True, readonly=True)
native_name = fields.Char("Native Name", readonly=True)
direction = fields.Selection(
required=True,
selection=[
("ltr", "left-to-right"),
("rtl", "right-to-left"),
],
readonly=True,
)

_sql_constraints = [
("language_code_unique", "unique(code)", "The language code must be unique.")
]
43 changes: 43 additions & 0 deletions addons/t9n/models/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from odoo import api, fields, models


class Message(models.Model):
"""Models a localizable message, i.e. any textual content to be translated.
Messages are retrieved from a Resource.
A Message localized to a specific Language becomes a Translation.
"""

_name = "t9n.message"
_description = "Localizable message"

body = fields.Text(
"Entry to be Translated",
help="Text to Translate",
)
context = fields.Char(help="Text Context")
translator_comments = fields.Text(
help="Comments written by the translator/developer in the resource file.",
)
extracted_comments = fields.Text("Resource Comments")
references = fields.Text(
help="The full text that represents the references, one per line.",
)
resource_id = fields.Many2one(
comodel_name="t9n.resource",
help="The resource (typically a file) from which the entry is coming from.",
ondelete="cascade",
required=True,
)
translation_ids = fields.One2many(
comodel_name="t9n.translation",
inverse_name="source_id",
string="Translations",
)

_sql_constraints = [
(
"body_context_resource_unique",
"UNIQUE(body, context, resource_id)",
"The combination of a text to translate and its context must be unique within the same resource!",
),
]
34 changes: 34 additions & 0 deletions addons/t9n/models/project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from odoo import fields, models, api, _
from odoo.exceptions import ValidationError


class Project(models.Model):
"""A project is a collection of Resources to be localized into a given set
of Languages.
"""

_name = "t9n.project"
_description = "Translation project"

name = fields.Char("Project", required=True)
src_lang_id = fields.Many2one(
comodel_name="t9n.language",
string="Source Language",
help="The original language of the messages you want to translate.",
)
resource_ids = fields.One2many(
comodel_name="t9n.resource",
inverse_name="project_id",
string="Resources",
)
target_lang_ids = fields.Many2many(
comodel_name="t9n.language",
string="Languages",
help="The list of languages into which the project can be translated.",
)

@api.constrains("src_lang_id", "target_lang_ids")
def _check_source_and_target_languages(self):
for record in self:
if record.src_lang_id in record.target_lang_ids:
raise ValidationError(_("A project's target languages must be different from its source language."))
97 changes: 97 additions & 0 deletions addons/t9n/models/resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import base64

import polib

from odoo import Command, _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools import format_list

from odoo import _, api, Command, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools import format_list

class Resource(models.Model):
_name = "t9n.resource"
_description = "Resource file"

file_name = fields.Char()
file = fields.Binary("Resource File", store=False)
message_ids = fields.One2many(
comodel_name="t9n.message",
inverse_name="resource_id",
string="Entries to translate",
)
project_id = fields.Many2one(
comodel_name="t9n.project",
)

_sql_constraints = [
(
"file_name_project_id_unique",
"unique(file_name, project_id)",
"A file with the same name already exists in the same project!",
),
]

def _decode_resource_file(self, resource_file):
try:
file_content = base64.b64decode(resource_file).decode()
po_obj = polib.pofile(file_content)
except (IOError, UnicodeDecodeError):
po_obj = []
return [
{
"body": entry.msgid,
"context": entry.msgctxt,
"translator_comments": entry.tcomment,
"extracted_comments": entry.comment,
"references": "\n".join([fpath + (lineno and f":{lineno}") for fpath, lineno in entry.occurrences]),
}
for entry in po_obj
]

@api.model_create_multi
def create(self, vals_list):
broken_files = []
for vals in vals_list:
if not vals.get("file"):
raise ValidationError(_("A resource file is required to create a resource."))
po_obj = self._decode_resource_file(vals["file"])
del vals["file"]
if not po_obj:
broken_files.append(vals["file_name"])
continue
vals["message_ids"] = [Command.create(message) for message in po_obj]
if broken_files:
raise UserError(
_(
"Resource files must be valid .pot files. The following files are ill-formatted or empty: %(file_names)s",
file_names=format_list(self.env, broken_files),
),
)
return super().create(vals_list)

def write(self, vals):
self.ensure_one()
if "file" not in vals:
return super().write(vals)
po_obj = self._decode_resource_file(vals["file"])
del vals["file"]
if not po_obj:
raise UserError(
_("The files: %(file_name)s should be a .po file with a valid syntax.", file_name=vals["file_name"]),
)
current_msgs_by_tuple = {(msg.body, msg.context): msg for msg in self.message_ids}
new_msgs_by_tuple = {(msg["body"], msg["context"]): msg for msg in po_obj}
to_create = [msg_val for key, msg_val in new_msgs_by_tuple.items() if key not in current_msgs_by_tuple]
to_unlink = {msg.id for key, msg in current_msgs_by_tuple.items() if key not in new_msgs_by_tuple}
to_update = [
(current_msgs_by_tuple[key].id, new_msgs_by_tuple[key])
for key in set(current_msgs_by_tuple) & set(new_msgs_by_tuple)
]
vals["message_ids"] = (
[Command.create(vals) for vals in to_create]
+ [Command.unlink(id) for id in to_unlink]
+ [Command.update(id, vals) for id, vals in to_update]
)
return super().write(vals)
20 changes: 20 additions & 0 deletions addons/t9n/models/translation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from odoo import fields, models


class Translation(models.Model):
_name = "t9n.translation"
_description = "Message translated into a language"

body = fields.Text(
help="The actual content of the translation.",
)
source_id = fields.Many2one(
comodel_name="t9n.message",
string="Source message",
help="The original text, the source of the translation.",
)
lang_id = fields.Many2one(
comodel_name="t9n.language",
string="Language",
help="The language to which the translation translates the original message.",
)
6 changes: 6 additions & 0 deletions addons/t9n/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_t9n_project_system,t9n.project.system,t9n.model_t9n_project,base.group_system,1,1,1,1
access_t9n_language_system,t9n.language.system,t9n.model_t9n_language,base.group_system,1,1,1,1
access_t9n_message_system,t9n.message.system,t9n.model_t9n_message,base.group_system,1,1,1,1
access_t9n_resource_system,t9n.resource.system,t9n.model_t9n_resource,base.group_system,1,1,1,1
access_t9n_translation_system,t9n.translation.system,t9n.model_t9n_translation,base.group_system,1,1,1,1
10 changes: 10 additions & 0 deletions addons/t9n/static/src/core/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Component } from "@odoo/owl";

/**
* The "root", the "homepage" of the translation application.
*/
export class App extends Component {
static components = {};
static props = {};
static template = "t9n.App";
}
8 changes: 8 additions & 0 deletions addons/t9n/static/src/core/app.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<templates xml:space="preserve">

<t t-name="t9n.App">
Hello World!
</t>

</templates>
18 changes: 18 additions & 0 deletions addons/t9n/static/src/web/open_app_action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component, xml } from "@odoo/owl";

import { App } from "@t9n/core/app";

import { registry } from "@web/core/registry";
import { standardActionServiceProps } from "@web/webclient/actions/action_service";

/**
* Wraps the application root, allowing us to open the application as a result
* of a call to the "t9n.open_app" client action.
*/
export class OpenApp extends Component {
static components = { App };
static props = { ...standardActionServiceProps };
static template = xml`<App/>`;
}

registry.category("actions").add("t9n.open_app", OpenApp);
26 changes: 26 additions & 0 deletions addons/t9n/views/t9n_language_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<odoo>
<record id="t9n.language_view_list" model="ir.ui.view">
<field name="name">t9n.language.list</field>
<field name="model">t9n.language</field>
<field name="arch" type="xml">
<tree create="false" edit="false">
<field name="code"/>
<field name="name"/>
<field name="native_name"/>
</tree>
</field>
</record>
<record id="t9n.language_view_search" model="ir.ui.view">
<field name="name">t9n.language.search</field>
<field name="model">t9n.language</field>
<field name="arch" type="xml">
<search>
<field name="name" string="All Languages" filter_domain="[ '|', '|', ('name', 'ilike', self), ('native_name', 'ilike', self), ('code', 'ilike', self)]"/>
<field name="name"/>
<field name="native_name"/>
<field name="code"/>
</search>
</field>
</record>
</odoo>
10 changes: 10 additions & 0 deletions addons/t9n/views/t9n_menu_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<odoo>
<record id="t9n.open_app" model="ir.actions.client">
<field name="name">Translate</field>
<field name="tag">t9n.open_app</field>
<field name="target">main</field>
</record>
<menuitem id="t9n.menu_root" name="Translations" action="t9n.open_app"/>
<menuitem id="t9n.project_menu" name="Projects" action="t9n.project_action" parent="t9n.menu_root"/>
</odoo>
39 changes: 39 additions & 0 deletions addons/t9n/views/t9n_message_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0"?>
<odoo>
<record id="t9n.message_view_list" model="ir.ui.view">
<field name="name">t9n.message.list</field>
<field name="model">t9n.message</field>
<field name="arch" type="xml">
<tree create="false">
<field name="body"/>
</tree>
</field>
</record>

<record id="t9n.message_view_form" model="ir.ui.view">
<field name="name">t9n.message.form</field>
<field name="model">t9n.message</field>
<field name="arch" type="xml">
<form>
<sheet>
<field name="body"/>
<group>
<field name="extracted_comments"/>
<field name="translator_comments"/>
</group>
<group>
<field name="context"/>
</group>
<notebook>
<page string="References">
<field name="references"/>
</page>
<page string="Translations">
<field name="translation_ids"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>
Loading

0 comments on commit 8ede015

Please sign in to comment.