From 139dd7d80f78c120ac3034ee054431424f76ef11 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 13 Jun 2024 11:45:01 +0200 Subject: [PATCH] :tada: Add new translations management script --- frontend/package.json | 1 + frontend/scripts/translations.js | 377 ++++++++++++++++++ frontend/scripts/validate-translations.js | 31 -- frontend/src/app/main/ui/auth/register.cljs | 9 +- frontend/src/app/main/ui/confirm.cljs | 10 +- frontend/src/app/main/ui/dashboard/fonts.cljs | 2 +- .../app/main/ui/dashboard/placeholder.cljs | 4 +- frontend/src/app/main/ui/dashboard/team.cljs | 8 +- frontend/src/app/main/ui/export.cljs | 12 +- frontend/src/app/main/ui/settings.cljs | 14 +- .../src/app/main/ui/settings/password.cljs | 22 +- .../src/app/main/ui/settings/sidebar.cljs | 5 +- .../ui/viewer/inspect/attributes/text.cljs | 16 +- .../main/ui/viewer/inspect/right_sidebar.cljs | 24 +- .../app/main/ui/workspace/context_menu.cljs | 2 +- .../ui/workspace/sidebar/assets/common.cljs | 18 +- .../main/ui/workspace/sidebar/history.cljs | 94 ++--- .../sidebar/options/menus/component.cljs | 10 +- .../main/ui/workspace/sidebar/shortcuts.cljs | 298 +++++++------- .../main/ui/workspace/viewport/top_bar.cljs | 5 +- frontend/src/app/util/i18n.cljs | 15 +- frontend/yarn.lock | 8 + 22 files changed, 677 insertions(+), 308 deletions(-) create mode 100755 frontend/scripts/translations.js delete mode 100644 frontend/scripts/validate-translations.js diff --git a/frontend/package.json b/frontend/package.json index e716c82fb..56835d511 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -55,6 +55,7 @@ "draft-js": "git+https://github.com/penpot/draft-js.git#commit=4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0", "express": "^4.19.2", "fancy-log": "^2.0.0", + "getopts": "^2.3.0", "gettext-parser": "^8.0.0", "gulp": "4.0.2", "gulp-concat": "^2.6.1", diff --git a/frontend/scripts/translations.js b/frontend/scripts/translations.js new file mode 100755 index 000000000..a781a45d8 --- /dev/null +++ b/frontend/scripts/translations.js @@ -0,0 +1,377 @@ +#!/usr/bin/env node + +import getopts from "getopts"; +import { promises as fs, createReadStream } from "fs"; +import gt from "gettext-parser"; +import l from "lodash"; +import path from "path"; +import readline from "readline"; + +const baseLocale = "en"; + +async function* getFiles(dir) { + // console.log("getFiles", dir) + const dirents = await fs.readdir(dir, { withFileTypes: true }); + for (const dirent of dirents) { + let res = path.resolve(dir, dirent.name); + res = path.relative(".", res); + + if (dirent.isDirectory()) { + yield* getFiles(res); + } else { + yield res; + } + } +} + +async function translationExists(locale) { + const target = path.normalize("./translations/"); + const targetPath = path.join(target, `${locale}.po`); + + try { + const result = await fs.stat(targetPath); + return true; + } catch (cause) { + return false; + } +} + +async function readLocaleByPath(path) { + const content = await fs.readFile(path); + return gt.po.parse(content, "utf-8"); +} + +async function writeLocaleByPath(path, data) { + const buff = gt.po.compile(data, { sort: true }); + await fs.writeFile(path, buff); +} + +async function readLocale(locale) { + const target = path.normalize("./translations/"); + const targetPath = path.join(target, `${locale}.po`); + return readLocaleByPath(targetPath); +} + +async function writeLocale(locale, data) { + const target = path.normalize("./translations/"); + const targetPath = path.join(target, `${locale}.po`); + return writeLocaleByPath(targetPath, data); +} + +async function* scanLocales() { + const fileRe = /.+\.po$/; + const target = path.normalize("./translations/"); + const parent = path.join(target, ".."); + + for await (const f of getFiles(target)) { + if (!fileRe.test(f)) continue; + const data = path.parse(f); + yield data; + } +} + +async function processLocale(options, f) { + let locales = options.locale; + if (typeof locales === "string") { + locales = locales.split(/,/); + } else if (Array.isArray(locales)) { + } else if (locales === undefined) { + } else { + console.error(`Invalid value found on locales parameter: '${locales}'`); + process.exit(-1); + } + + for await (const { name } of scanLocales()) { + if (locales === undefined || locales.includes(name)) { + await f(name); + } + } +} + +async function processTranslation(data, prefix, f) { + for (let key of Object.keys(data.translations[""])) { + if (key === prefix || key.startsWith(prefix)) { + let value = data.translations[""][key]; + value = await f(value); + data.translations[""][key] = value; + } + } + return data; +} + +async function* readLines(filePath) { + const fileStream = createReadStream(filePath); + + const reader = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity, + }); + + let counter = 1; + + for await (const line of reader) { + yield [counter, line]; + counter++; + } +} + +const trRe1 = /\(tr\s+"([\w\.\-]+)"/g; + +function getTranslationStrings(line) { + const result = Array.from(line.matchAll(trRe1)).map((match) => { + return match[1]; + }); + + return result; +} + +async function deleteByPrefix(options, prefix, ...params) { + if (!prefix) { + console.error(`Prefix undefined`); + process.exit(1); + } + + await processLocale(options, async (locale) => { + const data = await readLocale(locale); + let deleted = []; + + for (const [key, value] of Object.entries(data.translations[""])) { + if (key.startsWith(prefix)) { + delete data.translations[""][key]; + deleted.push(key); + } + } + + await writeLocale(locale, data); + + console.log( + `=> Processed locale '${locale}': deleting prefix '${prefix}' (deleted=${deleted.length})`, + ); + + if (options.verbose) { + for (let key of deleted) { + console.log(`-> Deleted key: ${key}`); + } + } + }); +} + +async function markFuzzy(options, prefix, ...other) { + if (!prefix) { + console.error(`Prefix undefined`); + process.exit(1); + } + + await processLocale(options, async (locale) => { + let data = await readLocale(locale); + data = await processTranslation(data, prefix, (translation) => { + if (translation.comments === undefined) { + translation.comments = {}; + } + + const flagData = translation.comments.flag ?? ""; + const flags = flagData.split(/\s*,\s*/).filter((s) => s !== ""); + + if (!flags.includes("fuzzy")) { + flags.push("fuzzy"); + } + + translation.comments.flag = flags.join(", "); + + console.log( + `=> Processed '${locale}': marking fuzzy '${translation.msgid}'`, + ); + + return translation; + }); + + await writeLocale(locale, data); + }); +} + +async function rehash(options, ...other) { + const fileRe = /.+\.(?:clj|cljs|cljc)$/; + + // Iteration 1: process all locales and update it with existing + // entries on the source code. + + const used = await (async function () { + const result = {}; + + for await (const f of getFiles("src")) { + if (!fileRe.test(f)) continue; + + for await (const [n, line] of readLines(f)) { + const strings = getTranslationStrings(line); + + strings.forEach((key) => { + const entry = `${f}:${n}`; + if (result[key] !== undefined) { + result[key].push(entry); + } else { + result[key] = [entry]; + } + }); + } + } + + await processLocale({ locale: baseLocale }, async (locale) => { + const data = await readLocale(locale); + + for (let [key, val] of Object.entries(result)) { + let entry = data.translations[""][key]; + + if (entry === undefined) { + entry = { + msgid: key, + comments: { + reference: val.join(", "), + flag: "fuzzy", + }, + msgstr: [""], + }; + } else { + if (entry.comments === undefined) { + entry.comments = {}; + } + + entry.comments.reference = val.join(", "); + + const flagData = entry.comments.flag ?? ""; + const flags = flagData.split(/\s*,\s*/).filter((s) => s !== ""); + + if (flags.includes("unused")) { + flags = flags.filter((o) => o !== "unused"); + } + + entry.comments.flag = flags.join(", "); + } + + data.translations[""][key] = entry; + } + + await writeLocale(locale, data); + + const keys = Object.keys(data.translations[""]); + console.log(`=> Found ${keys.length} used translations`); + }); + + return result; + })(); + + // Iteration 2: process only base locale and properly detect unused + // translation strings. + + await (async function () { + let totalUnused = 0; + + await processLocale({ locale: baseLocale }, async (locale) => { + const data = await readLocale(locale); + + for (let [key, val] of Object.entries(data.translations[""])) { + if (key === "") continue; + + if (!used.hasOwnProperty(key)) { + totalUnused++; + + const entry = data.translations[""][key]; + if (entry.comments === undefined) { + entry.comments = {}; + } + + const flagData = entry.comments.flag ?? ""; + const flags = flagData.split(/\s*,\s*/).filter((s) => s !== ""); + + if (!flags.includes("unused")) { + flags.push("unused"); + } + + entry.comments.flag = flags.join(", "); + + data.translations[""][key] = entry; + } + } + + await writeLocale(locale, data); + }); + + console.log(`=> Found ${totalUnused} unused strings`); + })(); +} + +async function synchronize(options, ...other) { + const baseData = await readLocale(baseLocale); + + await processLocale(options, async (locale) => { + if (locale === baseLocale) return; + + const data = await readLocale(locale); + + for (let [key, val] of Object.entries(baseData.translations[""])) { + if (key === "") continue; + + const baseEntry = baseData.translations[""][key]; + const entry = data.translations[""][key]; + + if (entry === undefined) { + // Do nothing + } else { + entry.comments = baseEntry.comments; + data.translations[""][key] = entry; + } + } + + for (let [key, val] of Object.entries(data.translations[""])) { + if (key === "") continue; + + const baseEntry = baseData.translations[""][key]; + const entry = data.translations[""][key]; + + if (baseEntry === undefined) { + delete data.translations[""][key]; + } + } + + await writeLocale(locale, data); + }); +} + +const options = getopts(process.argv.slice(2), { + boolean: ["h", "v"], + alias: { + help: ["h"], + locale: ["l"], + verbose: ["v"], + }, + stopEarly: true, +}); + +const [command, ...params] = options._; + +if (command === "rehash") { + await rehash(options, ...params); +} else if (command === "sync") { + await synchronize(options, ...params); +} else if (command === "delete") { + await deleteByPrefix(options, ...params); +} else if (command === "fuzzy") { + await markFuzzy(options, ...params); +} else { + console.log(`Translations manipulation script. +How to use: +./scripts/translation.js + +Available options: + + --locale -l : specify a concrete locale + --verbose -v : enables verbose output + --help -h : prints this help + +Available subcommands: + + rehash : reads and writes all translations files, sorting and validating + sync : synchronize baselocale file with all other locale files + delete : delete all entries that matches the prefix + fuzzy : mark as fuzzy all entries that matches the prefix +`); +} diff --git a/frontend/scripts/validate-translations.js b/frontend/scripts/validate-translations.js deleted file mode 100644 index 0fe05e2c9..000000000 --- a/frontend/scripts/validate-translations.js +++ /dev/null @@ -1,31 +0,0 @@ -import { promises as fs } from "fs"; -import gt from "gettext-parser"; -import l from "lodash"; -import path from "path"; - -async function* getFiles(dir) { - const dirents = await fs.readdir(dir, { withFileTypes: true }); - for (const dirent of dirents) { - const res = path.resolve(dir, dirent.name); - if (dirent.isDirectory()) { - yield* getFiles(res); - } else { - yield res; - } - } -} - -(async () => { - const fileRe = /.+\.po$/; - const target = path.normalize("./translations/"); - const parent = path.join(target, ".."); - for await (const f of getFiles(target)) { - if (!fileRe.test(f)) continue; - const entry = path.relative(parent, f); - console.log(`=> processing: ${entry}`); - const content = await fs.readFile(f); - const data = gt.po.parse(content, "utf-8"); - const buff = gt.po.compile(data, { sort: true }); - await fs.writeFile(f, buff); - } -})(); diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 1c1d0c5a1..90daacf1e 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -18,7 +18,7 @@ [app.main.ui.components.forms :as fm] [app.main.ui.components.link :as lk] [app.main.ui.icons :as i] - [app.util.i18n :refer [tr tr-html]] + [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.storage :as sto] [beicon.v2.core :as rx] @@ -197,10 +197,11 @@ [] (let [terms-label (mf/html - [:& tr-html + [:> i18n/tr-html* {:tag-name "div" - :label "auth.terms-and-privacy-agreement" - :params [cf/terms-of-service-uri cf/privacy-policy-uri]}])] + :content (tr "auth.terms-and-privacy-agreement" + cf/terms-of-service-uri + cf/privacy-policy-uri)}])] [:div {:class (stl/css :fields-row :input-visible :accept-terms-and-privacy-wrapper)} [:& fm/input {:name :accept-terms-and-privacy diff --git a/frontend/src/app/main/ui/confirm.cljs b/frontend/src/app/main/ui/confirm.cljs index 66fd00e92..abb11ea8c 100644 --- a/frontend/src/app/main/ui/confirm.cljs +++ b/frontend/src/app/main/ui/confirm.cljs @@ -11,7 +11,7 @@ [app.main.store :as st] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [tr t]] + [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as k] [goog.events :as events] [rumext.v2 :as mf]) @@ -30,15 +30,13 @@ cancel-label accept-label accept-style] :as props}] - (let [locale (mf/deref i18n/locale) - - on-accept (or on-accept identity) + (let [on-accept (or on-accept identity) on-cancel (or on-cancel identity) - message (or message (t locale "ds.confirm-title")) + message (or message (tr "ds.confirm-title")) cancel-label (or cancel-label (tr "ds.confirm-cancel")) accept-label (or accept-label (tr "ds.confirm-ok")) accept-style (or accept-style :danger) - title (or title (t locale "ds.confirm-title")) + title (or title (tr "ds.confirm-title")) accept-fn (mf/use-callback diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index 174b9ecfb..514be108d 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -167,7 +167,7 @@ [:div {:class (stl/css :dashboard-fonts-hero)} [:div {:class (stl/css :desc)} [:h2 (tr "labels.upload-custom-fonts")] - [:& i18n/tr-html {:label "dashboard.fonts.hero-text1"}] + [:> i18n/tr-html* {:content (tr "dashboard.fonts.hero-text1")}] [:button {:class (stl/css :btn-primary) :on-click on-click diff --git a/frontend/src/app/main/ui/dashboard/placeholder.cljs b/frontend/src/app/main/ui/dashboard/placeholder.cljs index 08798b707..15cb2d98d 100644 --- a/frontend/src/app/main/ui/dashboard/placeholder.cljs +++ b/frontend/src/app/main/ui/dashboard/placeholder.cljs @@ -12,7 +12,7 @@ [rumext.v2 :as mf])) (mf/defc empty-placeholder - [{:keys [dragging? limit origin create-fn] :as props}] + [{:keys [dragging? limit origin create-fn]}] (let [on-click (mf/use-fn (mf/deps create-fn) @@ -29,7 +29,7 @@ [:div {:class (stl/css :grid-empty-placeholder :libs) :data-testid "empty-placeholder"} [:div {:class (stl/css :text)} - [:& i18n/tr-html {:label "dashboard.empty-placeholder-drafts"}]]] + [:> i18n/tr-html* {:content (tr "dashboard.empty-placeholder-drafts")}]]] :else [:div diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index e3d19a10e..0d78ee520 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -693,8 +693,8 @@ [:div {:class (stl/css :empty-invitations)} [:span (tr "labels.no-invitations")] (when can-invite? - [:& i18n/tr-html {:label "labels.no-invitations-hint" - :tag-name "span"}])]) + [:> i18n/tr-html* {:content (tr "labels.no-invitations-hint") + :tag-name "span"}])]) (mf/defc invitation-section [{:keys [team invitations] :as props}] @@ -878,8 +878,8 @@ [:div {:class (stl/css :webhooks-hero-container)} [:h2 {:class (stl/css :hero-title)} (tr "labels.webhooks")] - [:& i18n/tr-html {:class (stl/css :hero-desc) - :label "dashboard.webhooks.description"}] + [:> i18n/tr-html* {:class (stl/css :hero-desc) + :content (tr "dashboard.webhooks.description")}] [:button {:class (stl/css :hero-btn) :on-click #(st/emit! (modal/show :webhook {}))} (tr "dashboard.webhooks.create")]]) diff --git a/frontend/src/app/main/ui/export.cljs b/frontend/src/app/main/ui/export.cljs index c13b074a4..f1c0a004b 100644 --- a/frontend/src/app/main/ui/export.cljs +++ b/frontend/src/app/main/ui/export.cljs @@ -432,12 +432,12 @@ [:label {:for (str "export-" type) :class (stl/css-case :global/checked (= selected type))} ;; Execution time translation strings: - ;; dashboard.export.options.all.message - ;; dashboard.export.options.all.title - ;; dashboard.export.options.detach.message - ;; dashboard.export.options.detach.title - ;; dashboard.export.options.merge.message - ;; dashboard.export.options.merge.title + ;; (tr "dashboard.export.options.all.message") + ;; (tr "dashboard.export.options.all.title") + ;; (tr "dashboard.export.options.detach.message") + ;; (tr "dashboard.export.options.detach.title") + ;; (tr "dashboard.export.options.merge.message") + ;; (tr "dashboard.export.options.merge.title") [:span {:class (stl/css-case :global/checked (= selected type))} (when (= selected type) i/status-tick)] diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index 04a9a08f2..ce6099c88 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -33,18 +33,16 @@ (mf/defc settings [{:keys [route] :as props}] (let [section (get-in route [:data :name]) - profile (mf/deref refs/profile) - locale (mf/deref i18n/locale)] + profile (mf/deref refs/profile)] (hooks/use-shortcuts ::dashboard sc/shortcuts) - (mf/use-effect - #(when (nil? profile) + (mf/with-effect [profile] + (when (nil? profile) (st/emit! (rt/nav :auth-login)))) [:section {:class (stl/css :dashboard-layout-refactor :dashboard)} [:& sidebar {:profile profile - :locale locale :section section}] [:div {:class (stl/css :dashboard-content)} @@ -52,16 +50,16 @@ [:section {:class (stl/css :dashboard-container)} (case section :settings-profile - [:& profile-page {:locale locale}] + [:& profile-page] :settings-feedback [:& feedback-page] :settings-password - [:& password-page {:locale locale}] + [:& password-page] :settings-options - [:& options-page {:locale locale}] + [:& options-page] :settings-access-tokens [:& access-tokens-page])]]])) diff --git a/frontend/src/app/main/ui/settings/password.cljs b/frontend/src/app/main/ui/settings/password.cljs index bceecf29f..3f3a4ca72 100644 --- a/frontend/src/app/main/ui/settings/password.cljs +++ b/frontend/src/app/main/ui/settings/password.cljs @@ -13,7 +13,7 @@ [app.main.store :as st] [app.main.ui.components.forms :as fm] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t tr]] + [app.util.i18n :as i18n :refer [tr]] [cljs.spec.alpha :as s] [rumext.v2 :as mf])) @@ -69,7 +69,7 @@ ::password-old])) (mf/defc password-form - [{:keys [locale] :as props}] + [] (let [initial (mf/use-memo (constantly {:password-old nil})) form (fm/use-form :spec ::password-form :validators [(fm/validate-not-all-spaces :password-old (tr "auth.password-not-empty")) @@ -86,35 +86,35 @@ {:type "password" :name :password-old :auto-focus? true - :label (t locale "labels.old-password")}]] + :label (tr "labels.old-password")}]] [:div {:class (stl/css :fields-row)} [:& fm/input {:type "password" :name :password-1 :show-success? true - :label (t locale "labels.new-password")}]] + :label (tr "labels.new-password")}]] [:div {:class (stl/css :fields-row)} [:& fm/input {:type "password" :name :password-2 :show-success? true - :label (t locale "labels.confirm-password")}]] + :label (tr "labels.confirm-password")}]] [:> fm/submit-button* - {:label (t locale "dashboard.password-change") + {:label (tr "dashboard.password-change") :data-testid "submit-password" :class (stl/css :update-btn)}]])) ;; --- Password Page (mf/defc password-page - [{:keys [locale]}] - (mf/use-effect - #(dom/set-html-title (tr "title.settings.password"))) + [] + (mf/with-effect [] + (dom/set-html-title (tr "title.settings.password"))) [:section {:class (stl/css :dashboard-settings)} [:div {:class (stl/css :form-container)} - [:h2 (t locale "dashboard.password-change")] - [:& password-form {:locale locale}]]]) + [:h2 (tr "dashboard.password-change")] + [:& password-form]]]) diff --git a/frontend/src/app/main/ui/settings/sidebar.cljs b/frontend/src/app/main/ui/settings/sidebar.cljs index 7fbe6021f..651129a36 100644 --- a/frontend/src/app/main/ui/settings/sidebar.cljs +++ b/frontend/src/app/main/ui/settings/sidebar.cljs @@ -115,10 +115,9 @@ (mf/defc sidebar {::mf/wrap [mf/memo] ::mf/props :obj} - [{:keys [profile locale section]}] + [{:keys [profile section]}] [:div {:class (stl/css :dashboard-sidebar :settings)} [:& sidebar-content {:profile profile :section section}] - [:& profile-section {:profile profile - :locale locale}]]) + [:& profile-section {:profile profile}]]) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs index d2a3eceb8..66cf55eb3 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs @@ -142,9 +142,9 @@ [:div {:class (stl/css :global/attr-label)} (tr "inspect.attributes.typography.text-decoration")] ;; Execution time translation strings: - ;; inspect.attributes.typography.text-decoration.none - ;; inspect.attributes.typography.text-decoration.strikethrough - ;; inspect.attributes.typography.text-decoration.underline + ;; (tr "inspect.attributes.typography.text-decoration.none") + ;; (tr "inspect.attributes.typography.text-decoration.strikethrough") + ;; (tr "inspect.attributes.typography.text-decoration.underline") [:div {:class (stl/css :global/attr-value)} [:& copy-button {:data (copy-style-data style :text-decoration)} [:div {:class (stl/css :button-children)} @@ -155,11 +155,11 @@ [:div {:class (stl/css :global/attr-label)} (tr "inspect.attributes.typography.text-transform")] ;; Execution time translation strings: - ;; inspect.attributes.typography.text-transform.lowercase - ;; inspect.attributes.typography.text-transform.none - ;; inspect.attributes.typography.text-transform.titlecase - ;; inspect.attributes.typography.text-transform.uppercase - ;; inspect.attributes.typography.text-transform.unset + ;; (tr "inspect.attributes.typography.text-transform.lowercase") + ;; (tr "inspect.attributes.typography.text-transform.none") + ;; (tr "inspect.attributes.typography.text-transform.titlecase") + ;; (tr "inspect.attributes.typography.text-transform.uppercase") + ;; (tr "inspect.attributes.typography.text-transform.unset") [:div {:class (stl/css :global/attr-value)} [:& copy-button {:data (copy-style-data style :text-transform)} [:div {:class (stl/css :button-children)} diff --git a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs index c90ab718a..fe76cb6f3 100644 --- a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs @@ -94,18 +94,18 @@ [:* [:span {:class (stl/css :shape-icon)} [:& sir/element-icon {:shape first-shape :main-instance? main-instance?}]] - ;; Execution time translation strings: - ;; inspect.tabs.code.selected.circle - ;; inspect.tabs.code.selected.component - ;; inspect.tabs.code.selected.curve - ;; inspect.tabs.code.selected.frame - ;; inspect.tabs.code.selected.group - ;; inspect.tabs.code.selected.image - ;; inspect.tabs.code.selected.mask - ;; inspect.tabs.code.selected.path - ;; inspect.tabs.code.selected.rect - ;; inspect.tabs.code.selected.svg-raw - ;; inspect.tabs.code.selected.text + ;; Execution time translation strings: + ;; (tr "inspect.tabs.code.selected.circle") + ;; (tr "inspect.tabs.code.selected.component") + ;; (tr "inspect.tabs.code.selected.curve") + ;; (tr "inspect.tabs.code.selected.frame") + ;; (tr "inspect.tabs.code.selected.group") + ;; (tr "inspect.tabs.code.selected.image") + ;; (tr "inspect.tabs.code.selected.mask") + ;; (tr "inspect.tabs.code.selected.path") + ;; (tr "inspect.tabs.code.selected.rect") + ;; (tr "inspect.tabs.code.selected.svg-raw") + ;; (tr "inspect.tabs.code.selected.text") [:span {:class (stl/css :layer-title)} (:name first-shape)]])] [:div {:class (stl/css :inspect-content)} [:& tab-container {:on-change-tab handle-change-tab diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index c25d5cdfe..0d631ac45 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -474,7 +474,7 @@ [:& menu-separator] (for [entry components-menu-entries :when (not (nil? entry))] [:& menu-entry {:key (uuid/next) - :title (tr (:msg entry)) + :title (:title entry) :shortcut (when (contains? entry :shortcut) (sc/get-tooltip (:shortcut entry))) :on-click (:action entry)}])])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs index 2e247e0f1..25a6252aa 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs @@ -421,27 +421,27 @@ (ts/schedule 1000 do-show-component))) menu-entries [(when (and (not multi) main-instance?) - {:msg "workspace.shape.menu.show-in-assets" + {:title (tr "workspace.shape.menu.show-in-assets") :action do-show-in-assets}) (when (and (not multi) main-instance? local-component? lacks-annotation? components-v2) - {:msg "workspace.shape.menu.create-annotation" + {:title (tr "workspace.shape.menu.create-annotation") :action do-create-annotation}) (when can-detach? - {:msg (if (> (count copies) 1) - "workspace.shape.menu.detach-instances-in-bulk" - "workspace.shape.menu.detach-instance") + {:title (if (> (count copies) 1) + (tr "workspace.shape.menu.detach-instances-in-bulk") + (tr "workspace.shape.menu.detach-instance")) :action do-detach-component :shortcut :detach-component}) (when can-reset-overrides? - {:msg "workspace.shape.menu.reset-overrides" + {:title (tr "workspace.shape.menu.reset-overrides") :action do-reset-component}) (when (and (seq restorable-copies) components-v2) - {:msg "workspace.shape.menu.restore-main" + {:title (tr "workspace.shape.menu.restore-main") :action do-restore-component}) (when can-show-component? - {:msg "workspace.shape.menu.show-main" + {:title (tr "workspace.shape.menu.show-main") :action do-show-component}) (when can-update-main? - {:msg "workspace.shape.menu.update-main" + {:title (tr "workspace.shape.menu.update-main") :action do-update-component})]] (filter (complement nil?) menu-entries))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/history.cljs b/frontend/src/app/main/ui/workspace/sidebar/history.cljs index cb62682f3..7ea8e22fd 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/history.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/history.cljs @@ -16,7 +16,7 @@ [app.main.store :as st] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.i18n :refer [t] :as i18n] + [app.util.i18n :refer [tr] :as i18n] [cuerdas.core :as str] [okulary.core :as l] [rumext.v2 :as mf])) @@ -104,49 +104,49 @@ (defn entry-type->message "Formats the message that will be displayed to the user" - [locale type multiple?] + [type multiple?] (let [arity (if multiple? "multiple" "single") attribute (name (or type :multiple))] ;; Execution time translation strings: - ;; workspace.undo.entry.multiple.circle - ;; workspace.undo.entry.multiple.color - ;; workspace.undo.entry.multiple.component - ;; workspace.undo.entry.multiple.curve - ;; workspace.undo.entry.multiple.frame - ;; workspace.undo.entry.multiple.group - ;; workspace.undo.entry.multiple.media - ;; workspace.undo.entry.multiple.multiple - ;; workspace.undo.entry.multiple.page - ;; workspace.undo.entry.multiple.path - ;; workspace.undo.entry.multiple.rect - ;; workspace.undo.entry.multiple.shape - ;; workspace.undo.entry.multiple.text - ;; workspace.undo.entry.multiple.typography - ;; workspace.undo.entry.single.circle - ;; workspace.undo.entry.single.color - ;; workspace.undo.entry.single.component - ;; workspace.undo.entry.single.curve - ;; workspace.undo.entry.single.frame - ;; workspace.undo.entry.single.group - ;; workspace.undo.entry.single.image - ;; workspace.undo.entry.single.media - ;; workspace.undo.entry.single.multiple - ;; workspace.undo.entry.single.page - ;; workspace.undo.entry.single.path - ;; workspace.undo.entry.single.rect - ;; workspace.undo.entry.single.shape - ;; workspace.undo.entry.single.text - ;; workspace.undo.entry.single.typography - (t locale (str/format "workspace.undo.entry.%s.%s" arity attribute)))) + ;; (tr "workspace.undo.entry.multiple.circle") + ;; (tr "workspace.undo.entry.multiple.color") + ;; (tr "workspace.undo.entry.multiple.component") + ;; (tr "workspace.undo.entry.multiple.curve") + ;; (tr "workspace.undo.entry.multiple.frame") + ;; (tr "workspace.undo.entry.multiple.group") + ;; (tr "workspace.undo.entry.multiple.media") + ;; (tr "workspace.undo.entry.multiple.multiple") + ;; (tr "workspace.undo.entry.multiple.page") + ;; (tr "workspace.undo.entry.multiple.path") + ;; (tr "workspace.undo.entry.multiple.rect") + ;; (tr "workspace.undo.entry.multiple.shape") + ;; (tr "workspace.undo.entry.multiple.text") + ;; (tr "workspace.undo.entry.multiple.typography") + ;; (tr "workspace.undo.entry.single.circle") + ;; (tr "workspace.undo.entry.single.color") + ;; (tr "workspace.undo.entry.single.component") + ;; (tr "workspace.undo.entry.single.curve") + ;; (tr "workspace.undo.entry.single.frame") + ;; (tr "workspace.undo.entry.single.group") + ;; (tr "workspace.undo.entry.single.image") + ;; (tr "workspace.undo.entry.single.media") + ;; (tr "workspace.undo.entry.single.multiple") + ;; (tr "workspace.undo.entry.single.page") + ;; (tr "workspace.undo.entry.single.path") + ;; (tr "workspace.undo.entry.single.rect") + ;; (tr "workspace.undo.entry.single.shape") + ;; (tr "workspace.undo.entry.single.text") + ;; (tr "workspace.undo.entry.single.typography") + (tr (str/format "workspace.undo.entry.%s.%s" arity attribute)))) -(defn entry->message [locale entry] - (let [value (entry-type->message locale (:type entry) (= :multiple (:id entry)))] +(defn entry->message [entry] + (let [value (entry-type->message (:type entry) (= :multiple (:id entry)))] (case (:operation entry) - :new (t locale "workspace.undo.entry.new" value) - :modify (t locale "workspace.undo.entry.modify" value) - :delete (t locale "workspace.undo.entry.delete" value) - :move (t locale "workspace.undo.entry.move" value) - (t locale "workspace.undo.entry.unknown" value)))) + :new (tr "workspace.undo.entry.new" value) + :modify (tr "workspace.undo.entry.modify" value) + :delete (tr "workspace.undo.entry.delete" value) + :move (tr "workspace.undo.entry.move" value) + (tr "workspace.undo.entry.unknown" value)))) (defn entry->icon [{:keys [type]}] (case type @@ -284,8 +284,9 @@ nil)])) -(mf/defc history-entry [{:keys [locale entry idx-entry disabled? current?]}] +(mf/defc history-entry {::mf/props :obj} + [{:keys [entry idx-entry disabled? current?]}] (let [hover? (mf/use-state false) show-detail? (mf/use-state false) toggle-show-detail @@ -309,7 +310,7 @@ [:div {:class (stl/css :history-entry-summary)} [:div {:class (stl/css :history-entry-summary-icon)} (entry->icon entry)] - [:div {:class (stl/css :history-entry-summary-text)} (entry->message locale entry)] + [:div {:class (stl/css :history-entry-summary-text)} (entry->message entry)] (when (:detail entry) [:div {:class (stl/css-case :history-entry-summary-button true :button-opened @show-detail?) @@ -320,9 +321,9 @@ (when @show-detail? [:& history-entry-details {:entry entry}])])) -(mf/defc history-toolbox [] - (let [locale (mf/deref i18n/locale) - objects (mf/deref refs/workspace-page-objects) +(mf/defc history-toolbox + [] + (let [objects (mf/deref refs/workspace-page-objects) {:keys [items index]} (mf/deref workspace-undo) entries (parse-entries items objects) toggle-history @@ -331,18 +332,17 @@ (vary-meta assoc ::ev/origin "history-toolbox"))))] [:div {:class (stl/css :history-toolbox)} [:div {:class (stl/css :history-toolbox-title)} - [:span (t locale "workspace.undo.title")] + [:span (tr "workspace.undo.title")] [:div {:class (stl/css :close-button) :on-click toggle-history} i/close]] (if (empty? entries) [:div {:class (stl/css :history-entry-empty)} [:div {:class (stl/css :history-entry-empty-icon)} i/history] - [:div {:class (stl/css :history-entry-empty-msg)} (t locale "workspace.undo.empty")]] + [:div {:class (stl/css :history-entry-empty-msg)} (tr "workspace.undo.empty")]] [:ul {:class (stl/css :history-entries)} (for [[idx-entry entry] (->> entries (map-indexed vector) reverse)] #_[i (range 0 10)] [:& history-entry {:key (str "entry-" idx-entry) - :locale locale :entry entry :idx-entry idx-entry :current? (= idx-entry index) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index aa40b01c0..9072c9962 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -512,12 +512,12 @@ [:& dropdown {:show show :on-close on-close} [:ul {:class (stl/css-case :custom-select-dropdown true :not-main (not main-instance))} - (for [{:keys [msg] :as entry} menu-entries] - (when (some? msg) - [:li {:key msg + (for [{:keys [title action]} menu-entries] + (when (some? title) + [:li {:key title :class (stl/css :dropdown-element) - :on-click (partial do-action (:action entry))} - [:span {:class (stl/css :dropdown-label)} (tr msg)]]))]])) + :on-click (partial do-action action)} + [:span {:class (stl/css :dropdown-label)} title]]))]])) (mf/defc component-menu {::mf/props :obj} diff --git a/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs b/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs index 20d41a9f7..d2877b8a4 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs @@ -52,144 +52,166 @@ (defn translation-keyname [type keyname] ;; Execution time translation strings: - ;; shortcut-subsection.alignment - ;; shortcut-subsection.edit - ;; shortcut-subsection.general-dashboard - ;; shortcut-subsection.general-viewer - ;; shortcut-subsection.main-menu - ;; shortcut-subsection.modify-layers - ;; shortcut-subsection.navigation-dashboard - ;; shortcut-subsection.navigation-viewer - ;; shortcut-subsection.navigation-workspace - ;; shortcut-subsection.panels - ;; shortcut-subsection.path-editor - ;; shortcut-subsection.shape - ;; shortcut-subsection.tools - ;; shortcut-subsection.zoom-viewer - ;; shortcut-subsection.zoom-workspace - ;; shortcuts.add-comment - ;; shortcuts.add-node - ;; shortcuts.align-bottom - ;; shortcuts.align-hcenter - ;; shortcuts.align-left - ;; shortcuts.align-right - ;; shortcuts.align-top - ;; shortcuts.align-vcenter - ;; shortcuts.artboard-selection - ;; shortcuts.bool-difference - ;; shortcuts.bool-exclude - ;; shortcuts.bool-intersection - ;; shortcuts.bool-union - ;; shortcuts.bring-back - ;; shortcuts.bring-backward - ;; shortcuts.bring-forward - ;; shortcuts.bring-front - ;; shortcuts.clear-undo - ;; shortcuts.copy - ;; shortcuts.create-component - ;; shortcuts.create-new-project - ;; shortcuts.cut - ;; shortcuts.decrease-zoom - ;; shortcuts.delete - ;; shortcuts.delete-node - ;; shortcuts.detach-component - ;; shortcuts.draw-curve - ;; shortcuts.draw-ellipse - ;; shortcuts.draw-frame - ;; shortcuts.draw-nodes - ;; shortcuts.draw-path - ;; shortcuts.draw-rect - ;; shortcuts.draw-text - ;; shortcuts.duplicate - ;; shortcuts.escape - ;; shortcuts.export-shapes - ;; shortcuts.fit-all - ;; shortcuts.flip-horizontal - ;; shortcuts.flip-vertical - ;; shortcuts.go-to-drafts - ;; shortcuts.go-to-libs - ;; shortcuts.go-to-search - ;; shortcuts.group - ;; shortcuts.h-distribute - ;; shortcuts.hide-ui - ;; shortcuts.increase-zoom - ;; shortcuts.insert-image - ;; shortcuts.join-nodes - ;; shortcuts.make-corner - ;; shortcuts.make-curve - ;; shortcuts.mask - ;; shortcuts.merge-nodes - ;; shortcuts.move - ;; shortcuts.move-fast-down - ;; shortcuts.move-fast-left - ;; shortcuts.move-fast-right - ;; shortcuts.move-fast-up - ;; shortcuts.move-nodes - ;; shortcuts.move-unit-down - ;; shortcuts.move-unit-left - ;; shortcuts.move-unit-right - ;; shortcuts.move-unit-up - ;; shortcuts.next-frame - ;; shortcuts.opacity-0 - ;; shortcuts.opacity-1 - ;; shortcuts.opacity-2 - ;; shortcuts.opacity-3 - ;; shortcuts.opacity-4 - ;; shortcuts.opacity-5 - ;; shortcuts.opacity-6 - ;; shortcuts.opacity-7 - ;; shortcuts.opacity-8 - ;; shortcuts.opacity-9 - ;; shortcuts.open-color-picker - ;; shortcuts.open-comments - ;; shortcuts.open-dashboard - ;; shortcuts.select-prev - ;; shortcuts.select-next - ;; shortcuts.open-inspect - ;; shortcuts.open-interactions - ;; shortcuts.open-viewer - ;; shortcuts.open-workspace - ;; shortcuts.paste - ;; shortcuts.prev-frame - ;; shortcuts.redo - ;; shortcuts.reset-zoom - ;; shortcuts.select-all - ;; shortcuts.separate-nodes - ;; shortcuts.show-pixel-grid - ;; shortcuts.show-shortcuts - ;; shortcuts.snap-nodes - ;; shortcuts.snap-pixel-grid - ;; shortcuts.start-editing - ;; shortcuts.start-measure - ;; shortcuts.stop-measure - ;; shortcuts.text-align-center - ;; shortcuts.text-align-left - ;; shortcuts.text-align-justify - ;; shortcuts.text-align-right - ;; shortcuts.thumbnail-set - ;; shortcuts.toggle-alignment - ;; shortcuts.toggle-assets - ;; shortcuts.toggle-colorpalette - ;; shortcuts.toggle-focus-mode - ;; shortcuts.toggle-guides - ;; shortcuts.toggle-history - ;; shortcuts.toggle-layers - ;; shortcuts.toggle-lock - ;; shortcuts.toggle-lock-size - ;; shortcuts.toggle-rules - ;; shortcuts.scale - ;; shortcuts.toggle-snap-guides - ;; shortcuts.toggle-snap-ruler-guide - ;; shortcuts.toggle-textpalette - ;; shortcuts.toggle-visibility - ;; shortcuts.toggle-zoom-style - ;; shortcuts.toggle-fullscreen - ;; shortcuts.undo - ;; shortcuts.ungroup - ;; shortcuts.unmask - ;; shortcuts.v-distribute - ;; shortcuts.zoom-selected - ;; shortcuts.toggle-layout-grid + (comment + (tr "shortcut-subsection.alignment") + (tr "shortcut-subsection.edit") + (tr "shortcut-subsection.general-dashboard") + (tr "shortcut-subsection.general-viewer") + (tr "shortcut-subsection.main-menu") + (tr "shortcut-subsection.modify-layers") + (tr "shortcut-subsection.navigation-dashboard") + (tr "shortcut-subsection.navigation-viewer") + (tr "shortcut-subsection.navigation-workspace") + (tr "shortcut-subsection.panels") + (tr "shortcut-subsection.path-editor") + (tr "shortcut-subsection.shape") + (tr "shortcut-subsection.text-editor") + (tr "shortcut-subsection.tools") + (tr "shortcut-subsection.zoom-viewer") + (tr "shortcut-subsection.zoom-workspace") + (tr "shortcuts.add-comment") + (tr "shortcuts.add-node") + (tr "shortcuts.align-bottom") + (tr "shortcuts.align-center") + (tr "shortcuts.align-hcenter") + (tr "shortcuts.align-justify") + (tr "shortcuts.align-left") + (tr "shortcuts.align-right") + (tr "shortcuts.align-top") + (tr "shortcuts.align-vcenter") + (tr "shortcuts.artboard-selection") + (tr "shortcuts.bold") + (tr "shortcuts.bool-difference") + (tr "shortcuts.bool-exclude") + (tr "shortcuts.bool-intersection") + (tr "shortcuts.bool-union") + (tr "shortcuts.bring-back") + (tr "shortcuts.bring-backward") + (tr "shortcuts.bring-forward") + (tr "shortcuts.bring-front") + (tr "shortcuts.clear-undo") + (tr "shortcuts.copy") + (tr "shortcuts.create-component") + (tr "shortcuts.create-new-project") + (tr "shortcuts.cut") + (tr "shortcuts.decrease-zoom") + (tr "shortcuts.delete") + (tr "shortcuts.delete-node") + (tr "shortcuts.detach-component") + (tr "shortcuts.draw-curve") + (tr "shortcuts.draw-ellipse") + (tr "shortcuts.draw-frame") + (tr "shortcuts.draw-nodes") + (tr "shortcuts.draw-path") + (tr "shortcuts.draw-rect") + (tr "shortcuts.draw-text") + (tr "shortcuts.duplicate") + (tr "shortcuts.escape") + (tr "shortcuts.export-shapes") + (tr "shortcuts.fit-all") + (tr "shortcuts.flip-horizontal") + (tr "shortcuts.flip-vertical") + (tr "shortcuts.font-size-dec") + (tr "shortcuts.font-size-inc") + (tr "shortcuts.go-to-drafts") + (tr "shortcuts.go-to-libs") + (tr "shortcuts.go-to-search") + (tr "shortcuts.group") + (tr "shortcuts.h-distribute") + (tr "shortcuts.hide-ui") + (tr "shortcuts.increase-zoom") + (tr "shortcuts.insert-image") + (tr "shortcuts.italic") + (tr "shortcuts.join-nodes") + (tr "shortcuts.letter-spacing-dec") + (tr "shortcuts.letter-spacing-inc") + (tr "shortcuts.line-height-dec") + (tr "shortcuts.line-height-inc") + (tr "shortcuts.line-through") + (tr "shortcuts.make-corner") + (tr "shortcuts.make-curve") + (tr "shortcuts.mask") + (tr "shortcuts.merge-nodes") + (tr "shortcuts.move") + (tr "shortcuts.move-fast-down") + (tr "shortcuts.move-fast-left") + (tr "shortcuts.move-fast-right") + (tr "shortcuts.move-fast-up") + (tr "shortcuts.move-nodes") + (tr "shortcuts.move-unit-down") + (tr "shortcuts.move-unit-left") + (tr "shortcuts.move-unit-right") + (tr "shortcuts.move-unit-up") + (tr "shortcuts.next-frame") + (tr "shortcuts.opacity-0") + (tr "shortcuts.opacity-1") + (tr "shortcuts.opacity-2") + (tr "shortcuts.opacity-3") + (tr "shortcuts.opacity-4") + (tr "shortcuts.opacity-5") + (tr "shortcuts.opacity-6") + (tr "shortcuts.opacity-7") + (tr "shortcuts.opacity-8") + (tr "shortcuts.opacity-9") + (tr "shortcuts.open-color-picker") + (tr "shortcuts.open-comments") + (tr "shortcuts.open-dashboard") + (tr "shortcuts.open-inspect") + (tr "shortcuts.open-interactions") + (tr "shortcuts.open-viewer") + (tr "shortcuts.open-workspace") + (tr "shortcuts.paste") + (tr "shortcuts.prev-frame") + (tr "shortcuts.redo") + (tr "shortcuts.reset-zoom") + (tr "shortcuts.scale") + (tr "shortcuts.search-placeholder") + (tr "shortcuts.select-all") + (tr "shortcuts.select-next") + (tr "shortcuts.select-parent-layer") + (tr "shortcuts.select-prev") + (tr "shortcuts.separate-nodes") + (tr "shortcuts.show-pixel-grid") + (tr "shortcuts.show-shortcuts") + (tr "shortcuts.snap-nodes") + (tr "shortcuts.snap-pixel-grid") + (tr "shortcuts.start-editing") + (tr "shortcuts.start-measure") + (tr "shortcuts.stop-measure") + (tr "shortcuts.text-align-center") + (tr "shortcuts.text-align-justify") + (tr "shortcuts.text-align-left") + (tr "shortcuts.text-align-right") + (tr "shortcuts.thumbnail-set") + (tr "shortcuts.toggle-alignment") + (tr "shortcuts.toggle-assets") + (tr "shortcuts.toggle-colorpalette") + (tr "shortcuts.toggle-focus-mode") + (tr "shortcuts.toggle-fullscreen") + (tr "shortcuts.toggle-guides") + (tr "shortcuts.toggle-history") + (tr "shortcuts.toggle-layers") + (tr "shortcuts.toggle-layout-flex") + (tr "shortcuts.toggle-layout-grid") + (tr "shortcuts.toggle-lock") + (tr "shortcuts.toggle-lock-size") + (tr "shortcuts.toggle-rulers") + (tr "shortcuts.toggle-rules") + (tr "shortcuts.toggle-snap-guides") + (tr "shortcuts.toggle-snap-ruler-guide") + (tr "shortcuts.toggle-textpalette") + (tr "shortcuts.toggle-theme") + (tr "shortcuts.toggle-visibility") + (tr "shortcuts.toggle-zoom-style") + (tr "shortcuts.underline") + (tr "shortcuts.undo") + (tr "shortcuts.ungroup") + (tr "shortcuts.unmask") + (tr "shortcuts.v-distribute") + (tr "shortcuts.zoom-lense-decrease") + (tr "shortcuts.zoom-lense-increase") + (tr "shortcuts.zoom-selected")) + (let [translat-pre (case type :sc "shortcuts." :sec "shortcut-section." diff --git a/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs b/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs index 1cbd37d1c..7c2963ae1 100644 --- a/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs @@ -30,8 +30,9 @@ [:div {:class (stl/css :viewport-actions)} [:div {:class (stl/css :viewport-actions-container)} [:div {:class (stl/css :viewport-actions-title)} - [:& i18n/tr-html {:tag-name "span" - :label "workspace.top-bar.view-only"}]] + [:> i18n/tr-html* + {:tag-name "span" + :content (tr "workspace.top-bar.view-only")}]] [:button {:class (stl/css :done-btn) :on-click handle-close-view-mode} (tr "workspace.top-bar.read-only.done")]]])) diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 39575d601..c87ac6f20 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -12,7 +12,6 @@ [app.config :as cfg] [app.util.dom :as dom] [app.util.globals :as globals] - [app.util.object :as obj] [app.util.storage :refer [storage]] [cuerdas.core :as str] [goog.object :as gobj] @@ -173,15 +172,11 @@ ([code] (t @locale code)) ([code & args] (apply t @locale code args))) -(mf/defc tr-html - {::mf/wrap-props false} - [props] - (let [label (obj/get props "label") - class (obj/get props "class") - tag-name (obj/get props "tag-name" "p") - params (obj/get props "params" []) - html (apply tr (d/concat-vec [label] params))] - [:> tag-name {:dangerouslySetInnerHTML #js {:__html html} +(mf/defc tr-html* + {::mf/props :obj} + [{:keys [content class tag-name]}] + (let [tag-name (d/nilv tag-name "p")] + [:> tag-name {:dangerouslySetInnerHTML #js {:__html content} :className class}])) ;; DEPRECATED diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 36f2b0f36..1eaae603b 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -7923,6 +7923,7 @@ __metadata: eventsource-parser: "npm:^1.1.2" express: "npm:^4.19.2" fancy-log: "npm:^2.0.0" + getopts: "npm:^2.3.0" gettext-parser: "npm:^8.0.0" gulp: "npm:4.0.2" gulp-concat: "npm:^2.6.1" @@ -8236,6 +8237,13 @@ __metadata: languageName: node linkType: hard +"getopts@npm:^2.3.0": + version: 2.3.0 + resolution: "getopts@npm:2.3.0" + checksum: 10c0/edbcbd7020e9d87dc41e4ad9add5eb3873ae61339a62431bd92a461be2c0eaa9ec33b6fd0d67fa1b44feedffcf1cf28d6f9dbdb7d604cb1617eaba146a33cbca + languageName: node + linkType: hard + "gettext-parser@npm:^8.0.0": version: 8.0.0 resolution: "gettext-parser@npm:8.0.0"