Skip to content

Commit

Permalink
feat: switch formatter to format-message
Browse files Browse the repository at this point in the history
  • Loading branch information
SychO9 committed Oct 23, 2024
1 parent 8c76ca7 commit 2595901
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 96 deletions.
43 changes: 20 additions & 23 deletions framework/core/js/src/common/Translator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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] ?? {},
},
});
}
Expand All @@ -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;
},
});
}

/**
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -160,15 +159,14 @@ export default class Translator {
if (result) return result;
}

return time.format(this.translations[id]);
return time.format(this.preprocessTranslation(this.translations[id]));
}

/**
* Backwards compatibility for translations such as `<a href='{href}'>`, the old
* 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 {
Expand All @@ -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;
}
}
1 change: 0 additions & 1 deletion framework/core/js/src/common/utils/abbreviateNumber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 1 addition & 2 deletions framework/core/js/src/forum/components/AccessTokensList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -187,7 +186,7 @@ export default class AccessTokensList<CustomAttrs extends IAccessTokensListAttrs
m.redraw();
}

generateTokenTitle(token: AccessToken): NestedStringArray {
generateTokenTitle(token: AccessToken): any[] | string {
const name = token.title() || app.translator.trans('core.forum.security.token_title_placeholder');
const value = this.tokenValueDisplay(token);

Expand Down
76 changes: 36 additions & 40 deletions framework/core/js/tests/unit/common/utils/Translator.test.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,59 @@
import Translator from '../../../../src/common/Translator';
import extractText from "../../../../src/common/utils/extractText";
import extractText from '../../../../src/common/utils/extractText';

/*
* These tests should be in sync with PHP tests in `tests/unit/Locale/TranslatorTest.php`, to make sure that JS
* translator works in the same way as JS translator.
*/

test('placeholders encoding', () => {
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');
});
3 changes: 2 additions & 1 deletion js-packages/jest-config/src/boostrap/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function bootstrap(Application, app, payload = {}) {

app.load({
apiDocument: null,
locale: 'en',
locale: null,
locales: {},
resources: [
{
Expand Down Expand Up @@ -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();
}
63 changes: 34 additions & 29 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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==
Expand Down Expand Up @@ -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/[email protected]", "@webassemblyjs/ast@^1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 2595901

Please sign in to comment.