Skip to content

Commit c26d3b3

Browse files
added tests
1 parent 5e35244 commit c26d3b3

File tree

2 files changed

+173
-64
lines changed

2 files changed

+173
-64
lines changed

src/v3/src/util/languageUtils.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ export const initDefaultLanguage = () => {
2424
// Load translations for default language from Bundles to i18next
2525
const languageCode = Bundles.currentLanguage ?? config.defaultLanguage;
2626
const isDefaultLanguage = !Bundles.currentLanguage;
27-
if (isDefaultLanguage && !i18next?.hasResourceBundle(languageCode, 'login')) {
28-
i18next?.addResourceBundle(languageCode, 'login', Bundles.login);
29-
i18next?.addResourceBundle(languageCode, 'country', Bundles.country);
27+
if (isDefaultLanguage && !i18next.hasResourceBundle(languageCode, 'login')) {
28+
i18next.addResourceBundle(languageCode, 'login', Bundles.login);
29+
i18next.addResourceBundle(languageCode, 'country', Bundles.country);
3030
}
3131
};
3232

@@ -53,9 +53,9 @@ export const loadLanguage = async (widgetProps: WidgetProps): Promise<void> => {
5353
}, supportedLanguages, omitDefaultKeys);
5454

5555
// Load translations from Bundles to i18next and change language
56-
i18next?.addResourceBundle(languageCode, 'login', Bundles.login);
57-
i18next?.addResourceBundle(languageCode, 'country', Bundles.country);
58-
i18next?.changeLanguage(languageCode);
56+
i18next.addResourceBundle(languageCode, 'login', Bundles.login);
57+
i18next.addResourceBundle(languageCode, 'country', Bundles.country);
58+
i18next.changeLanguage(languageCode);
5959
};
6060

6161
export const unloadLanguage = (languageCode: LanguageCode) => {
@@ -64,10 +64,10 @@ export const unloadLanguage = (languageCode: LanguageCode) => {
6464
// For dev environment with HMR don't clear translations.
6565
// Otherwise during HMR widget language will be reset to default one.
6666
// `Bundles.remove()` also doesn't reset bundles to default language.
67-
i18next?.removeResourceBundle(languageCode, 'login');
68-
i18next?.removeResourceBundle(languageCode, 'country');
67+
i18next.removeResourceBundle(languageCode, 'login');
68+
i18next.removeResourceBundle(languageCode, 'country');
6969
}
70-
i18next?.changeLanguage(undefined);
70+
i18next.changeLanguage(undefined);
7171
};
7272

7373
export const getOdysseyTranslationOverrides = (): Partial<OdysseyI18nResourceKeys> => (

src/v3/src/util/locUtil.test.ts

Lines changed: 164 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,69 +10,178 @@
1010
* See the License for the specific language governing permissions and limitations under the License.
1111
*/
1212

13-
jest.unmock('./locUtil');
13+
import type { loc as origLoc } from './locUtil';
14+
import * as OrigLanguageUtils from './languageUtils';
1415

15-
const MockedBundle: Record<string, string> = {
16-
'some.basic.key': 'This is a key without params',
17-
'some.key.with.$1.token': 'This is a key with a token <$1>This is some text</$1>',
18-
'some.key.with.plain.html': 'This is a key with a token <span class="strong">This is some text</span>',
19-
'some.key.with.multiple.tokens': 'This is some test string with multiple tokens: <$1> <$2> here is a test string </$2> </$1>',
20-
};
21-
22-
jest.mock('./i18next', () => ({
23-
i18next: {
24-
t: jest.fn().mockImplementation(
25-
(origKey, params) => {
26-
const bundleAndKey = origKey.split(':');
27-
let bundle;
28-
let key = origKey;
29-
if (bundleAndKey.length === 2) {
30-
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
31-
([bundle, key] = bundleAndKey);
32-
if (bundle === 'login' && MockedBundle[key]) {
33-
return MockedBundle[key].replace('{0}', params?.[0]);
34-
}
35-
}
36-
return '';
37-
},
38-
),
39-
},
40-
}));
16+
jest.unmock('./locUtil');
4117

42-
// eslint-disable-next-line import/first
43-
import { loc } from './locUtil';
18+
let loc: typeof origLoc;
19+
let languageUtils: typeof OrigLanguageUtils;
4420

4521
describe('locUtil Tests', () => {
46-
it('should return simple translated string', () => {
47-
const localizedText = loc('some.basic.key', 'login');
48-
expect(localizedText).toBe('This is a key without params');
49-
});
22+
describe('Token replacement', () => {
23+
const MockedBundle: Record<string, string> = {
24+
'some.basic.key': 'This is a key without params',
25+
'some.key.with.$1.token': 'This is a key with a token <$1>This is some text</$1>',
26+
'some.key.with.plain.html': 'This is a key with a token <span class="strong">This is some text</span>',
27+
'some.key.with.multiple.tokens': 'This is some test string with multiple tokens: <$1> <$2> here is a test string </$2> </$1>',
28+
};
5029

51-
it('should not perform replacement when tokens are not found in the string', () => {
52-
const localizedText = loc('some.key.with.plain.html', 'login', undefined, { $1: { element: 'span' } });
53-
expect(localizedText).toBe('This is a key with a token <span class="strong">This is some text</span>');
54-
});
30+
beforeEach(() => {
31+
jest.resetModules();
32+
jest.unmock('util/Bundles');
33+
jest.mock('./i18next', () => ({
34+
i18next: {
35+
t: jest.fn().mockImplementation(
36+
(origKey, params) => {
37+
const bundleAndKey = origKey.split(':');
38+
let bundle;
39+
let key = origKey;
40+
if (bundleAndKey.length === 2) {
41+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
42+
([bundle, key] = bundleAndKey);
43+
if (bundle === 'login' && MockedBundle[key]) {
44+
return MockedBundle[key].replace('{0}', params?.[0]);
45+
}
46+
}
47+
return '';
48+
},
49+
),
50+
},
51+
}));
5552

56-
it('should perform replacement when tokens are found in the string', () => {
57-
const localizedText = loc('some.key.with.$1.token', 'login', undefined, { $1: { element: 'span' } });
58-
expect(localizedText).toBe('This is a key with a token <span>This is some text</span>');
59-
});
53+
loc = jest.requireActual('./locUtil').loc;
54+
});
6055

61-
it('should perform replacement with provided attributes when tokens are found in the string', () => {
62-
const localizedText = loc('some.key.with.$1.token', 'login', undefined, { $1: { element: 'span', attributes: { class: 'strong' } } });
63-
expect(localizedText).toBe('This is a key with a token <span class="strong">This is some text</span>');
64-
});
56+
it('should return simple translated string', () => {
57+
const localizedText = loc('some.basic.key', 'login');
58+
expect(localizedText).toBe('This is a key without params');
59+
});
60+
61+
it('should not perform replacement when tokens are not found in the string', () => {
62+
const localizedText = loc('some.key.with.plain.html', 'login', undefined, { $1: { element: 'span' } });
63+
expect(localizedText).toBe('This is a key with a token <span class="strong">This is some text</span>');
64+
});
65+
66+
it('should perform replacement when tokens are found in the string', () => {
67+
const localizedText = loc('some.key.with.$1.token', 'login', undefined, { $1: { element: 'span' } });
68+
expect(localizedText).toBe('This is a key with a token <span>This is some text</span>');
69+
});
70+
71+
it('should perform replacement with provided attributes when tokens are found in the string', () => {
72+
const localizedText = loc('some.key.with.$1.token', 'login', undefined, { $1: { element: 'span', attributes: { class: 'strong' } } });
73+
expect(localizedText).toBe('This is a key with a token <span class="strong">This is some text</span>');
74+
});
6575

66-
it('should perform replacement when translation string contains multiple tokens', () => {
67-
const localizedText = loc(
68-
'some.key.with.multiple.tokens',
69-
'login',
70-
undefined,
71-
{
72-
$1: { element: 'a', attributes: { href: '#' } },
73-
$2: { element: 'span', attributes: { class: 'strong' } },
76+
it('should perform replacement when translation string contains multiple tokens', () => {
77+
const localizedText = loc(
78+
'some.key.with.multiple.tokens',
79+
'login',
80+
undefined,
81+
{
82+
$1: { element: 'a', attributes: { href: '#' } },
83+
$2: { element: 'span', attributes: { class: 'strong' } },
84+
},
85+
);
86+
expect(localizedText).toBe('This is some test string with multiple tokens: <a href="#"> <span class="strong"> here is a test string </span> </a>');
87+
});
88+
});
89+
90+
// https://www.i18next.com/translation-function/plurals
91+
describe('Pluralization', () => {
92+
const MockedLogin: Record<string, Record<string, string>> = {
93+
en: {
94+
'item_one': 'one item',
95+
'item_other': '{0} items',
96+
'apple_one': 'one apple',
97+
'apple_other': '{0} apples',
98+
'pear_one': 'one pear',
99+
'pear_other': '{0} pears',
100+
},
101+
ro: {
102+
'item_one': 'un articol',
103+
'item_few': '{0} articole',
104+
'item_other': '{0} de articole',
105+
// 'apple_one': 'un măr', // missing translation
106+
'apple_few': '{0} mere',
107+
'apple_other': '{0} de mere',
108+
'apple': '{0} de mere', // will be used as fallback
109+
'pear_few': '{0} pere', // no fallback
74110
},
75-
);
76-
expect(localizedText).toBe('This is some test string with multiple tokens: <a href="#"> <span class="strong"> here is a test string </span> </a>');
111+
};
112+
113+
beforeEach(() => {
114+
jest.resetModules();
115+
jest.unmock('./i18next');
116+
jest.mock('util/Bundles', () => {
117+
const Bundles: {
118+
currentLanguage: string | undefined,
119+
login: Record<string, string>,
120+
country: Record<string, string>,
121+
loadLanguage: () => Promise<void>,
122+
} = {
123+
currentLanguage: undefined,
124+
login: MockedLogin.en,
125+
country: {},
126+
loadLanguage: jest.fn().mockImplementation(
127+
// eslint-disable-next-line no-unused-vars
128+
async (language: string, overrides: any, assets: Record<string, string>,
129+
supportedLanguages: string[], omitDefaultKeys?: (key: string) => boolean
130+
) => {
131+
Bundles.currentLanguage = language;
132+
Bundles.login = MockedLogin[language] ?? {};
133+
Bundles.country = {};
134+
},
135+
),
136+
};
137+
return Bundles;
138+
});
139+
140+
languageUtils = jest.requireActual('./languageUtils');
141+
loc = jest.requireActual('./locUtil').loc;
142+
});
143+
144+
it('can localize singular/plural in English', () => {
145+
languageUtils.initDefaultLanguage();
146+
const localizedTextOne = loc('item', 'login', [1]);
147+
expect(localizedTextOne).toBe('one item');
148+
const localizedTextTwo = loc('item', 'login', [2]);
149+
expect(localizedTextTwo).toBe('2 items');
150+
});
151+
152+
it('can localize multiple plurals in other languages', async () => {
153+
languageUtils.initDefaultLanguage();
154+
await languageUtils.loadLanguage({ language: 'ro' });
155+
const localizedText1 = loc('item', 'login', [1]);
156+
expect(localizedText1).toBe('un articol');
157+
const localizedText2 = loc('item', 'login', [2]);
158+
expect(localizedText2).toBe('2 articole');
159+
const localizedText20 = loc('item', 'login', [20]);
160+
expect(localizedText20).toBe('20 de articole');
161+
});
162+
163+
it('can fallback to default form if some plural form translation is missing', async () => {
164+
languageUtils.initDefaultLanguage();
165+
await languageUtils.loadLanguage({ language: 'ro' });
166+
const localizedText2 = loc('apple', 'login', [2]);
167+
expect(localizedText2).toBe('2 mere');
168+
// fallback
169+
const localizedText1 = loc('apple', 'login', [1]);
170+
expect(localizedText1).toBe('1 de mere');
171+
});
172+
173+
it('will fallback to English if some plural form translation is missing and there is no fallback', async () => {
174+
languageUtils.initDefaultLanguage();
175+
await languageUtils.loadLanguage({ language: 'ro' });
176+
const localizedText2 = loc('pear', 'login', [2]);
177+
expect(localizedText2).toBe('2 pere');
178+
// fallback to English
179+
const localizedText1 = loc('pear', 'login', [1]);
180+
expect(localizedText1).toBe('one pear');
181+
});
182+
183+
// todo: unload lang
184+
185+
// todo: L10N_ERROR
77186
});
78187
});

0 commit comments

Comments
 (0)