From 25959018ca05106ffce7ca0180379bfd5e499667 Mon Sep 17 00:00:00 2001 From: Sami Mazouz Date: Wed, 23 Oct 2024 17:55:25 +0100 Subject: [PATCH] feat: switch formatter to `format-message` --- framework/core/js/src/common/Translator.tsx | 43 +++++------ .../js/src/common/utils/abbreviateNumber.ts | 1 - .../src/forum/components/AccessTokensList.tsx | 3 +- .../unit/common/utils/Translator.test.ts | 76 +++++++++---------- .../jest-config/src/boostrap/common.js | 3 +- yarn.lock | 63 ++++++++------- 6 files changed, 93 insertions(+), 96 deletions(-) diff --git a/framework/core/js/src/common/Translator.tsx b/framework/core/js/src/common/Translator.tsx index a77e83c4a9..442993a2bb 100644 --- a/framework/core/js/src/common/Translator.tsx +++ b/framework/core/js/src/common/Translator.tsx @@ -3,7 +3,7 @@ import type { Dayjs } from 'dayjs'; import User from './models/User'; import extract from './utils/extract'; import formatMessage, { Translation } from 'format-message'; -import { fireDeprecationWarning } from './helpers/fireDebugWarning'; +import fireDebugWarning, { fireDeprecationWarning } from './helpers/fireDebugWarning'; import extractText from './utils/extractText'; import ItemList from './utils/ItemList'; @@ -36,7 +36,7 @@ export default class Translator { this.formatter.setup({ locale, translations: { - [locale]: this.formatter.setup().translations.tmp ?? {}, + [locale]: this.formatter.setup().translations[locale] ?? {}, }, }); } @@ -49,20 +49,18 @@ export default class Translator { } addTranslations(translations: Translations) { + const locale = this.getLocale(); + this.formatter.setup({ translations: { - // The locale has not been set yet by this time. - // @TODO: in v2.0 we should add translations with the locale info. - tmp: Object.assign(this.translations, translations), + [locale]: Object.assign(this.translations, translations), }, - }); - } + missingReplacement: (key: string) => { + fireDebugWarning(`Missing translation for key: ${key}`); - /** - * An extensible entrypoint for extenders to register type handlers for translations. - */ - protected formatterTypeHandlers() { - return this.formatter; + return key; + }, + }); } /** @@ -123,15 +121,16 @@ export default class Translator { return parameters; } - trans(id: string, parameters: TranslatorParameters): NestedStringArray; - trans(id: string, parameters: TranslatorParameters, extract: false): NestedStringArray; + trans(id: string, parameters: TranslatorParameters): any[]; + trans(id: string, parameters: TranslatorParameters, extract: false): any[]; trans(id: string, parameters: TranslatorParameters, extract: true): string; - trans(id: string): NestedStringArray | string; + trans(id: string): any[] | string; trans(id: string, parameters: TranslatorParameters = {}, extract = false) { const translation = this.preprocessTranslation(this.translations[id]); if (translation) { parameters = this.preprocessParameters(parameters, translation); + this.translations[id] = translation; const locale = this.formatter.rich({ id, default: id }, parameters); @@ -160,7 +159,7 @@ export default class Translator { if (result) return result; } - return time.format(this.translations[id]); + return time.format(this.preprocessTranslation(this.translations[id])); } /** @@ -168,7 +167,6 @@ export default class Translator { * formatter supported that, but the new one doesn't, so attributes are auto dropped * to avoid errors. * - * @deprecated Will be removed in v2.0 * @private */ private preprocessTranslation(translation: string | Translation | undefined): string | undefined { @@ -180,14 +178,13 @@ export default class Translator { // remove the attributes for backwards compatibility. Will be removed in v2.0. // And if it did have attributes, then we'll fire a warning if (translation.match(/<\w+ [^>]+>/g)) { - fireDeprecationWarning( - `Any HTML tags used within translations must be simple tags, without attributes.\nCaught in translation: \n\n"""\n${translation}\n"""`, - '', - 'v2.0', - 'flarum/framework' + fireDebugWarning( + `Any HTML tags used within translations must be simple tags, without attributes.\nCaught in translation: \n\n"""\n${translation}\n"""` ); + + return translation.replace(/<(\w+)([^>]*)>/g, '<$1>'); } - return translation.replace(/<(\w+)([^>]*)>/g, '<$1>'); + return translation; } } diff --git a/framework/core/js/src/common/utils/abbreviateNumber.ts b/framework/core/js/src/common/utils/abbreviateNumber.ts index 43586876a6..bc4d340743 100644 --- a/framework/core/js/src/common/utils/abbreviateNumber.ts +++ b/framework/core/js/src/common/utils/abbreviateNumber.ts @@ -9,7 +9,6 @@ import extractText from './extractText'; * // "1.2K" */ export default function abbreviateNumber(number: number): string { - // TODO: translation if (number >= 1000000) { return Math.floor(number / 1000000) + extractText(app.translator.trans('core.lib.number_suffix.mega_text')); } else if (number >= 1000) { diff --git a/framework/core/js/src/forum/components/AccessTokensList.tsx b/framework/core/js/src/forum/components/AccessTokensList.tsx index 5d58250817..e93e557fd3 100644 --- a/framework/core/js/src/forum/components/AccessTokensList.tsx +++ b/framework/core/js/src/forum/components/AccessTokensList.tsx @@ -9,7 +9,6 @@ import classList from '../../common/utils/classList'; import Tooltip from '../../common/components/Tooltip'; import type Mithril from 'mithril'; import type AccessToken from '../../common/models/AccessToken'; -import { NestedStringArray } from '@askvortsov/rich-icu-message-formatter'; import Icon from '../../common/components/Icon'; export interface IAccessTokensListAttrs extends ComponentAttrs { @@ -187,7 +186,7 @@ export default class AccessTokensList { - const translator = new Translator(); - translator.setLocale('en'); - translator.addTranslations({ - 'test1': 'test1 {placeholder} test1', - 'test2': 'test2 {placeholder} test2', - }); - - expect(extractText(translator.trans('test1', {'placeholder': "'"}))).toBe("test1 ' test1"); - expect(extractText(translator.trans('test1', {'placeholder': translator.trans('test2', {'placeholder': "'"})}))).toBe("test1 test2 ' test2 test1"); + const translator = new Translator(); + translator.addTranslations({ + test1: 'test1 {placeholder} test1', + test2: 'test2 {placeholder} test2', + }); + + expect(extractText(translator.trans('test1', { placeholder: "'" }))).toBe("test1 ' test1"); + expect(extractText(translator.trans('test1', { placeholder: translator.trans('test2', { placeholder: "'" }) }))).toBe("test1 test2 ' test2 test1"); }); test('missing placeholders', () => { - const translator = new Translator(); - translator.setLocale('en'); - translator.addTranslations({ - 'test1': 'test1 {placeholder} test1', - }); + const translator = new Translator(); + translator.addTranslations({ + test1: 'test1 {placeholder} test1', + }); - expect(extractText(translator.trans('test1', {}))).toBe('test1 {placeholder} test1'); + expect(extractText(translator.trans('test1', {}))).toBe('test1 {placeholder} test1'); }); test('escaped placeholders', () => { - const translator = new Translator(); - translator.setLocale('en'); - translator.addTranslations({ - 'test3': "test1 {placeholder} '{placeholder}' test1", - }); + const translator = new Translator(); + translator.addTranslations({ + test3: "test1 {placeholder} '{placeholder}' test1", + }); - expect(extractText(translator.trans('test3', {placeholder: "'"}))).toBe("test1 ' {placeholder} test1"); + expect(extractText(translator.trans('test3', { placeholder: "'" }))).toBe("test1 ' {placeholder} test1"); }); test('plural rules', () => { - const translator = new Translator(); - translator.setLocale('en'); - translator.addTranslations({ - 'test4': '{pageNumber, plural, =1 {{forumName}} other {Page # - {forumName}}}', - }); - - expect(extractText(translator.trans('test4', {forumName: 'A & B', pageNumber: 1}))).toBe('A & B'); - expect(extractText(translator.trans('test4', {forumName: 'A & B', pageNumber: 2}))).toBe('Page 2 - A & B'); + const translator = new Translator(); + translator.addTranslations({ + test4: '{pageNumber, plural, =1 {{forumName}} other {Page # - {forumName}}}', + }); + + expect(extractText(translator.trans('test4', { forumName: 'A & B', pageNumber: 1 }))).toBe('A & B'); + expect(extractText(translator.trans('test4', { forumName: 'A & B', pageNumber: 2 }))).toBe('Page 2 - A & B'); }); test('plural rules 2', () => { - const translator = new Translator(); - translator.setLocale('pl'); - translator.addTranslations({ - 'test5': '{count, plural, one {# post} few {# posty} many {# postów} other {# posta}}', - }); - - expect(extractText(translator.trans('test5', {count: 1}))).toBe('1 post'); - expect(extractText(translator.trans('test5', {count: 2}))).toBe('2 posty'); - expect(extractText(translator.trans('test5', {count: 5}))).toBe('5 postów'); - expect(extractText(translator.trans('test5', {count: 1.5}))).toBe('1,5 posta'); + const translator = new Translator(); + translator.setLocale('pl'); + translator.addTranslations({ + test5: '{count, plural, one {# post} few {# posty} many {# postów} other {# posta}}', + }); + + expect(extractText(translator.trans('test5', { count: 1 }))).toBe('1 post'); + expect(extractText(translator.trans('test5', { count: 2 }))).toBe('2 posty'); + expect(extractText(translator.trans('test5', { count: 5 }))).toBe('5 postów'); + expect(extractText(translator.trans('test5', { count: 1.5 }))).toBe('1,5 posta'); }); diff --git a/js-packages/jest-config/src/boostrap/common.js b/js-packages/jest-config/src/boostrap/common.js index ea51a2244c..42c0abfc9e 100644 --- a/js-packages/jest-config/src/boostrap/common.js +++ b/js-packages/jest-config/src/boostrap/common.js @@ -9,7 +9,7 @@ export default function bootstrap(Application, app, payload = {}) { app.load({ apiDocument: null, - locale: 'en', + locale: null, locales: {}, resources: [ { @@ -37,5 +37,6 @@ export default function bootstrap(Application, app, payload = {}) { }); app.translator.addTranslations(flatten(jsYaml.load(fs.readFileSync('../locale/core.yml', 'utf8')))); + app.translator.setLocale('en'); app.drawer = new Drawer(); } diff --git a/yarn.lock b/yarn.lock index cfb2ed6998..d98bbd9aac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,15 +10,6 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@askvortsov/rich-icu-message-formatter@^0.2.4": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@askvortsov/rich-icu-message-formatter/-/rich-icu-message-formatter-0.2.4.tgz#5810886d6d6751e9b800640748355a87ea985556" - integrity sha512-JOdZ7iw7qF3uxC3cfY8dighM3rgrV0WufgwVeFD9VEkxB7IwA7DX2kHs24zk4CYPR6HQXUEnM6fwOy+VKUrc8w== - dependencies: - "@babel/runtime" "^7.11.2" - "@ultraq/array-utils" "^2.1.0" - "@ultraq/icu-message-formatter" "^0.12.0" - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" @@ -987,7 +978,7 @@ "@babel/plugin-transform-modules-commonjs" "^7.25.7" "@babel/plugin-transform-typescript" "^7.25.7" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.20.1", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.20.1", "@babel/runtime@^7.8.4": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w== @@ -1545,25 +1536,6 @@ dependencies: "@types/yargs-parser" "*" -"@ultraq/array-utils@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@ultraq/array-utils/-/array-utils-2.1.0.tgz#56f16a1ea3ef46c5d5f04638b47c4fca4d71a8c1" - integrity sha512-TKO1zE6foqs5HG3+QH32yKwJ0zhZrm6J3UmltscveQmxCdbgIPXhNf3A8C9HakjyZDHVRK5pYZOU0tTl28YGFg== - -"@ultraq/function-utils@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@ultraq/function-utils/-/function-utils-0.3.0.tgz#63eb7dceff18fdca212fae11a59b3ee01f556917" - integrity sha512-AwFCYorRn0GE34hfgxaCmfnReHqcwWE6QwWPQf/1Zj7k3Zi0FATSJhbtDA+6ayV8p6AnhEntntXaMWMkK17tEQ== - -"@ultraq/icu-message-formatter@^0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@ultraq/icu-message-formatter/-/icu-message-formatter-0.12.0.tgz#15a812a323395d7e5b5e3c6c2cc92df3989b26ce" - integrity sha512-ebd/ZyC1lCVPPrX3AQ9h77NDK4d1nor0Grmv43e97+omWvJB29lbuT+9yM3sq4Ri1QKwTvKG1BUhXBz0oAAR2w== - dependencies: - "@babel/runtime" "^7.11.2" - "@ultraq/array-utils" "^2.1.0" - "@ultraq/function-utils" "^0.3.0" - "@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" @@ -2723,6 +2695,34 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +format-message-formats@^6.2.4: + version "6.2.4" + resolved "https://registry.yarnpkg.com/format-message-formats/-/format-message-formats-6.2.4.tgz#68b782e70c3c15f017377848c3225731e52ac4ea" + integrity sha512-smT/fAqBLqusWfWCKRAx6QBDAAbmYznWsIyTyk66COmvwt2Byiqd7SJe2ma9a5oV0kwRaOJpN/F4lr4YK/n6qQ== + +format-message-interpret@^6.2.4: + version "6.2.4" + resolved "https://registry.yarnpkg.com/format-message-interpret/-/format-message-interpret-6.2.4.tgz#28f579b9cd4b57f3de2ec2a4d9623f9870e9ed03" + integrity sha512-dRvz9mXhITApyOtfuFEb/XqvCe1u6RMkQW49UJHXS8w2S8cAHCqq5LNDFK+QK6XVzcofROycLb/k1uybTAKt2w== + dependencies: + format-message-formats "^6.2.4" + lookup-closest-locale "^6.2.0" + +format-message-parse@^6.2.4: + version "6.2.4" + resolved "https://registry.yarnpkg.com/format-message-parse/-/format-message-parse-6.2.4.tgz#2c9b39a32665bd247cb1c31ba2723932d9edf3f9" + integrity sha512-k7WqXkEzgXkW4wkHdS6Cv2Ou0rIFtiDelZjgoe1saW4p7FT7zS8OeAUpAekhormqzpeecR97e4vBft1zMsfFOQ== + +format-message@^6.2.4: + version "6.2.4" + resolved "https://registry.yarnpkg.com/format-message/-/format-message-6.2.4.tgz#0bd4b6161b036e3fbcf3207dce14a62e318b4c48" + integrity sha512-/24zYeSRy2ZlEO2OIctm7jOHvMpoWf+uhqFCaqqyZKi1C229zAAy2E5vF4lSSaMH0a2kewPrOzq6xN4Yy7cQrw== + dependencies: + format-message-formats "^6.2.4" + format-message-interpret "^6.2.4" + format-message-parse "^6.2.4" + lookup-closest-locale "^6.2.0" + frappe-charts@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-1.6.2.tgz#4671a943a8606e5020180fa65c8ea1835c510baf" @@ -3748,6 +3748,11 @@ lodash@^4.17.15: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lookup-closest-locale@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/lookup-closest-locale/-/lookup-closest-locale-6.2.0.tgz#57f665e604fd26f77142d48152015402b607bcf3" + integrity sha512-/c2kL+Vnp1jnV6K6RpDTHK3dgg0Tu2VVp+elEiJpjfS1UyY7AjOYHohRug6wT0OpoX2qFgNORndE9RqesfVxWQ== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"