diff --git a/addons/t9n/__init__.py b/addons/t9n/__init__.py new file mode 100644 index 0000000000000..0650744f6bc69 --- /dev/null +++ b/addons/t9n/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/addons/t9n/__manifest__.py b/addons/t9n/__manifest__.py new file mode 100644 index 0000000000000..6ac8c7a01d92a --- /dev/null +++ b/addons/t9n/__manifest__.py @@ -0,0 +1,19 @@ +{ + "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": [ + "security/ir.model.access.csv", + "views/t9n_project_views.xml", + "views/t9n_menu_views.xml", + ], + "license": "LGPL-3", +} diff --git a/addons/t9n/models/__init__.py b/addons/t9n/models/__init__.py new file mode 100644 index 0000000000000..22e0bdf014aa7 --- /dev/null +++ b/addons/t9n/models/__init__.py @@ -0,0 +1,5 @@ +from . import language +from . import message +from . import project +from . import resource +from . import translation diff --git a/addons/t9n/models/language.py b/addons/t9n/models/language.py new file mode 100644 index 0000000000000..91f9a9232d182 --- /dev/null +++ b/addons/t9n/models/language.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class Language(models.Model): + _name = "t9n.language" + _description = "Language" + + name = fields.Char("Language", required=True) diff --git a/addons/t9n/models/message.py b/addons/t9n/models/message.py new file mode 100644 index 0000000000000..a36404dcabcf4 --- /dev/null +++ b/addons/t9n/models/message.py @@ -0,0 +1,24 @@ +from odoo import 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( + help="The actual, textual content to be translated.", + ) + resource_id = fields.Many2one( + comodel_name="t9n.resource", + help="The resource (typically a file) from which the entry is coming from.", + ) + translation_ids = fields.One2many( + comodel_name="t9n.translation", + inverse_name="source_id", + string="Translations", + ) diff --git a/addons/t9n/models/project.py b/addons/t9n/models/project.py new file mode 100644 index 0000000000000..424edfecc080d --- /dev/null +++ b/addons/t9n/models/project.py @@ -0,0 +1,36 @@ +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( + _("Target languages must be different from source language.") + ) diff --git a/addons/t9n/models/resource.py b/addons/t9n/models/resource.py new file mode 100644 index 0000000000000..792f71b9ca39e --- /dev/null +++ b/addons/t9n/models/resource.py @@ -0,0 +1,16 @@ +from odoo import fields, models + + +class Resource(models.Model): + _name = "t9n.resource" + _description = "Resource file" + + name = fields.Char("Resource") + message_ids = fields.One2many( + comodel_name="t9n.message", + inverse_name="resource_id", + string="Entries to translate", + ) + project_id = fields.Many2one( + comodel_name="t9n.project", + ) diff --git a/addons/t9n/models/translation.py b/addons/t9n/models/translation.py new file mode 100644 index 0000000000000..4d511dd2dba5d --- /dev/null +++ b/addons/t9n/models/translation.py @@ -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.", + ) diff --git a/addons/t9n/security/ir.model.access.csv b/addons/t9n/security/ir.model.access.csv new file mode 100644 index 0000000000000..892a06d3a359f --- /dev/null +++ b/addons/t9n/security/ir.model.access.csv @@ -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 diff --git a/addons/t9n/static/src/core/app.js b/addons/t9n/static/src/core/app.js new file mode 100644 index 0000000000000..79ff2d525fff2 --- /dev/null +++ b/addons/t9n/static/src/core/app.js @@ -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"; +} diff --git a/addons/t9n/static/src/core/app.xml b/addons/t9n/static/src/core/app.xml new file mode 100644 index 0000000000000..737753809f19d --- /dev/null +++ b/addons/t9n/static/src/core/app.xml @@ -0,0 +1,8 @@ + + + + + Hello World! + + + diff --git a/addons/t9n/static/src/web/open_app_action.js b/addons/t9n/static/src/web/open_app_action.js new file mode 100644 index 0000000000000..dc66682513b26 --- /dev/null +++ b/addons/t9n/static/src/web/open_app_action.js @@ -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``; +} + +registry.category("actions").add("t9n.open_app", OpenApp); diff --git a/addons/t9n/views/t9n_menu_views.xml b/addons/t9n/views/t9n_menu_views.xml new file mode 100644 index 0000000000000..a37d1dc343985 --- /dev/null +++ b/addons/t9n/views/t9n_menu_views.xml @@ -0,0 +1,11 @@ + + + + Translate + translate + t9n.open_app + main + + + + diff --git a/addons/t9n/views/t9n_project_views.xml b/addons/t9n/views/t9n_project_views.xml new file mode 100644 index 0000000000000..dbd1c557a8be5 --- /dev/null +++ b/addons/t9n/views/t9n_project_views.xml @@ -0,0 +1,35 @@ + + + + Projects + t9n.project + tree,form + + + + t9n.project.form + t9n.project + +
+ +

+ +

+ + + + + + + + + + + + + +
+
+
+
+