From d0414481efc7a10ae4fe2833ade8072ebac43ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Tejero-Cantero?= <807608+alvorithm@users.noreply.github.com> Date: Thu, 22 Oct 2026 20:40:34 +0200 Subject: [PATCH] :globe_with_meridians: Translate the compare dialog labels and counts (#12566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compare dialog named changed properties, booleans and collection counts from English literals in the UI namespace, so no locale could replace them; it also passed bare numbers to tr, so a plural catalogue entry could never reach its singular form. Property labels now come from workspace.branches.compare.attr.*, the true and false values from .value-true and .value-false, and every count travels through i18n/c, which makes the singular msgstr[0] reachable. Numbers are rendered with the active locale, so Spanish reads 439,5 and groups thousands. An attr the catalogue does not name still falls back to its own key, humanised: a technical identifier rather than prose. The en.po entries for the counts gain their plural forms. es.po does not carry the compare area yet, so it stays untouched. AI-assisted-by: claude-opus-5-5 Signed-off-by: Álvaro Tejero Cantero --- .../main/ui/workspace/sidebar/branches.cljs | 63 +++++----- frontend/test/frontend_tests/runner.cljs | 2 + .../ui/branches_compare_labels_test.cljs | 71 +++++++++++ frontend/translations/en.po | 112 ++++++++++++++---- 4 files changed, 201 insertions(+), 47 deletions(-) create mode 100644 frontend/test/frontend_tests/ui/branches_compare_labels_test.cljs diff --git a/frontend/src/app/main/ui/workspace/sidebar/branches.cljs b/frontend/src/app/main/ui/workspace/sidebar/branches.cljs index 4bd05af116..e3794e2189 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/branches.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/branches.cljs @@ -88,21 +88,26 @@ (str (subs s 0 40) "…") s))) -(def ^:private attr->label - "Friendlier names for attrs whose raw key is unclear; every other attr - is humanized generically (kebab-case -> Capitalized words)." - {:rx "Corner radius" :ry "Corner radius" - :grow-type "Text auto-grow" :blend-mode "Blend mode" - :shape-ref "Component reference" :component-id "Component" - :component-file "Component library" :hidden "Visibility" - :blocked "Locked" :proportion-lock "Lock proportions" - :parent-id "Parent" :frame-id "Container"}) - (defn- attr-label - "Human-readable label for a changed property key." + "Human-readable label for a changed property key. The named attrs are + the ones whose raw key means nothing to a user, so each carries a + catalogue entry. Every other attr keeps its own key, humanised + (kebab-case -> Capitalized words): a technical identifier rather than + prose, and the last resort for an attr the catalogue does not name." [attr] - (or (get attr->label attr) - (-> (name attr) (str/replace #"-" " ") str/capital))) + (case attr + (:rx :ry) (tr "workspace.branches.compare.attr.corner-radius") + :grow-type (tr "workspace.branches.compare.attr.text-auto-grow") + :blend-mode (tr "workspace.branches.compare.attr.blend-mode") + :shape-ref (tr "workspace.branches.compare.attr.component-reference") + :component-id (tr "workspace.branches.compare.attr.component") + :component-file (tr "workspace.branches.compare.attr.component-library") + :hidden (tr "workspace.branches.compare.attr.visibility") + :blocked (tr "workspace.branches.compare.attr.locked") + :proportion-lock (tr "workspace.branches.compare.attr.lock-proportions") + :parent-id (tr "workspace.branches.compare.attr.parent") + :frame-id (tr "workspace.branches.compare.attr.container") + (-> (name attr) (str/replace #"-" " ") str/capital))) ;; --- Compare: grouping by component + per-item type labels @@ -181,10 +186,12 @@ (defn- fmt-number "At most two decimals, trailing zeros stripped (89.99999 -> \"90\", - 439.4999 -> \"439.5\", 386 -> \"386\")." + 439.4999 -> \"439.5\", 386 -> \"386\"), grouped and punctuated the way + the active locale writes numbers (\"439,5\" in Spanish)." [v] - (-> (.toFixed v 2) - (str/replace #"\.?0+$" ""))) + (let [locale (-> i18n/*current-locale* + (str/replace "_" "-"))] + (.toLocaleString v locale #js {:maximumFractionDigits 2}))) (defn- display-val "Compact, human-readable rendering of a property value. Raw uuids and @@ -195,12 +202,14 @@ (nil? v) "—" (string? v) (if (> (count v) 32) (str (subs v 0 32) "…") v) (number? v) (fmt-number v) - (boolean? v) (if v "true" "false") + (boolean? v) (if v + (tr "workspace.branches.compare.value-true") + (tr "workspace.branches.compare.value-false")) (keyword? v) (name v) (ct/inst? v) (ct/format-inst v :localized-date-time) (uuid? v) (str (subs (str v) 0 8) "…") - (sequential? v) (tr "workspace.branches.compare.n-items" (dm/str (count v))) - (map? v) (tr "workspace.branches.compare.n-props" (dm/str (count v))) + (sequential? v) (tr "workspace.branches.compare.n-items" (i18n/c (count v))) + (map? v) (tr "workspace.branches.compare.n-props" (i18n/c (count v))) :else (short-str v))) (defn- status-matches? @@ -1083,7 +1092,7 @@ (= :added status) (tr "workspace.branches.compare.subtitle-new" (if type-lbl (tr type-lbl) "")) (and (= :modified status) (seq attrs)) - (tr "workspace.branches.compare.subtitle-mods" (count attrs)) + (tr "workspace.branches.compare.subtitle-mods" (i18n/c (count attrs))) type-lbl (tr type-lbl) :else nil)] [:li {:class (stl/css-case :compare-item true @@ -1256,14 +1265,14 @@ (when stats [:div {:class (stl/css :compare-stats)} [:span {:class (stl/css :stat-badge :badge-added)} - (dm/str (:added stats) " " (tr "workspace.branches.compare.stats.added"))] + (tr "workspace.branches.compare.stats.added" (i18n/c (:added stats)))] [:span {:class (stl/css :stat-badge :badge-modified)} - (dm/str (:modified stats) " " (tr "workspace.branches.compare.stats.modified"))] + (tr "workspace.branches.compare.stats.modified" (i18n/c (:modified stats)))] [:span {:class (stl/css :stat-badge :badge-deleted)} - (dm/str (:deleted stats) " " (tr "workspace.branches.compare.stats.deleted"))] + (tr "workspace.branches.compare.stats.deleted" (i18n/c (:deleted stats)))] (when (pos? (:conflicts stats)) [:span {:class (stl/css :stat-badge :badge-conflict)} - (dm/str (:conflicts stats) " " (tr "workspace.branches.compare.stats.conflicts"))])])] + (tr "workspace.branches.compare.stats.conflicts" (i18n/c (:conflicts stats)))])])] [:> modal-content* {:class (stl/css :compare-content)} (cond @@ -1345,14 +1354,14 @@ (mf/html [:div {:class (stl/css :compare-footer-info)} [:> i/icon* {:icon-id i/info :size "s"}] - [:span (tr "workspace.branches.compare.footer-changes" total)] + [:span (tr "workspace.branches.compare.footer-changes" (i18n/c total))] (when (seq all-keys) [:span {:class (stl/css :compare-footer-selected)} (tr "workspace.branches.compare.footer-selected" - (str kept-count) (str (count all-keys)))]) + (str kept-count) (i18n/c (count all-keys)))]) (when conflicts? [:span {:class (stl/css :compare-footer-conflicts)} - (dm/str " · " (tr "workspace.branches.compare.footer-conflicts" conflicts))])]) + (dm/str " · " (tr "workspace.branches.compare.footer-conflicts" (i18n/c conflicts)))])]) :end (mf/html [:* diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 816ea362d9..836b43b7d7 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -110,6 +110,7 @@ [frontend-tests.tokens.style-dictionary-test] [frontend-tests.tokens.token-errors-test] [frontend-tests.tokens.workspace-tokens-remap-test] + [frontend-tests.ui.branches-compare-labels-test] [frontend-tests.ui.check-updates-test] [frontend-tests.ui.color-token-position-test] [frontend-tests.ui.colorpicker-token-set-order-test] @@ -267,6 +268,7 @@ 'frontend-tests.tokens.token-errors-test 'frontend-tests.tokens.logic.tokens-status-test 'frontend-tests.tokens.workspace-tokens-remap-test + 'frontend-tests.ui.branches-compare-labels-test 'frontend-tests.ui.check-updates-test 'frontend-tests.ui.color-token-position-test 'frontend-tests.ui.colorpicker-token-set-order-test diff --git a/frontend/test/frontend_tests/ui/branches_compare_labels_test.cljs b/frontend/test/frontend_tests/ui/branches_compare_labels_test.cljs new file mode 100644 index 0000000000..be088040b6 --- /dev/null +++ b/frontend/test/frontend_tests/ui/branches_compare_labels_test.cljs @@ -0,0 +1,71 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns frontend-tests.ui.branches-compare-labels-test + "The compare dialog names properties, booleans, numbers and collection + counts through the catalogue, so a translation replaces every one of + them. The catalogue here is a stand-in for en.po: its values are picked + to differ from the English the dialog used to hardcode." + (:require + [app.main.ui.workspace.sidebar.branches :as branches] + [app.util.i18n :as i18n] + [cljs.test :as t :include-macros true])) + +;; Access the private formatters for direct testing. +(def ^:private attr-label @#'branches/attr-label) +(def ^:private display-val @#'branches/display-val) +(def ^:private get-translations @#'i18n/get-translations) + +(def ^:private catalogue + #js {"workspace.branches.compare.attr.corner-radius" "Radio de esquina" + "workspace.branches.compare.attr.component-reference" "Referencia de componente" + "workspace.branches.compare.value-true" "verdadero" + "workspace.branches.compare.value-false" "falso" + "workspace.branches.compare.n-items" #js ["1 elemento" "%s elementos"] + "workspace.branches.compare.n-props" #js ["1 propiedad" "%s propiedades"]}) + +(defn- with-catalogue + "Install the stand-in catalogue for one test and put the previous one + back, so other test namespaces keep their own strings." + [f] + (let [translations (get-translations) + previous (unchecked-get translations "en")] + (i18n/set-default-translations catalogue) + (try + (f) + (finally + (i18n/set-translations "en" previous))))) + +(t/use-fixtures :each with-catalogue) + +(t/deftest property-labels-come-from-the-catalogue + (t/is (= "Radio de esquina" (attr-label :rx))) + (t/is (= "Radio de esquina" (attr-label :ry))) + (t/is (= "Referencia de componente" (attr-label :shape-ref)))) + +(t/deftest an-attr-with-no-name-of-its-own-falls-back-to-its-key + (t/is (= "Some internal attr" (attr-label :some-internal-attr)) + "an attr the catalogue does not name stays a humanised key")) + +(t/deftest boolean-values-come-from-the-catalogue + (t/is (= "verdadero" (display-val true))) + (t/is (= "falso" (display-val false)))) + +(t/deftest collection-counts-choose-the-plural-form + (t/is (= "1 elemento" (display-val [1])) + "a single item reads in the singular") + (t/is (= "3 elementos" (display-val [1 2 3]))) + (t/is (= "1 propiedad" (display-val {:x 1}))) + (t/is (= "2 propiedades" (display-val {:x 1 :y 2})))) + +(t/deftest numbers-are-punctuated-by-the-locale + (binding [i18n/*current-locale* "es"] + (t/is (= "439,5" (display-val 439.5)) + "a Spanish locale reads the decimal comma")) + (binding [i18n/*current-locale* "en"] + (t/is (= "439.5" (display-val 439.5))) + (t/is (= "90" (display-val 89.99999)) "at most two decimals") + (t/is (= "386" (display-val 386))))) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a40c5f26cb..40f8285671 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -6411,6 +6411,50 @@ msgstr "Compare" msgid "workspace.branches.compare.attr" msgstr "Property" +#: src/app/main/ui/workspace/sidebar/branches.cljs:99 +msgid "workspace.branches.compare.attr.blend-mode" +msgstr "Blend mode" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:101 +msgid "workspace.branches.compare.attr.component" +msgstr "Component" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:102 +msgid "workspace.branches.compare.attr.component-library" +msgstr "Component library" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:100 +msgid "workspace.branches.compare.attr.component-reference" +msgstr "Component reference" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:107 +msgid "workspace.branches.compare.attr.container" +msgstr "Container" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:97 +msgid "workspace.branches.compare.attr.corner-radius" +msgstr "Corner radius" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:105 +msgid "workspace.branches.compare.attr.lock-proportions" +msgstr "Lock proportions" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:104 +msgid "workspace.branches.compare.attr.locked" +msgstr "Locked" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:106 +msgid "workspace.branches.compare.attr.parent" +msgstr "Parent" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:98 +msgid "workspace.branches.compare.attr.text-auto-grow" +msgstr "Text auto-grow" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:103 +msgid "workspace.branches.compare.attr.visibility" +msgstr "Visibility" + #: src/app/main/ui/workspace/sidebar/branches.cljs:465 #, unused msgid "workspace.branches.compare.branch" @@ -6428,17 +6472,23 @@ msgstr "Could not load the changes of this branch" msgid "workspace.branches.compare.export" msgstr "Export diff" -#: src/app/main/ui/workspace/sidebar/branches.cljs:1118 +#: src/app/main/ui/workspace/sidebar/branches.cljs:1351 msgid "workspace.branches.compare.footer-changes" -msgstr "%s changes" +msgid_plural "workspace.branches.compare.footer-changes" +msgstr[0] "1 change" +msgstr[1] "%s changes" -#: src/app/main/ui/workspace/sidebar/branches.cljs:1121 +#: src/app/main/ui/workspace/sidebar/branches.cljs:1358 msgid "workspace.branches.compare.footer-conflicts" -msgstr "%s to resolve before merging" +msgid_plural "workspace.branches.compare.footer-conflicts" +msgstr[0] "1 to resolve before merging" +msgstr[1] "%s to resolve before merging" -#: src/app/main/ui/workspace/sidebar/branches.cljs:1391 +#: src/app/main/ui/workspace/sidebar/branches.cljs:1354 msgid "workspace.branches.compare.footer-selected" -msgstr "%s of %s selected" +msgid_plural "workspace.branches.compare.footer-selected" +msgstr[0] "%s of 1 selected" +msgstr[1] "%s of %s selected" #, unused msgid "workspace.branches.compare.group.colors" @@ -6481,13 +6531,17 @@ msgstr "Computing changes…" msgid "workspace.branches.compare.main" msgstr "main" -#: src/app/main/ui/workspace/sidebar/branches.cljs:220 +#: src/app/main/ui/workspace/sidebar/branches.cljs:209 msgid "workspace.branches.compare.n-items" -msgstr "%s items" +msgid_plural "workspace.branches.compare.n-items" +msgstr[0] "1 item" +msgstr[1] "%s items" -#: src/app/main/ui/workspace/sidebar/branches.cljs:221 +#: src/app/main/ui/workspace/sidebar/branches.cljs:210 msgid "workspace.branches.compare.n-props" -msgstr "%s properties" +msgid_plural "workspace.branches.compare.n-props" +msgstr[0] "1 property" +msgstr[1] "%s properties" #: src/app/main/ui/workspace/sidebar/branches.cljs:934 msgid "workspace.branches.compare.no-props" @@ -6556,25 +6610,35 @@ msgstr "Text" msgid "workspace.branches.compare.shape.variant" msgstr "Variant" -#: src/app/main/ui/workspace/sidebar/branches.cljs:1036 +#: src/app/main/ui/workspace/sidebar/branches.cljs:1262 msgid "workspace.branches.compare.stats.added" -msgstr "added" +msgid_plural "workspace.branches.compare.stats.added" +msgstr[0] "1 added" +msgstr[1] "%s added" -#: src/app/main/ui/workspace/sidebar/branches.cljs:1043 +#: src/app/main/ui/workspace/sidebar/branches.cljs:1269 msgid "workspace.branches.compare.stats.conflicts" -msgstr "conflicts" +msgid_plural "workspace.branches.compare.stats.conflicts" +msgstr[0] "1 conflict" +msgstr[1] "%s conflicts" -#: src/app/main/ui/workspace/sidebar/branches.cljs:1040 +#: src/app/main/ui/workspace/sidebar/branches.cljs:1266 msgid "workspace.branches.compare.stats.deleted" -msgstr "deleted" +msgid_plural "workspace.branches.compare.stats.deleted" +msgstr[0] "1 deleted" +msgstr[1] "%s deleted" -#: src/app/main/ui/workspace/sidebar/branches.cljs:1038 +#: src/app/main/ui/workspace/sidebar/branches.cljs:1264 msgid "workspace.branches.compare.stats.modified" -msgstr "modified" +msgid_plural "workspace.branches.compare.stats.modified" +msgstr[0] "1 modified" +msgstr[1] "%s modified" -#: src/app/main/ui/workspace/sidebar/branches.cljs:879 +#: src/app/main/ui/workspace/sidebar/branches.cljs:1089 msgid "workspace.branches.compare.subtitle-mods" -msgstr "%s property changes" +msgid_plural "workspace.branches.compare.subtitle-mods" +msgstr[0] "1 property change" +msgstr[1] "%s property changes" #: src/app/main/ui/workspace/sidebar/branches.cljs:876 msgid "workspace.branches.compare.subtitle-new" @@ -6612,6 +6676,14 @@ msgstr "Token" msgid "workspace.branches.compare.type.typography" msgstr "Typography" +#: src/app/main/ui/workspace/sidebar/branches.cljs:205 +msgid "workspace.branches.compare.value-false" +msgstr "false" + +#: src/app/main/ui/workspace/sidebar/branches.cljs:204 +msgid "workspace.branches.compare.value-true" +msgstr "true" + #: src/app/main/ui/workspace/sidebar/branches.cljs:1430 msgid "workspace.branches.conflicts.all-branch" msgstr "Use branch for all"