diff --git a/sbc_translation/__manifest__.py b/sbc_translation/__manifest__.py index f38d1541c..6f214f104 100644 --- a/sbc_translation/__manifest__.py +++ b/sbc_translation/__manifest__.py @@ -36,13 +36,6 @@ "license": "AGPL-3", "website": "https://github.com/CompassionCH/compassion-modules", "depends": ["sbc_compassion", "partner_contact_birthdate", "portal"], - "assets": { - "web.assets_frontend": [ - "sbc_translation/static/src/frontend/**/*.css", - "sbc_translation/static/src/frontend/**/*.xml", - "sbc_translation/static/src/frontend/**/*.esm.js", - ], - }, "data": [ "security/ir_groups.xml", "security/ir.model.access.csv", @@ -53,7 +46,6 @@ "data/mail_template.xml", "data/update_translation_priority_cron.xml", "data/queue_job.xml", - "templates/portal_templates.xml", "views/translation_user_view.xml", "views/correspondence_view.xml", "views/translation_pool_view.xml", diff --git a/sbc_translation/controllers/main.py b/sbc_translation/controllers/main.py index cfdc7aaf2..0306b508e 100644 --- a/sbc_translation/controllers/main.py +++ b/sbc_translation/controllers/main.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (C) 2023 Compassion CH (http://www.compassion.ch) +# Copyright (C) 2023-2026 Compassion CH (http://www.compassion.ch) # Releasing children from poverty in Jesus' name # # The licence is in the file __manifest__.py @@ -11,75 +11,31 @@ from werkzeug.utils import redirect from odoo import http -from odoo.http import request - -from odoo.addons.portal.controllers.portal import CustomerPortal +from odoo.tools import file_open _logger = logging.getLogger(__name__) -class TranslationPlatformController(CustomerPortal): - @http.route( - "/my/translation-platform", - type="http", - auth="user", - website=True, - ) - def translation_platform_portal(self, **kwargs): - """ - Portal page for the Translation Platform OWL app. - Only accessible to authenticated users who belong to the - sbc_translation.group_user group. - """ - if not request.env.user.has_group("sbc_translation.group_user"): - return redirect("/my") - return request.render("sbc_translation.portal_translation_platform", {}) - +class TranslationPlatformController(http.Controller): @http.route( ["/translation-platform", "/translation-platform/"], type="http", - auth="user", - website=True, + auth="public", ) - def translation_platform_legacy(self, page="", **kwargs): + def translation_platform(self, page=""): + """Serve the built translation-platform-web SPA from + `static/tp/`. + + `static/tp/` is the destination for the `npm run build` + output of the external translation-platform-web repo: copy + the `dist/` folder there at release time. The webapp itself + does client-side routing; this controller only serves + `index.html` for app routes and redirects asset URLs into + `/sbc_translation/static/tp/...`. """ - Legacy route: redirect old standalone-app URLs to the new portal page. - """ - return redirect("/my/translation-platform", 301) - - def _prepare_home_portal_values(self, counters): - values = super()._prepare_home_portal_values(counters) - if not request.env.user.has_group("sbc_translation.group_user"): - return values - partner = request.env.user.partner_id - translator = request.env["translation.user"].search( - [("partner_id", "=", partner.id)] - ) - if translator and "letters_to_translate" in counters: - if translator.translation_skills: - nb_letters = request.env["correspondence"].search_count( - [ - ("state", "=", "Global Partner translation queue"), - ("translation_status", "=", "to do"), - ("new_translator_id", "=", False), - ( - "translation_competence_id.skill_ids", - "in", - translator.translation_skills.ids, - ), - ] - ) - values["letters_to_translate"] = nb_letters - else: - values["letters_to_translate"] = 1 - if translator and "letters_in_progress" in counters: - nb_letters = request.env["correspondence"].search_count( - [ - ("state", "=", "Global Partner translation queue"), - ("translation_status", "!=", "done"), - ("new_translator_id", "=", translator.id), - ] - ) - values["letters_in_progress"] = nb_letters - values["translator"] = translator - return values + if ( + "assets" in page or page.endswith(".png") or page.endswith(".jpg") + ): + return redirect(f"/sbc_translation/static/tp/{page}") + with file_open("sbc_translation/static/tp/index.html") as app: + return app.read() diff --git a/sbc_translation/models/correspondence.py b/sbc_translation/models/correspondence.py index 1182180f3..d75009c8a 100644 --- a/sbc_translation/models/correspondence.py +++ b/sbc_translation/models/correspondence.py @@ -122,11 +122,28 @@ def _compute_translation_priority_name(self): ) def _compute_translation_url(self): - base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url", "") - base_url = base_url.rstrip("/") + """Build the link translators click to open this letter in + the webapp. + + The base URL is read from the system parameter + `sbc_translation.webapp_base_url`. If unset, the webapp is + assumed to be served by Odoo itself at `/translation-platform` + (see `controllers/main.py`), and the link falls back to + `/translation-platform`. + + Set the parameter to point at an external host + (e.g. `http://localhost:5173` for `npm run dev`) when the + webapp is not served by Odoo. + """ + icp = self.env["ir.config_parameter"].sudo() + webapp_url = icp.get_param("sbc_translation.webapp_base_url") + if not webapp_url: + base_url = icp.get_param("web.base.url", "").rstrip("/") + webapp_url = f"{base_url}/translation-platform" + webapp_url = webapp_url.rstrip("/") for letter in self: letter.translation_url = ( - f"{base_url}/odoo/translation-platform?letterId={letter.id}" + f"{webapp_url}/letters/letter-edit/{letter.id}" ) def _compute_paragraph_ids(self): @@ -564,6 +581,13 @@ def submit_translation(self, letter_elements, translator_id=None) -> bool: return True def action_approve_translation(self): + """Manager-side approval of a letter in `to validate` status. + + Marks the translator's skill for this letter's competence as + verified (if not already), clears any reported translation + issue, records the current user as supervisor, and runs the + post-processing step that ships the letter on. + """ for letter in self: skill_to_validate = letter.new_translator_id.translation_skills.filtered( lambda s, _letter=letter: s.competence_id @@ -619,6 +643,16 @@ def list_letters(self): """API call to fetch letters to translate""" return [letter.get_letter_info() for letter in self.sorted("scanned_date")] + # Webapp-facing aliases for the action_* methods. + # translation-platform-web calls these by their unprefixed names; + # the action_* names stay for backend button bindings (Odoo + # convention). + def remove_local_translate(self): + return self.action_remove_local_translate() + + def resubmit_to_translation(self): + return self.action_resubmit_to_translation() + def get_letter_info(self): """Translation Platform API for fetching letter data.""" self.ensure_one() diff --git a/sbc_translation/static/img/menu_icon.png b/sbc_translation/static/img/menu_icon.png deleted file mode 100644 index 0dfa89b60..000000000 Binary files a/sbc_translation/static/img/menu_icon.png and /dev/null differ diff --git a/sbc_translation/static/img/menu_icon_small.png b/sbc_translation/static/img/menu_icon_small.png deleted file mode 100644 index 445d02e3f..000000000 Binary files a/sbc_translation/static/img/menu_icon_small.png and /dev/null differ diff --git a/sbc_translation/static/src/frontend/components/tp_child_modal.esm.js b/sbc_translation/static/src/frontend/components/tp_child_modal.esm.js deleted file mode 100644 index 6652af45e..000000000 --- a/sbc_translation/static/src/frontend/components/tp_child_modal.esm.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Component } from "@odoo/owl"; -import { TpModal } from "./tp_modal.esm"; - -/** - * Child protection policy modal. - * Props: - * active {Boolean} - * onClose {Function} - */ -export class TpChildModal extends Component { - static template = "sbc_translation.TpChildModal"; - - static components = { TpModal }; - - static props = { - active: { type: Boolean }, - onClose: { type: Function }, - }; -} - -export default TpChildModal; diff --git a/sbc_translation/static/src/frontend/components/tp_child_modal.xml b/sbc_translation/static/src/frontend/components/tp_child_modal.xml deleted file mode 100644 index 422d500c6..000000000 --- a/sbc_translation/static/src/frontend/components/tp_child_modal.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - -
-
Expected/acceptable behaviors:
-
    -
  • I will demonstrate the proper respect and dignity of all children and will demonstrate Jesus's love and care for them, regardless of their gender, age, race, religion, social background, culture, special need or disability.
  • -
  • I will maintain appropriate and reasonable expectations for children based on their age and ability level.
  • -
  • I will engage in age-appropriate communication with beneficiaries.
  • -
  • I will submit to the appropriate background or police checks as permissible by law prior to face-to-face contact with beneficiaries.
  • -
  • I will engage in activities with beneficiaries only in open or visible places, and in the event that an activity needs to take place in an enclosed space, I will ensure that at least one other approved adult is present.
  • -
  • If I witness child abuse, know a child is in danger, observe any concerning behaviors from colleagues, partners or other representatives, or a child comes to me with a report of abuse, I will take it seriously and report it to the proper staff or relevant authorities.
  • -
  • I will keep all information about child protection investigations confidential, keeping in mind privacy and dignity concerns of all involved.
  • -
  • I will contribute to building an environment where children are respected and encouraged to discuss their concerns and rights.
  • -
  • I will follow Compassion's rules about communication with beneficiaries, including social media interaction.
  • -
-
Unacceptable behaviors:
-
    -
  • I will not solicit a romantic/dating relationship and will never engage in sexual/sexually suggestive behavior with any beneficiary, regardless of age.
  • -
  • I will never engage in sexual/sexually suggestive behavior with any child under age 18, regardless of the legal age of consent in-country.
  • -
  • I will never use language that is verbally/emotionally abusive, sexually suggestive, degrading, humiliating, shaming or is otherwise culturally inappropriate with a beneficiary.
  • -
  • I will not touch beneficiaries in an inappropriate or culturally insensitive way.
  • -
  • I will never use any kind of physical discipline or physical punishment as a method of correction for beneficiaries.
  • -
  • I will never travel alone with a beneficiary, without an approved representative or prior approval, except in a life-threatening emergency.
  • -
  • I will not hire any child in any harmful form of child labor and follow local laws regarding child employment.
  • -
  • I will not gather, disclose or support the disclosure of information about beneficiaries or their families without prior, express permission.
  • -
-
-

Child Protection Videos

- -
-
-
-
- -
diff --git a/sbc_translation/static/src/frontend/components/tp_content_editor.esm.js b/sbc_translation/static/src/frontend/components/tp_content_editor.esm.js deleted file mode 100644 index 69dd18fc9..000000000 --- a/sbc_translation/static/src/frontend/components/tp_content_editor.esm.js +++ /dev/null @@ -1,52 +0,0 @@ -import { Component, useState } from "@odoo/owl"; -import { TpModal } from "./tp_modal.esm"; - -/** - * Tips modal showing translation best practices. - * Props: - * active {Boolean} - * onClose {Function} - */ -class TpTipsModal extends Component { - static template = "sbc_translation.TpTipsModal"; - static components = { TpModal }; - static props = { - active: { type: Boolean }, - onClose: { type: Function }, - }; -} - -/** - * Content editor for translating individual paragraphs. - * Props: - * letter {Object} - letter data with translatedElements array - */ -export class TpContentEditor extends Component { - static template = "sbc_translation.TpContentEditor"; - - static components = { TpModal, TpTipsModal }; - - static props = { - letter: { type: Object }, - }; - - state = useState({ - sourceElem: undefined, - showTips: false, - }); - - openSource(elemId) { - const elem = this.props.letter.translatedElements.find( - (e) => e.id === elemId, - ); - if (elem && elem.type === "paragraph") { - this.state.sourceElem = elem.source || ""; - } - } - - openTips() { - this.state.showTips = true; - } -} - -export default TpContentEditor; diff --git a/sbc_translation/static/src/frontend/components/tp_content_editor.xml b/sbc_translation/static/src/frontend/components/tp_content_editor.xml deleted file mode 100644 index 1c529f711..000000000 --- a/sbc_translation/static/src/frontend/components/tp_content_editor.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - -
-
    -
  • Keep the same tone and emotional register as the source text.
  • -
  • Do not paraphrase – translate as closely as possible.
  • -
  • Preserve paragraph breaks from the source.
  • -
  • If the source text is unclear, add a comment explaining the issue.
  • -
  • Names of people and places should be kept as they appear.
  • -
  • If you're unsure about a phrase, use the comment field to flag it.
  • -
-
-
-
- - - -
-
- -
- — Page Break — -
- -
-
-
Translated Content
-