|
10 | 10 | * See the License for the specific language governing permissions and limitations under the License.
|
11 | 11 | */
|
12 | 12 |
|
13 |
| -jest.unmock('./locUtil'); |
| 13 | +import type { loc as origLoc } from './locUtil'; |
| 14 | +import * as OrigLanguageUtils from './languageUtils'; |
14 | 15 |
|
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'); |
41 | 17 |
|
42 |
| -// eslint-disable-next-line import/first |
43 |
| -import { loc } from './locUtil'; |
| 18 | +let loc: typeof origLoc; |
| 19 | +let languageUtils: typeof OrigLanguageUtils; |
44 | 20 |
|
45 | 21 | 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 | + }; |
50 | 29 |
|
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 | + })); |
55 | 52 |
|
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 | + }); |
60 | 55 |
|
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 | + }); |
65 | 75 |
|
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 |
74 | 110 | },
|
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 |
77 | 186 | });
|
78 | 187 | });
|
0 commit comments