From 360f466d307d8ab397387f0abb8616046022132b Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Mon, 7 Oct 2024 14:33:08 +0500 Subject: [PATCH 01/11] fix logging --- src/core/prelude/i18n/helpers.ts | 33 +++++++++++++++++++++--------- src/core/prelude/i18n/interface.ts | 5 +++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/core/prelude/i18n/helpers.ts b/src/core/prelude/i18n/helpers.ts index cbe0aa125..2f3c89824 100644 --- a/src/core/prelude/i18n/helpers.ts +++ b/src/core/prelude/i18n/helpers.ts @@ -12,7 +12,7 @@ import extend from 'core/prelude/extend'; import langPacs, { Translation, PluralTranslation } from 'lang'; import { locale } from 'core/prelude/i18n/const'; -import type { I18nOpts, PluralizationCount } from 'core/prelude/i18n/interface'; +import type { I18nOpts, PluralizationCount, I18nMeta } from 'core/prelude/i18n/interface'; /** @see [[i18n]] */ extend(globalThis, 'i18n', i18nFactory); @@ -54,18 +54,19 @@ export function i18nFactory( const key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find((keysetName) => langPacs[resolvedLocale]?.[keysetName]?.[key]), - translateValue = langPacs[resolvedLocale]?.[correctKeyset ?? '']?.[key]; + translateValue = langPacs[resolvedLocale]?.[correctKeyset ?? '']?.[key], + meta: I18nMeta = {language: resolvedLocale, keysets: keysetNames.join(', ')}; if (translateValue != null && translateValue !== '') { - return resolveTemplate(translateValue, params, {pluralRules}); + return resolveTemplate(translateValue, params, {pluralRules}, meta); } logger.error( 'Translation for the given key is not found', - `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}, available locales: ${Object.keys(langPacs).join(', ')}` + `Key: ${key}, KeysetNames: ${meta.keysets}, LocaleName: ${resolvedLocale}, available locales: ${Object.keys(langPacs).join(', ')}` ); - return resolveTemplate(key, params, {pluralRules}); + return resolveTemplate(key, params, {pluralRules}, meta); }; } @@ -74,6 +75,8 @@ export function i18nFactory( * * @param value - a string for the default case, or an array of strings for the plural case * @param params - a dictionary with parameters for internationalization + * @params [opts] = I18n options for current translation + * @param [meta] - I18n meta information about current translation * * @example * ```typescript @@ -91,9 +94,9 @@ export function i18nFactory( * console.log(examplePluralize); // '5 products' * ``` */ -export function resolveTemplate(value: Translation, params?: I18nParams, opts: I18nOpts = {}): string { +export function resolveTemplate(value: Translation, params?: I18nParams, opts: I18nOpts = {}, meta?: I18nMeta): string { const - template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, opts.pluralRules) : value; + template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, opts.pluralRules, meta) : value; return template.replace(/{([^}]+)}/g, (_, key) => { if (params?.[key] == null) { @@ -111,6 +114,7 @@ export function resolveTemplate(value: Translation, params?: I18nParams, opts: I * @param pluralTranslation - list of translation variants * @param count - the value on the basis of which the form of pluralization will be selected * @param rules - Intl plural rules for selected locale + * @param [meta] - I18n meta information about current translation * * @example * ```typescript @@ -128,7 +132,8 @@ export function resolveTemplate(value: Translation, params?: I18nParams, opts: I export function pluralizeText( pluralTranslation: PluralTranslation, count: CanUndef, - rules: CanUndef + rules: CanUndef, + meta?: I18nMeta ): string { let normalizedCount; @@ -144,7 +149,11 @@ export function pluralizeText( } if (normalizedCount == null) { - logger.error('Invalid value of the `count` parameter for string pluralization', `String: ${pluralTranslation[0]}`); + logger.error( + 'Invalid value of the `count` parameter for string pluralization', + `String: ${pluralTranslation[0]}, keysets: ${meta?.keysets}, language: ${meta?.language}` + ); + normalizedCount = 1; } @@ -153,7 +162,11 @@ export function pluralizeText( translation = pluralTranslation[pluralFormName]; if (translation == null) { - logger.error(`Plural form ${pluralFormName} doesn't exist.`, `String: ${pluralTranslation[0]}`); + logger.error( + `Plural form ${pluralFormName} doesn't exist.`, + `String: ${pluralTranslation[0]}, keysets: ${meta?.keysets}, language: ${meta?.language}` + ); + return pluralTranslation.one; } diff --git a/src/core/prelude/i18n/interface.ts b/src/core/prelude/i18n/interface.ts index 9981d87ae..64de75e53 100644 --- a/src/core/prelude/i18n/interface.ts +++ b/src/core/prelude/i18n/interface.ts @@ -46,3 +46,8 @@ export type PluralizationCount = StringPluralizationForms | number; export interface I18nOpts { pluralRules?: Intl.PluralRules; } + +export interface I18nMeta { + language: string; + keysets: string; +} From eb2202e98dcb0f39e06f53ce6e21c2366fbc889d Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Mon, 7 Oct 2024 14:36:15 +0500 Subject: [PATCH 02/11] add logging info in resolveTemplate --- src/core/prelude/i18n/helpers.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/prelude/i18n/helpers.ts b/src/core/prelude/i18n/helpers.ts index 2f3c89824..2c3d8b784 100644 --- a/src/core/prelude/i18n/helpers.ts +++ b/src/core/prelude/i18n/helpers.ts @@ -100,7 +100,7 @@ export function resolveTemplate(value: Translation, params?: I18nParams, opts: I return template.replace(/{([^}]+)}/g, (_, key) => { if (params?.[key] == null) { - logger.error('Undeclared variable', `Name: "${key}", Template: "${template}"`); + logger.error('Undeclared variable', `Name: "${key}", Template: "${template}", Keysets: "${meta?.keysets}", Language: "${meta?.language}"`); return key; } @@ -151,7 +151,7 @@ export function pluralizeText( if (normalizedCount == null) { logger.error( 'Invalid value of the `count` parameter for string pluralization', - `String: ${pluralTranslation[0]}, keysets: ${meta?.keysets}, language: ${meta?.language}` + `String: ${pluralTranslation[0]}, Keysets: ${meta?.keysets}, Language: ${meta?.language}` ); normalizedCount = 1; @@ -164,7 +164,7 @@ export function pluralizeText( if (translation == null) { logger.error( `Plural form ${pluralFormName} doesn't exist.`, - `String: ${pluralTranslation[0]}, keysets: ${meta?.keysets}, language: ${meta?.language}` + `String: ${pluralTranslation[0]}, Keysets: ${meta?.keysets}, Language: ${meta?.language}` ); return pluralTranslation.one; From e118ce120390811b988128da8c89228b9932f3e2 Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Mon, 7 Oct 2024 14:40:57 +0500 Subject: [PATCH 03/11] changelog --- CHANGELOG.md | 11 ++++++++++- package.json | 2 +- src/core/prelude/CHANGELOG.md | 9 ++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a38003f84..4388f4c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,18 @@ Changelog _Note: Gaps between patch versions are faulty, broken or test releases._ +## v3.101.1 (2024-10-07) + +#### :bug: Bug Fix + +* `core/prelude/i18n/helpers` + * Fix logging bug in `pluralizeText`. + * Add logging info in i18n helpers. + ## v3.101.0 (2024-09-25) -#### :boom: breaking change +#### :boom: Breaking Change + * `core/prelude/i18n/helpers` * changed `i18n` translations format. * added `intl` support for pluralization. diff --git a/package.json b/package.json index faefde881..e05660f97 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "lib/core/index.js", "typings": "index.d.ts", "license": "MIT", - "version": "3.101.0", + "version": "3.101.1", "author": "kobezzza (https://github.com/kobezzza)", "repository": { "type": "git", diff --git a/src/core/prelude/CHANGELOG.md b/src/core/prelude/CHANGELOG.md index e9edc94ff..46516d0a8 100644 --- a/src/core/prelude/CHANGELOG.md +++ b/src/core/prelude/CHANGELOG.md @@ -9,9 +9,16 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v3.101.1 (2024-10-07) + +#### :bug: Bug Fix + +* Fix logging bug in `pluralizeText`. +* Add logging info in i18n helpers. + ## v3.101.0 (2024-09-25) -#### :boom: breaking change +#### :boom: Breaking Change * changed `i18n` translations format. * added `intl` support for pluralization. From 58708cc9e1dc9cbf668e00edfab2cec9212596b0 Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Mon, 7 Oct 2024 14:42:51 +0500 Subject: [PATCH 04/11] one form in error logging --- src/core/prelude/i18n/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/prelude/i18n/helpers.ts b/src/core/prelude/i18n/helpers.ts index 2c3d8b784..ebf0962b1 100644 --- a/src/core/prelude/i18n/helpers.ts +++ b/src/core/prelude/i18n/helpers.ts @@ -151,7 +151,7 @@ export function pluralizeText( if (normalizedCount == null) { logger.error( 'Invalid value of the `count` parameter for string pluralization', - `String: ${pluralTranslation[0]}, Keysets: ${meta?.keysets}, Language: ${meta?.language}` + `String: ${pluralTranslation.one}, Keysets: ${meta?.keysets}, Language: ${meta?.language}` ); normalizedCount = 1; @@ -164,7 +164,7 @@ export function pluralizeText( if (translation == null) { logger.error( `Plural form ${pluralFormName} doesn't exist.`, - `String: ${pluralTranslation[0]}, Keysets: ${meta?.keysets}, Language: ${meta?.language}` + `String: ${pluralTranslation.one}, Keysets: ${meta?.keysets}, Language: ${meta?.language}` ); return pluralTranslation.one; From 0a184d120ecbbc42adfea6aad6127ce095f778bd Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Mon, 7 Oct 2024 15:17:13 +0500 Subject: [PATCH 05/11] add tests for non existing forms --- src/core/prelude/i18n/spec.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/core/prelude/i18n/spec.ts b/src/core/prelude/i18n/spec.ts index 69cd3b5cb..7a77e3c7e 100644 --- a/src/core/prelude/i18n/spec.ts +++ b/src/core/prelude/i18n/spec.ts @@ -56,6 +56,17 @@ describe('core/prelude/i18n', () => { expect(pluralizeText(input.forms, input.count[index], rules)).toBe(form); }); }); + + it('return one form if correct form does not exists', () => { + const input = { + forms, + count: [1, 2, 100, 0] + }; + + [forms.one, forms.one, forms.one, forms.one].forEach((form, index) => { + expect(pluralizeText({one: input.forms.one}, input.count[index], rules)).toBe(form); + }); + }); }); describe('substitution of variables and pluralization forms in a template', () => { From 7149a769aa427ba21709ea5e2fca9c067d56fa04 Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Mon, 7 Oct 2024 17:03:13 +0500 Subject: [PATCH 06/11] change logging info --- src/core/prelude/i18n/helpers.ts | 10 +++++----- src/core/prelude/i18n/interface.ts | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/core/prelude/i18n/helpers.ts b/src/core/prelude/i18n/helpers.ts index ebf0962b1..a0dfd3d94 100644 --- a/src/core/prelude/i18n/helpers.ts +++ b/src/core/prelude/i18n/helpers.ts @@ -55,7 +55,7 @@ export function i18nFactory( key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find((keysetName) => langPacs[resolvedLocale]?.[keysetName]?.[key]), translateValue = langPacs[resolvedLocale]?.[correctKeyset ?? '']?.[key], - meta: I18nMeta = {language: resolvedLocale, keysets: keysetNames.join(', ')}; + meta: I18nMeta = {language: resolvedLocale, keyset: correctKeyset, key}; if (translateValue != null && translateValue !== '') { return resolveTemplate(translateValue, params, {pluralRules}, meta); @@ -63,7 +63,7 @@ export function i18nFactory( logger.error( 'Translation for the given key is not found', - `Key: ${key}, KeysetNames: ${meta.keysets}, LocaleName: ${resolvedLocale}, available locales: ${Object.keys(langPacs).join(', ')}` + `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}, available locales: ${Object.keys(langPacs).join(', ')}` ); return resolveTemplate(key, params, {pluralRules}, meta); @@ -100,7 +100,7 @@ export function resolveTemplate(value: Translation, params?: I18nParams, opts: I return template.replace(/{([^}]+)}/g, (_, key) => { if (params?.[key] == null) { - logger.error('Undeclared variable', `Name: "${key}", Template: "${template}", Keysets: "${meta?.keysets}", Language: "${meta?.language}"`); + logger.error('Undeclared variable', `Name: "${key}", Template: "${template}"`); return key; } @@ -151,7 +151,7 @@ export function pluralizeText( if (normalizedCount == null) { logger.error( 'Invalid value of the `count` parameter for string pluralization', - `String: ${pluralTranslation.one}, Keysets: ${meta?.keysets}, Language: ${meta?.language}` + `Count: ${count}, Key: ${meta?.key}, Language: ${meta?.language}, Keyset: ${meta?.keyset}` ); normalizedCount = 1; @@ -164,7 +164,7 @@ export function pluralizeText( if (translation == null) { logger.error( `Plural form ${pluralFormName} doesn't exist.`, - `String: ${pluralTranslation.one}, Keysets: ${meta?.keysets}, Language: ${meta?.language}` + `Key: ${meta?.key}, Language: ${meta?.language}, Keyset: ${meta?.keyset}` ); return pluralTranslation.one; diff --git a/src/core/prelude/i18n/interface.ts b/src/core/prelude/i18n/interface.ts index 64de75e53..d7a8e484f 100644 --- a/src/core/prelude/i18n/interface.ts +++ b/src/core/prelude/i18n/interface.ts @@ -49,5 +49,6 @@ export interface I18nOpts { export interface I18nMeta { language: string; - keysets: string; + key: string; + keyset?: string; } From 382b424c18fd36db792da2d06769eb1362ae415f Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Mon, 7 Oct 2024 20:17:12 +0500 Subject: [PATCH 07/11] refactor i18n helpers --- src/core/prelude/i18n/helpers.ts | 41 ++++++++++++++++-------------- src/core/prelude/i18n/interface.ts | 10 ++++++-- src/core/prelude/i18n/spec.ts | 34 ++++++++++++------------- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/core/prelude/i18n/helpers.ts b/src/core/prelude/i18n/helpers.ts index a0dfd3d94..632a705fd 100644 --- a/src/core/prelude/i18n/helpers.ts +++ b/src/core/prelude/i18n/helpers.ts @@ -12,7 +12,7 @@ import extend from 'core/prelude/extend'; import langPacs, { Translation, PluralTranslation } from 'lang'; import { locale } from 'core/prelude/i18n/const'; -import type { I18nOpts, PluralizationCount, I18nMeta } from 'core/prelude/i18n/interface'; +import type { I18nOpts, I18nMeta, I18nPluralizationParams } from 'core/prelude/i18n/interface'; /** @see [[i18n]] */ extend(globalThis, 'i18n', i18nFactory); @@ -58,7 +58,7 @@ export function i18nFactory( meta: I18nMeta = {language: resolvedLocale, keyset: correctKeyset, key}; if (translateValue != null && translateValue !== '') { - return resolveTemplate(translateValue, params, {pluralRules}, meta); + return resolveTemplate(translateValue, {params, pluralRules}, meta); } logger.error( @@ -66,7 +66,7 @@ export function i18nFactory( `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}, available locales: ${Object.keys(langPacs).join(', ')}` ); - return resolveTemplate(key, params, {pluralRules}, meta); + return resolveTemplate(key, {params, pluralRules}, meta); }; } @@ -74,13 +74,15 @@ export function i18nFactory( * Returns the form for plural sentences and resolves variables from the passed template * * @param value - a string for the default case, or an array of strings for the plural case - * @param params - a dictionary with parameters for internationalization - * @params [opts] = I18n options for current translation + * @params [opts] = I18n options for current translation. Plural rules, variables, e.t.c * @param [meta] - I18n meta information about current translation * * @example * ```typescript - * const example = resolveTemplate('My name is {name}, I live in {city}', {name: 'John', city: 'Denver'}); + * const example = resolveTemplate( + * 'My name is {name}, I live in {city}', + * {params: {name: 'John', city: 'Denver'} + * }); * * console.log(example); // 'My name is John, I live in Denver' * @@ -89,14 +91,15 @@ export function i18nFactory( * few: {count} products, * many: {count} products, * zero: {count} products, - * }, {count: 5}); + * }, {params: {count: 5}}); * * console.log(examplePluralize); // '5 products' * ``` */ -export function resolveTemplate(value: Translation, params?: I18nParams, opts: I18nOpts = {}, meta?: I18nMeta): string { +export function resolveTemplate(value: Translation, opts: I18nOpts = {}, meta?: I18nMeta): string { const - template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, opts.pluralRules, meta) : value; + {params, pluralRules: rules} = opts, + template = Object.isPlainObject(value) ? pluralizeText(value, {count: params?.count, rules}, meta) : value; return template.replace(/{([^}]+)}/g, (_, key) => { if (params?.[key] == null) { @@ -112,29 +115,29 @@ export function resolveTemplate(value: Translation, params?: I18nParams, opts: I * Returns the correct plural form to translate based on the given count * * @param pluralTranslation - list of translation variants - * @param count - the value on the basis of which the form of pluralization will be selected - * @param rules - Intl plural rules for selected locale + * @param params - Pluralization params for current text. Plural rules, count, e.t.c. * @param [meta] - I18n meta information about current translation * * @example * ```typescript * const result = pluralizeText({ - * one: {count} product, - * few: {count} products, - * many: {count} products, - * zero: {count} products, - * other: {count} products, - * }, 5, new Intl.PluralRulse('en')); + * one: {count} product, + * few: {count} products, + * many: {count} products, + * zero: {count} products, + * other: {count} products, + * }, {count: 5, rules: new Intl.PluralRulse('en')}); * * console.log(result); // '{count} products' * ``` */ export function pluralizeText( pluralTranslation: PluralTranslation, - count: CanUndef, - rules: CanUndef, + params: I18nPluralizationParams, meta?: I18nMeta ): string { + const {count, rules} = params; + let normalizedCount; if (Object.isNumber(count)) { diff --git a/src/core/prelude/i18n/interface.ts b/src/core/prelude/i18n/interface.ts index d7a8e484f..29b5369b3 100644 --- a/src/core/prelude/i18n/interface.ts +++ b/src/core/prelude/i18n/interface.ts @@ -43,8 +43,9 @@ export interface LocaleKVStorage { export type PluralizationCount = StringPluralizationForms | number; -export interface I18nOpts { - pluralRules?: Intl.PluralRules; +export interface I18nPluralizationParams { + count?: PluralizationCount; + rules?: Intl.PluralRules; } export interface I18nMeta { @@ -52,3 +53,8 @@ export interface I18nMeta { key: string; keyset?: string; } + +export interface I18nOpts { + params?: I18nParams; + pluralRules?: Intl.PluralRules; +} diff --git a/src/core/prelude/i18n/spec.ts b/src/core/prelude/i18n/spec.ts index 7a77e3c7e..8fec47753 100644 --- a/src/core/prelude/i18n/spec.ts +++ b/src/core/prelude/i18n/spec.ts @@ -42,7 +42,7 @@ describe('core/prelude/i18n', () => { describe('text pluralization', () => { it('using pluralization constants to choose the right form', () => { formNames.forEach((form) => { - expect(pluralizeText(forms, form, rules)).toBe(forms[form]); + expect(pluralizeText(forms, {count: form, rules})).toBe(forms[form]); }); }); @@ -53,7 +53,7 @@ describe('core/prelude/i18n', () => { }; [forms.one, forms.other, forms.other, forms.other].forEach((form, index) => { - expect(pluralizeText(input.forms, input.count[index], rules)).toBe(form); + expect(pluralizeText(input.forms, {count: input.count[index], rules})).toBe(form); }); }); @@ -64,7 +64,7 @@ describe('core/prelude/i18n', () => { }; [forms.one, forms.one, forms.one, forms.one].forEach((form, index) => { - expect(pluralizeText({one: input.forms.one}, input.count[index], rules)).toBe(form); + expect(pluralizeText({one: input.forms.one}, {count: input.count[index], rules})).toBe(form); }); }); }); @@ -76,12 +76,12 @@ describe('core/prelude/i18n', () => { it('passing variables for template resolving', () => { const tpl = 'foo {macros} {macros2}'; - expect(resolveTemplate(tpl, {macros: 'bar', macros2: 'baz'})).toBe('foo bar baz'); + expect(resolveTemplate(tpl, {params: {macros: 'bar', macros2: 'baz'}})).toBe('foo bar baz'); }); it('if the variable is not set, then it should be displayed as text', () => { const tpl = 'foo {macros} {macros2}'; - expect(resolveTemplate(tpl, {macros: 'bar'})).toBe('foo bar macros2'); + expect(resolveTemplate(tpl, {params: {macros: 'bar'}})).toBe('foo bar macros2'); }); it('passing the `count` parameter for template resolving', () => { @@ -90,14 +90,14 @@ describe('core/prelude/i18n', () => { few: 'few {count}', many: 'many {count}', other: 'other {count}' - }, {count: 5}, {pluralRules: rules}); + }, {params: {count: 5}, pluralRules: rules}); const res2 = resolveTemplate({ one: 'one {count}', few: 'few {count}', many: 'many {count}', other: 'other {count}' - }, {count: 1}, {pluralRules: rules}); + }, {params: {count: 1}, pluralRules: rules}); expect(res1).toBe('other 5'); expect(res2).toBe('one 1'); @@ -115,11 +115,11 @@ describe('core/prelude/i18n', () => { zero: '{count} яблок' }; - expect(resolveTemplate(forms, {count: 1}, {pluralRules: cyrillicRules})).toBe('1 яблоко'); - expect(resolveTemplate(forms, {count: 2}, {pluralRules: cyrillicRules})).toBe('2 яблока'); - expect(resolveTemplate(forms, {count: 0}, {pluralRules: cyrillicRules})).toBe('0 яблок'); - expect(resolveTemplate(forms, {count: 12}, {pluralRules: cyrillicRules})).toBe('12 яблок'); - expect(resolveTemplate(forms, {count: 22}, {pluralRules: cyrillicRules})).toBe('22 яблока'); + expect(resolveTemplate(forms, {params: {count: 1}, pluralRules: cyrillicRules})).toBe('1 яблоко'); + expect(resolveTemplate(forms, {params: {count: 2}, pluralRules: cyrillicRules})).toBe('2 яблока'); + expect(resolveTemplate(forms, {params: {count: 0}, pluralRules: cyrillicRules})).toBe('0 яблок'); + expect(resolveTemplate(forms, {params: {count: 12}, pluralRules: cyrillicRules})).toBe('12 яблок'); + expect(resolveTemplate(forms, {params: {count: 22}, pluralRules: cyrillicRules})).toBe('22 яблока'); }); it('russian language without Intl', () => { @@ -131,11 +131,11 @@ describe('core/prelude/i18n', () => { zero: '{count} яблок' }; - expect(resolveTemplate(forms, {count: 1})).toBe('1 яблоко'); - expect(resolveTemplate(forms, {count: 2})).toBe('2 яблока'); - expect(resolveTemplate(forms, {count: 0})).toBe('0 яблок'); - expect(resolveTemplate(forms, {count: 12})).toBe('12 яблок'); - expect(resolveTemplate(forms, {count: 22})).toBe('22 яблок'); + expect(resolveTemplate(forms, {params: {count: 1}})).toBe('1 яблоко'); + expect(resolveTemplate(forms, {params: {count: 2}})).toBe('2 яблока'); + expect(resolveTemplate(forms, {params: {count: 0}})).toBe('0 яблок'); + expect(resolveTemplate(forms, {params: {count: 12}})).toBe('12 яблок'); + expect(resolveTemplate(forms, {params: {count: 22}})).toBe('22 яблок'); }); }); }); From ce19ca0bdb3d29ab5afb46a7e2e4ff10fa3e38bb Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Tue, 8 Oct 2024 14:48:57 +0500 Subject: [PATCH 08/11] Revert "refactor i18n helpers" This reverts commit 382b424c18fd36db792da2d06769eb1362ae415f. --- src/core/prelude/i18n/helpers.ts | 41 ++++++++++++++---------------- src/core/prelude/i18n/interface.ts | 10 ++------ src/core/prelude/i18n/spec.ts | 34 ++++++++++++------------- 3 files changed, 38 insertions(+), 47 deletions(-) diff --git a/src/core/prelude/i18n/helpers.ts b/src/core/prelude/i18n/helpers.ts index 632a705fd..a0dfd3d94 100644 --- a/src/core/prelude/i18n/helpers.ts +++ b/src/core/prelude/i18n/helpers.ts @@ -12,7 +12,7 @@ import extend from 'core/prelude/extend'; import langPacs, { Translation, PluralTranslation } from 'lang'; import { locale } from 'core/prelude/i18n/const'; -import type { I18nOpts, I18nMeta, I18nPluralizationParams } from 'core/prelude/i18n/interface'; +import type { I18nOpts, PluralizationCount, I18nMeta } from 'core/prelude/i18n/interface'; /** @see [[i18n]] */ extend(globalThis, 'i18n', i18nFactory); @@ -58,7 +58,7 @@ export function i18nFactory( meta: I18nMeta = {language: resolvedLocale, keyset: correctKeyset, key}; if (translateValue != null && translateValue !== '') { - return resolveTemplate(translateValue, {params, pluralRules}, meta); + return resolveTemplate(translateValue, params, {pluralRules}, meta); } logger.error( @@ -66,7 +66,7 @@ export function i18nFactory( `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}, available locales: ${Object.keys(langPacs).join(', ')}` ); - return resolveTemplate(key, {params, pluralRules}, meta); + return resolveTemplate(key, params, {pluralRules}, meta); }; } @@ -74,15 +74,13 @@ export function i18nFactory( * Returns the form for plural sentences and resolves variables from the passed template * * @param value - a string for the default case, or an array of strings for the plural case - * @params [opts] = I18n options for current translation. Plural rules, variables, e.t.c + * @param params - a dictionary with parameters for internationalization + * @params [opts] = I18n options for current translation * @param [meta] - I18n meta information about current translation * * @example * ```typescript - * const example = resolveTemplate( - * 'My name is {name}, I live in {city}', - * {params: {name: 'John', city: 'Denver'} - * }); + * const example = resolveTemplate('My name is {name}, I live in {city}', {name: 'John', city: 'Denver'}); * * console.log(example); // 'My name is John, I live in Denver' * @@ -91,15 +89,14 @@ export function i18nFactory( * few: {count} products, * many: {count} products, * zero: {count} products, - * }, {params: {count: 5}}); + * }, {count: 5}); * * console.log(examplePluralize); // '5 products' * ``` */ -export function resolveTemplate(value: Translation, opts: I18nOpts = {}, meta?: I18nMeta): string { +export function resolveTemplate(value: Translation, params?: I18nParams, opts: I18nOpts = {}, meta?: I18nMeta): string { const - {params, pluralRules: rules} = opts, - template = Object.isPlainObject(value) ? pluralizeText(value, {count: params?.count, rules}, meta) : value; + template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, opts.pluralRules, meta) : value; return template.replace(/{([^}]+)}/g, (_, key) => { if (params?.[key] == null) { @@ -115,29 +112,29 @@ export function resolveTemplate(value: Translation, opts: I18nOpts = {}, meta?: * Returns the correct plural form to translate based on the given count * * @param pluralTranslation - list of translation variants - * @param params - Pluralization params for current text. Plural rules, count, e.t.c. + * @param count - the value on the basis of which the form of pluralization will be selected + * @param rules - Intl plural rules for selected locale * @param [meta] - I18n meta information about current translation * * @example * ```typescript * const result = pluralizeText({ - * one: {count} product, - * few: {count} products, - * many: {count} products, - * zero: {count} products, - * other: {count} products, - * }, {count: 5, rules: new Intl.PluralRulse('en')}); + * one: {count} product, + * few: {count} products, + * many: {count} products, + * zero: {count} products, + * other: {count} products, + * }, 5, new Intl.PluralRulse('en')); * * console.log(result); // '{count} products' * ``` */ export function pluralizeText( pluralTranslation: PluralTranslation, - params: I18nPluralizationParams, + count: CanUndef, + rules: CanUndef, meta?: I18nMeta ): string { - const {count, rules} = params; - let normalizedCount; if (Object.isNumber(count)) { diff --git a/src/core/prelude/i18n/interface.ts b/src/core/prelude/i18n/interface.ts index 29b5369b3..d7a8e484f 100644 --- a/src/core/prelude/i18n/interface.ts +++ b/src/core/prelude/i18n/interface.ts @@ -43,9 +43,8 @@ export interface LocaleKVStorage { export type PluralizationCount = StringPluralizationForms | number; -export interface I18nPluralizationParams { - count?: PluralizationCount; - rules?: Intl.PluralRules; +export interface I18nOpts { + pluralRules?: Intl.PluralRules; } export interface I18nMeta { @@ -53,8 +52,3 @@ export interface I18nMeta { key: string; keyset?: string; } - -export interface I18nOpts { - params?: I18nParams; - pluralRules?: Intl.PluralRules; -} diff --git a/src/core/prelude/i18n/spec.ts b/src/core/prelude/i18n/spec.ts index 8fec47753..7a77e3c7e 100644 --- a/src/core/prelude/i18n/spec.ts +++ b/src/core/prelude/i18n/spec.ts @@ -42,7 +42,7 @@ describe('core/prelude/i18n', () => { describe('text pluralization', () => { it('using pluralization constants to choose the right form', () => { formNames.forEach((form) => { - expect(pluralizeText(forms, {count: form, rules})).toBe(forms[form]); + expect(pluralizeText(forms, form, rules)).toBe(forms[form]); }); }); @@ -53,7 +53,7 @@ describe('core/prelude/i18n', () => { }; [forms.one, forms.other, forms.other, forms.other].forEach((form, index) => { - expect(pluralizeText(input.forms, {count: input.count[index], rules})).toBe(form); + expect(pluralizeText(input.forms, input.count[index], rules)).toBe(form); }); }); @@ -64,7 +64,7 @@ describe('core/prelude/i18n', () => { }; [forms.one, forms.one, forms.one, forms.one].forEach((form, index) => { - expect(pluralizeText({one: input.forms.one}, {count: input.count[index], rules})).toBe(form); + expect(pluralizeText({one: input.forms.one}, input.count[index], rules)).toBe(form); }); }); }); @@ -76,12 +76,12 @@ describe('core/prelude/i18n', () => { it('passing variables for template resolving', () => { const tpl = 'foo {macros} {macros2}'; - expect(resolveTemplate(tpl, {params: {macros: 'bar', macros2: 'baz'}})).toBe('foo bar baz'); + expect(resolveTemplate(tpl, {macros: 'bar', macros2: 'baz'})).toBe('foo bar baz'); }); it('if the variable is not set, then it should be displayed as text', () => { const tpl = 'foo {macros} {macros2}'; - expect(resolveTemplate(tpl, {params: {macros: 'bar'}})).toBe('foo bar macros2'); + expect(resolveTemplate(tpl, {macros: 'bar'})).toBe('foo bar macros2'); }); it('passing the `count` parameter for template resolving', () => { @@ -90,14 +90,14 @@ describe('core/prelude/i18n', () => { few: 'few {count}', many: 'many {count}', other: 'other {count}' - }, {params: {count: 5}, pluralRules: rules}); + }, {count: 5}, {pluralRules: rules}); const res2 = resolveTemplate({ one: 'one {count}', few: 'few {count}', many: 'many {count}', other: 'other {count}' - }, {params: {count: 1}, pluralRules: rules}); + }, {count: 1}, {pluralRules: rules}); expect(res1).toBe('other 5'); expect(res2).toBe('one 1'); @@ -115,11 +115,11 @@ describe('core/prelude/i18n', () => { zero: '{count} яблок' }; - expect(resolveTemplate(forms, {params: {count: 1}, pluralRules: cyrillicRules})).toBe('1 яблоко'); - expect(resolveTemplate(forms, {params: {count: 2}, pluralRules: cyrillicRules})).toBe('2 яблока'); - expect(resolveTemplate(forms, {params: {count: 0}, pluralRules: cyrillicRules})).toBe('0 яблок'); - expect(resolveTemplate(forms, {params: {count: 12}, pluralRules: cyrillicRules})).toBe('12 яблок'); - expect(resolveTemplate(forms, {params: {count: 22}, pluralRules: cyrillicRules})).toBe('22 яблока'); + expect(resolveTemplate(forms, {count: 1}, {pluralRules: cyrillicRules})).toBe('1 яблоко'); + expect(resolveTemplate(forms, {count: 2}, {pluralRules: cyrillicRules})).toBe('2 яблока'); + expect(resolveTemplate(forms, {count: 0}, {pluralRules: cyrillicRules})).toBe('0 яблок'); + expect(resolveTemplate(forms, {count: 12}, {pluralRules: cyrillicRules})).toBe('12 яблок'); + expect(resolveTemplate(forms, {count: 22}, {pluralRules: cyrillicRules})).toBe('22 яблока'); }); it('russian language without Intl', () => { @@ -131,11 +131,11 @@ describe('core/prelude/i18n', () => { zero: '{count} яблок' }; - expect(resolveTemplate(forms, {params: {count: 1}})).toBe('1 яблоко'); - expect(resolveTemplate(forms, {params: {count: 2}})).toBe('2 яблока'); - expect(resolveTemplate(forms, {params: {count: 0}})).toBe('0 яблок'); - expect(resolveTemplate(forms, {params: {count: 12}})).toBe('12 яблок'); - expect(resolveTemplate(forms, {params: {count: 22}})).toBe('22 яблок'); + expect(resolveTemplate(forms, {count: 1})).toBe('1 яблоко'); + expect(resolveTemplate(forms, {count: 2})).toBe('2 яблока'); + expect(resolveTemplate(forms, {count: 0})).toBe('0 яблок'); + expect(resolveTemplate(forms, {count: 12})).toBe('12 яблок'); + expect(resolveTemplate(forms, {count: 22})).toBe('22 яблок'); }); }); }); From 8420c97d5ca6d54793136b080a4c71d4f64e9061 Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Tue, 8 Oct 2024 14:46:27 +0500 Subject: [PATCH 09/11] refactor i18n helpers --- src/core/prelude/i18n/helpers.ts | 23 +++++++++++------------ src/core/prelude/i18n/interface.ts | 10 ++++++---- src/core/prelude/i18n/spec.ts | 6 +++--- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/core/prelude/i18n/helpers.ts b/src/core/prelude/i18n/helpers.ts index a0dfd3d94..cf5c802bf 100644 --- a/src/core/prelude/i18n/helpers.ts +++ b/src/core/prelude/i18n/helpers.ts @@ -58,7 +58,7 @@ export function i18nFactory( meta: I18nMeta = {language: resolvedLocale, keyset: correctKeyset, key}; if (translateValue != null && translateValue !== '') { - return resolveTemplate(translateValue, params, {pluralRules}, meta); + return resolveTemplate(translateValue, params, {pluralRules, meta}); } logger.error( @@ -66,7 +66,7 @@ export function i18nFactory( `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}, available locales: ${Object.keys(langPacs).join(', ')}` ); - return resolveTemplate(key, params, {pluralRules}, meta); + return resolveTemplate(key, params, {pluralRules, meta}); }; } @@ -75,8 +75,7 @@ export function i18nFactory( * * @param value - a string for the default case, or an array of strings for the plural case * @param params - a dictionary with parameters for internationalization - * @params [opts] = I18n options for current translation - * @param [meta] - I18n meta information about current translation + * @params [opts] - additional options for current translation * * @example * ```typescript @@ -94,9 +93,9 @@ export function i18nFactory( * console.log(examplePluralize); // '5 products' * ``` */ -export function resolveTemplate(value: Translation, params?: I18nParams, opts: I18nOpts = {}, meta?: I18nMeta): string { +export function resolveTemplate(value: Translation, params?: I18nParams, opts: I18nOpts = {}): string { const - template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, opts.pluralRules, meta) : value; + template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, opts) : value; return template.replace(/{([^}]+)}/g, (_, key) => { if (params?.[key] == null) { @@ -113,8 +112,7 @@ export function resolveTemplate(value: Translation, params?: I18nParams, opts: I * * @param pluralTranslation - list of translation variants * @param count - the value on the basis of which the form of pluralization will be selected - * @param rules - Intl plural rules for selected locale - * @param [meta] - I18n meta information about current translation + * @params [opts] - additional options for current translation * * @example * ```typescript @@ -124,7 +122,7 @@ export function resolveTemplate(value: Translation, params?: I18nParams, opts: I * many: {count} products, * zero: {count} products, * other: {count} products, - * }, 5, new Intl.PluralRulse('en')); + * }, 5, {pluralRules: new Intl.PluralRulse('en')}); * * console.log(result); // '{count} products' * ``` @@ -132,9 +130,10 @@ export function resolveTemplate(value: Translation, params?: I18nParams, opts: I export function pluralizeText( pluralTranslation: PluralTranslation, count: CanUndef, - rules: CanUndef, - meta?: I18nMeta + opts: I18nOpts = {} ): string { + const {pluralRules, meta} = opts; + let normalizedCount; if (Object.isNumber(count)) { @@ -158,7 +157,7 @@ export function pluralizeText( } const - pluralFormName = getPluralFormName(normalizedCount, rules), + pluralFormName = getPluralFormName(normalizedCount, pluralRules), translation = pluralTranslation[pluralFormName]; if (translation == null) { diff --git a/src/core/prelude/i18n/interface.ts b/src/core/prelude/i18n/interface.ts index d7a8e484f..fe97cbd39 100644 --- a/src/core/prelude/i18n/interface.ts +++ b/src/core/prelude/i18n/interface.ts @@ -43,12 +43,14 @@ export interface LocaleKVStorage { export type PluralizationCount = StringPluralizationForms | number; -export interface I18nOpts { - pluralRules?: Intl.PluralRules; -} - export interface I18nMeta { language: string; key: string; keyset?: string; } + +export interface I18nOpts { + pluralRules?: Intl.PluralRules; + meta?: I18nMeta; +} + diff --git a/src/core/prelude/i18n/spec.ts b/src/core/prelude/i18n/spec.ts index 7a77e3c7e..8383f926b 100644 --- a/src/core/prelude/i18n/spec.ts +++ b/src/core/prelude/i18n/spec.ts @@ -42,7 +42,7 @@ describe('core/prelude/i18n', () => { describe('text pluralization', () => { it('using pluralization constants to choose the right form', () => { formNames.forEach((form) => { - expect(pluralizeText(forms, form, rules)).toBe(forms[form]); + expect(pluralizeText(forms, form, {pluralRules: rules})).toBe(forms[form]); }); }); @@ -53,7 +53,7 @@ describe('core/prelude/i18n', () => { }; [forms.one, forms.other, forms.other, forms.other].forEach((form, index) => { - expect(pluralizeText(input.forms, input.count[index], rules)).toBe(form); + expect(pluralizeText(input.forms, input.count[index], {pluralRules: rules})).toBe(form); }); }); @@ -64,7 +64,7 @@ describe('core/prelude/i18n', () => { }; [forms.one, forms.one, forms.one, forms.one].forEach((form, index) => { - expect(pluralizeText({one: input.forms.one}, input.count[index], rules)).toBe(form); + expect(pluralizeText({one: input.forms.one}, input.count[index], {pluralRules: rules})).toBe(form); }); }); }); From 2d2992b5de4d304f0f86b84f54315426acd49cca Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Thu, 10 Oct 2024 15:44:04 +0500 Subject: [PATCH 10/11] add test and change readme --- src/core/prelude/CHANGELOG.md | 2 +- src/core/prelude/i18n/README.md | 28 +++++++++++++++------------- src/core/prelude/i18n/spec.ts | 10 ++++++++++ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/core/prelude/CHANGELOG.md b/src/core/prelude/CHANGELOG.md index 46516d0a8..de2f504e0 100644 --- a/src/core/prelude/CHANGELOG.md +++ b/src/core/prelude/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v3.101.1 (2024-10-07) +## v3.101.1 (2024-10-10) #### :bug: Bug Fix diff --git a/src/core/prelude/i18n/README.md b/src/core/prelude/i18n/README.md index 25443469c..48bc24b0d 100644 --- a/src/core/prelude/i18n/README.md +++ b/src/core/prelude/i18n/README.md @@ -89,30 +89,32 @@ i18n('my-component')('My name is {name}', {name: 'John'}); ## Pluralization of translations Some keys may have multiple translations depending on some numeric value. For example, "1 apple" or "5 apples". -To specify such translations, a special macro `{count}` is used, and translations are specified as a tuple `[one, some, many, none]`. +To specify such translations, a special macro `{count}` is used, and translations are specified as a tuple `[zero, one, two, few, many, other]`. ```js export default { ru: { "my-component": { "time": "время", - "{count} product": [ - "{count} продукт", - "{count} продукта", - "{count} продуктов", - "{count} продуктов" - ] + "{count} product": { + "one": "{count} product", + "few": "{count} products", + "many": "{count} products", + "zero": "{count} products", + "other": "{count} products", + } } }, en: { "my-component": { - "{count} product": [ - "{count} product", - "{count} products", - "{count} products", - "{count} products" - ] + "{count} product": { + "one": "{count} product", + "few": "{count} products", + "many": "{count} products", + "zero": "{count} products", + "other": "{count} products", + } } } }; diff --git a/src/core/prelude/i18n/spec.ts b/src/core/prelude/i18n/spec.ts index 8383f926b..aa0602f0d 100644 --- a/src/core/prelude/i18n/spec.ts +++ b/src/core/prelude/i18n/spec.ts @@ -67,6 +67,16 @@ describe('core/prelude/i18n', () => { expect(pluralizeText({one: input.forms.one}, input.count[index], {pluralRules: rules})).toBe(form); }); }); + + it('return one form if incorrect count was passed', () => { + const input = { + forms + }; + + [forms.one, forms.one, forms.one, forms.one].forEach((form) => { + expect(pluralizeText({one: input.forms.one}, undefined, {pluralRules: rules})).toBe(form); + }); + }); }); describe('substitution of variables and pluralization forms in a template', () => { From ee81dbf597418c9f0584e0aea256ba7209b3e8e4 Mon Sep 17 00:00:00 2001 From: Maksim Sinelnikov Date: Mon, 21 Oct 2024 13:19:47 +0500 Subject: [PATCH 11/11] up changelog --- CHANGELOG.md | 2 +- src/core/prelude/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4388f4c00..8d413770c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Changelog _Note: Gaps between patch versions are faulty, broken or test releases._ -## v3.101.1 (2024-10-07) +## v3.101.1 (2024-10-21) #### :bug: Bug Fix diff --git a/src/core/prelude/CHANGELOG.md b/src/core/prelude/CHANGELOG.md index de2f504e0..3f2edd551 100644 --- a/src/core/prelude/CHANGELOG.md +++ b/src/core/prelude/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v3.101.1 (2024-10-10) +## v3.101.1 (2024-10-21) #### :bug: Bug Fix