Attribute fallback i18n strings with lang attribute (#7323)
* add lang attribute to fallback translations Signed-off-by: Kerry Archibald <kerrya@element.io> * readability improvement Signed-off-by: Kerry Archibald <kerrya@element.io> * split _t and _tDom Signed-off-by: Kerry <kerry@Kerrys-MBP.fritz.box> * use tDom in HomePage Signed-off-by: Kerry Archibald <kerrya@element.io> * lint Signed-off-by: Kerry Archibald <kerrya@element.io> * bump matrix-web-i18n Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
ea7ac453bc
commit
7f13a1b40a
7 changed files with 236 additions and 132 deletions
|
@ -1,79 +0,0 @@
|
|||
import * as languageHandler from '../../src/languageHandler';
|
||||
|
||||
const React = require('react');
|
||||
const expect = require('expect');
|
||||
|
||||
const testUtils = require('../test-utils');
|
||||
|
||||
describe('languageHandler', function() {
|
||||
beforeEach(function(done) {
|
||||
testUtils.stubClient();
|
||||
|
||||
languageHandler.setLanguage('en').then(done);
|
||||
languageHandler.setMissingEntryGenerator(key => key.split("|", 2)[1]);
|
||||
});
|
||||
|
||||
it('translates a string to german', function(done) {
|
||||
languageHandler.setLanguage('de').then(function() {
|
||||
const translated = languageHandler._t('Rooms');
|
||||
expect(translated).toBe('Räume');
|
||||
}).then(done);
|
||||
});
|
||||
|
||||
it('handles plurals', function() {
|
||||
const text = 'and %(count)s others...';
|
||||
expect(languageHandler._t(text, { count: 1 })).toBe('and one other...');
|
||||
expect(languageHandler._t(text, { count: 2 })).toBe('and 2 others...');
|
||||
});
|
||||
|
||||
it('handles simple variable subsitutions', function() {
|
||||
const text = 'You are now ignoring %(userId)s';
|
||||
expect(languageHandler._t(text, { userId: 'foo' })).toBe('You are now ignoring foo');
|
||||
});
|
||||
|
||||
it('handles simple tag substitution', function() {
|
||||
const text = 'Press <StartChatButton> to start a chat with someone';
|
||||
expect(languageHandler._t(text, {}, { 'StartChatButton': () => 'foo' }))
|
||||
.toBe('Press foo to start a chat with someone');
|
||||
});
|
||||
|
||||
it('handles text in tags', function() {
|
||||
const text = '<a>Click here</a> to join the discussion!';
|
||||
expect(languageHandler._t(text, {}, { 'a': (sub) => `x${sub}x` }))
|
||||
.toBe('xClick herex to join the discussion!');
|
||||
});
|
||||
|
||||
it('variable substitution with React component', function() {
|
||||
const text = 'You are now ignoring %(userId)s';
|
||||
expect(languageHandler._t(text, { userId: () => <i>foo</i> }))
|
||||
.toEqual((<span>You are now ignoring <i>foo</i></span>));
|
||||
});
|
||||
|
||||
it('variable substitution with plain React component', function() {
|
||||
const text = 'You are now ignoring %(userId)s';
|
||||
expect(languageHandler._t(text, { userId: <i>foo</i> }))
|
||||
.toEqual((<span>You are now ignoring <i>foo</i></span>));
|
||||
});
|
||||
|
||||
it('tag substitution with React component', function() {
|
||||
const text = 'Press <StartChatButton> to start a chat with someone';
|
||||
expect(languageHandler._t(text, {}, { 'StartChatButton': () => <i>foo</i> }))
|
||||
.toEqual(<span>Press <i>foo</i> to start a chat with someone</span>);
|
||||
});
|
||||
|
||||
it('replacements in the wrong order', function() {
|
||||
const text = '%(var1)s %(var2)s';
|
||||
expect(languageHandler._t(text, { var2: 'val2', var1: 'val1' })).toBe('val1 val2');
|
||||
});
|
||||
|
||||
it('multiple replacements of the same variable', function() {
|
||||
const text = '%(var1)s %(var1)s';
|
||||
expect(languageHandler.substitute(text, { var1: 'val1' })).toBe('val1 val1');
|
||||
});
|
||||
|
||||
it('multiple replacements of the same tag', function() {
|
||||
const text = '<a>Click here</a> to join the discussion! <a>or here</a>';
|
||||
expect(languageHandler.substitute(text, {}, { 'a': (sub) => `x${sub}x` }))
|
||||
.toBe('xClick herex to join the discussion! xor herex');
|
||||
});
|
||||
});
|
144
test/i18n-test/languageHandler-test.tsx
Normal file
144
test/i18n-test/languageHandler-test.tsx
Normal file
|
@ -0,0 +1,144 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
_t,
|
||||
_tDom,
|
||||
TranslatedString,
|
||||
setLanguage,
|
||||
setMissingEntryGenerator,
|
||||
substitute,
|
||||
} from '../../src/languageHandler';
|
||||
import { stubClient } from '../test-utils';
|
||||
|
||||
describe('languageHandler', function() {
|
||||
const basicString = 'Rooms';
|
||||
const selfClosingTagSub = 'Accept <policyLink /> to continue:';
|
||||
const textInTagSub = '<a>Upgrade</a> to your own domain';
|
||||
const plurals = 'and %(count)s others...';
|
||||
const variableSub = 'You are now ignoring %(userId)s';
|
||||
|
||||
type TestCase = [string, string, Record<string, unknown>, Record<string, unknown>, TranslatedString];
|
||||
const testCasesEn: TestCase[] = [
|
||||
['translates a basic string', basicString, {}, undefined, 'Rooms'],
|
||||
[
|
||||
'handles plurals when count is 1',
|
||||
plurals,
|
||||
{ count: 1 },
|
||||
undefined,
|
||||
'and one other...',
|
||||
],
|
||||
[
|
||||
'handles plurals when count is not 1',
|
||||
plurals,
|
||||
{ count: 2 },
|
||||
undefined,
|
||||
'and 2 others...',
|
||||
],
|
||||
[
|
||||
'handles simple variable substitution',
|
||||
variableSub,
|
||||
{ userId: 'foo' },
|
||||
undefined,
|
||||
'You are now ignoring foo',
|
||||
],
|
||||
[
|
||||
'handles simple tag substitution',
|
||||
selfClosingTagSub,
|
||||
{},
|
||||
{ 'policyLink': () => 'foo' },
|
||||
'Accept foo to continue:',
|
||||
],
|
||||
['handles text in tags', textInTagSub, {}, { 'a': (sub) => `x${sub}x` }, 'xUpgradex to your own domain'],
|
||||
[
|
||||
'handles variable substitution with React function component',
|
||||
variableSub,
|
||||
{ userId: () => <i>foo</i> },
|
||||
undefined,
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<span>You are now ignoring <i>foo</i></span>,
|
||||
],
|
||||
[
|
||||
'handles variable substitution with react node',
|
||||
variableSub,
|
||||
{ userId: <i>foo</i> },
|
||||
undefined,
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<span>You are now ignoring <i>foo</i></span>,
|
||||
],
|
||||
[
|
||||
'handles tag substitution with React function component',
|
||||
selfClosingTagSub,
|
||||
{},
|
||||
{ 'policyLink': () => <i>foo</i> },
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<span>Accept <i>foo</i> to continue:</span>,
|
||||
],
|
||||
];
|
||||
|
||||
describe('when translations exist in language', () => {
|
||||
beforeEach(function(done) {
|
||||
stubClient();
|
||||
|
||||
setLanguage('en').then(done);
|
||||
setMissingEntryGenerator(key => key.split("|", 2)[1]);
|
||||
});
|
||||
|
||||
it('translates a string to german', function(done) {
|
||||
setLanguage('de').then(function() {
|
||||
const translated = _t(basicString);
|
||||
expect(translated).toBe('Räume');
|
||||
}).then(done);
|
||||
});
|
||||
|
||||
it.each(testCasesEn)("%s", async (_d, translationString, variables, tags, result) => {
|
||||
expect(_t(translationString, variables, tags)).toEqual(result);
|
||||
});
|
||||
|
||||
it('replacements in the wrong order', function() {
|
||||
const text = '%(var1)s %(var2)s';
|
||||
expect(_t(text, { var2: 'val2', var1: 'val1' })).toBe('val1 val2');
|
||||
});
|
||||
|
||||
it('multiple replacements of the same variable', function() {
|
||||
const text = '%(var1)s %(var1)s';
|
||||
expect(substitute(text, { var1: 'val1' })).toBe('val1 val1');
|
||||
});
|
||||
|
||||
it('multiple replacements of the same tag', function() {
|
||||
const text = '<a>Click here</a> to join the discussion! <a>or here</a>';
|
||||
expect(substitute(text, {}, { 'a': (sub) => `x${sub}x` }))
|
||||
.toBe('xClick herex to join the discussion! xor herex');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a translation string does not exist in active language', () => {
|
||||
beforeEach(async () => {
|
||||
stubClient();
|
||||
await setLanguage('lv');
|
||||
// counterpart doesnt expose any way to restore default config
|
||||
// missingEntryGenerator is mocked in the root setup file
|
||||
// reset to default here
|
||||
const counterpartDefaultMissingEntryGen =
|
||||
function(key) { return 'missing translation: ' + key; };
|
||||
setMissingEntryGenerator(counterpartDefaultMissingEntryGen);
|
||||
});
|
||||
|
||||
describe('_t', () => {
|
||||
it.each(testCasesEn)(
|
||||
"%s and translates with fallback locale",
|
||||
async (_d, translationString, variables, tags, result) => {
|
||||
expect(_t(translationString, variables, tags)).toEqual(result);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('_tDom()', () => {
|
||||
it.each(testCasesEn)(
|
||||
"%s and translates with fallback locale, attributes fallback locale",
|
||||
async (_d, translationString, variables, tags, result) => {
|
||||
expect(_tDom(translationString, variables, tags)).toEqual(<span lang="en">{ result }</span>);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue