227 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
/*
 | 
						|
Copyright 2022 The Matrix.org Foundation C.I.C.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
import React from "react";
 | 
						|
 | 
						|
import {
 | 
						|
    _t,
 | 
						|
    _tDom,
 | 
						|
    TranslatedString,
 | 
						|
    setLanguage,
 | 
						|
    setMissingEntryGenerator,
 | 
						|
    substitute,
 | 
						|
} from "../../src/languageHandler";
 | 
						|
import { stubClient } from "../test-utils";
 | 
						|
 | 
						|
describe("languageHandler", function () {
 | 
						|
    // See setupLanguage.ts for how we are stubbing out translations to provide fixture data for these tests
 | 
						|
    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> | undefined, TranslatedString];
 | 
						|
    const testCasesEn: TestCase[] = [
 | 
						|
        // description of the test case, translationString, variables, tags, expected result
 | 
						|
        ["translates a basic string", basicString, {}, undefined, "Rooms"],
 | 
						|
        ["handles plurals when count is 0", plurals, { count: 0 }, undefined, "and 0 others..."],
 | 
						|
        ["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: string) => `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>,
 | 
						|
        ],
 | 
						|
    ];
 | 
						|
 | 
						|
    let oldNodeEnv: string | undefined;
 | 
						|
    beforeAll(() => {
 | 
						|
        oldNodeEnv = process.env.NODE_ENV;
 | 
						|
        process.env.NODE_ENV = "test";
 | 
						|
    });
 | 
						|
 | 
						|
    afterAll(() => {
 | 
						|
        process.env.NODE_ENV = oldNodeEnv;
 | 
						|
    });
 | 
						|
 | 
						|
    describe("when translations exist in language", () => {
 | 
						|
        beforeEach(function () {
 | 
						|
            stubClient();
 | 
						|
 | 
						|
            setLanguage("en");
 | 
						|
            setMissingEntryGenerator((key) => key.split("|", 2)[1]);
 | 
						|
        });
 | 
						|
 | 
						|
        it("translates a string to german", async () => {
 | 
						|
            await setLanguage("de");
 | 
						|
            const translated = _t(basicString);
 | 
						|
            expect(translated).toBe("Räume");
 | 
						|
        });
 | 
						|
 | 
						|
        it.each(testCasesEn)("%s", (_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("for a non-en language", () => {
 | 
						|
        beforeEach(() => {
 | 
						|
            stubClient();
 | 
						|
            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: string) {
 | 
						|
                return "missing translation: " + key;
 | 
						|
            };
 | 
						|
            setMissingEntryGenerator(counterpartDefaultMissingEntryGen);
 | 
						|
        });
 | 
						|
 | 
						|
        // mocked lv has only `"Uploading %(filename)s and %(count)s others|one"`
 | 
						|
        const lvExistingPlural = "Uploading %(filename)s and %(count)s others";
 | 
						|
        const lvNonExistingPlural = "%(spaceName)s and %(count)s others";
 | 
						|
 | 
						|
        describe("pluralization", () => {
 | 
						|
            const pluralCases = [
 | 
						|
                [
 | 
						|
                    "falls back when plural string exists but not for for count",
 | 
						|
                    lvExistingPlural,
 | 
						|
                    { count: 2, filename: "test.txt" },
 | 
						|
                    undefined,
 | 
						|
                    "Uploading test.txt and 2 others",
 | 
						|
                ],
 | 
						|
                [
 | 
						|
                    "falls back when plural string does not exists at all",
 | 
						|
                    lvNonExistingPlural,
 | 
						|
                    { count: 2, spaceName: "test" },
 | 
						|
                    undefined,
 | 
						|
                    "test and 2 others",
 | 
						|
                ],
 | 
						|
            ] as TestCase[];
 | 
						|
 | 
						|
            describe("_t", () => {
 | 
						|
                it("translated correctly when plural string exists for count", () => {
 | 
						|
                    expect(_t(lvExistingPlural, { count: 1, filename: "test.txt" })).toEqual(
 | 
						|
                        "Качване на test.txt и 1 друг",
 | 
						|
                    );
 | 
						|
                });
 | 
						|
                it.each(pluralCases)("%s", (_d, translationString, variables, tags, result) => {
 | 
						|
                    expect(_t(translationString, variables, tags!)).toEqual(result);
 | 
						|
                });
 | 
						|
            });
 | 
						|
 | 
						|
            describe("_tDom()", () => {
 | 
						|
                it("translated correctly when plural string exists for count", () => {
 | 
						|
                    expect(_tDom(lvExistingPlural, { count: 1, filename: "test.txt" })).toEqual(
 | 
						|
                        "Качване на test.txt и 1 друг",
 | 
						|
                    );
 | 
						|
                });
 | 
						|
                it.each(pluralCases)(
 | 
						|
                    "%s and translates with fallback locale, attributes fallback locale",
 | 
						|
                    (_d, translationString, variables, tags, result) => {
 | 
						|
                        expect(_tDom(translationString, variables, tags!)).toEqual(<span lang="en">{result}</span>);
 | 
						|
                    },
 | 
						|
                );
 | 
						|
            });
 | 
						|
        });
 | 
						|
 | 
						|
        describe("when a translation string does not exist in active language", () => {
 | 
						|
            describe("_t", () => {
 | 
						|
                it.each(testCasesEn)(
 | 
						|
                    "%s and translates with fallback locale",
 | 
						|
                    (_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",
 | 
						|
                    (_d, translationString, variables, tags, result) => {
 | 
						|
                        expect(_tDom(translationString, variables, tags!)).toEqual(<span lang="en">{result}</span>);
 | 
						|
                    },
 | 
						|
                );
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("when languages dont load", () => {
 | 
						|
        it("_t", () => {
 | 
						|
            const STRING_NOT_IN_THE_DICTIONARY = "a string that isn't in the translations dictionary";
 | 
						|
            expect(_t(STRING_NOT_IN_THE_DICTIONARY, {})).toEqual(STRING_NOT_IN_THE_DICTIONARY);
 | 
						|
        });
 | 
						|
 | 
						|
        it("_tDom", () => {
 | 
						|
            const STRING_NOT_IN_THE_DICTIONARY = "a string that isn't in the translations dictionary";
 | 
						|
            expect(_tDom(STRING_NOT_IN_THE_DICTIONARY, {})).toEqual(
 | 
						|
                <span lang="en">{STRING_NOT_IN_THE_DICTIONARY}</span>,
 | 
						|
            );
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 |