UNPKG

@owja/i18n

Version:

lightweight internationalization library for javascript

406 lines (350 loc) 16.6 kB
import {TranslateOptions, Translator, TranslatorInterface} from "../"; import testResource from "../test/test.json"; import {testFullLocales, testShortLocales} from "../test/locales"; import ISO6391 from "iso-639-1"; describe("Translator", () => { let instance: Translator; beforeEach(() => { instance = new Translator(); }); test("will have default options", () => { expect((instance as any)._options.fallback).toBe("en"); expect((instance as any)._options.default).toBe("en-US"); expect(instance.short()).toBe("en"); expect(instance.long()).toBe("en-US"); }); test("can handle a bad locale", () => { instance = new Translator({ fallback: "xx", default: "xxx", }); expect(instance.short()).toBe("xxx"); expect(instance.long()).toBe("xxx"); expect(instance.region()).toBeUndefined(); }); test("can set options", () => { instance = new Translator({ fallback: "es", default: "it", }); expect((instance as any)._options.fallback).toBe("es"); expect((instance as any)._options.default).toBe("it"); }); test("can set partial options", () => { instance = new Translator({ default: "de", }); expect((instance as any)._options.fallback).toBe("en"); expect((instance as any)._options.default).toBe("de"); }); test("can change the language", () => { instance.locale("de"); expect(instance.short()).toBe("de"); expect(instance.long()).toBe("de-DE"); }); test('can set "all" short locales', () => { const ignoring = ["prs", "qut", "quz"]; for (const locale of testShortLocales) { if (ignoring.includes(locale)) continue; instance.locale(locale); expect(instance.short()).toBe(locale); } }); test('can set "all" full locales', () => { for (const locale of testFullLocales) { instance.locale(locale); expect(instance.long()).toBe(locale); } }); test('can set "all" codes delivered by the ISO-639-1 package', () => { const ignoring = ["bh", "ie", "oj", "pi", "tw"]; for (const locale of ISO6391.getAllCodes()) { if (ignoring.includes(locale)) continue; instance.locale(locale); expect(instance.short()).toBe(locale); } }); test("can change the language by passing a Intl.Locale object", () => { instance.locale(new Intl.Locale("de")); expect(instance.short()).toBe("de"); expect(instance.long()).toBe("de-DE"); }); test("can change the language with deprecated language() method too", () => { instance.language("de"); expect(instance.short()).toBe("de"); expect(instance.long()).toBe("de-DE"); }); test("should not trigger listener on deprecated language change when changing to same language", () => { instance.language("de"); const spy = jest.fn(); instance.listen(spy); instance.language("de"); expect(spy).not.toHaveBeenCalled(); }); test("should trigger listener on language change", () => { const spy = jest.fn(); instance.listen(spy); instance.locale("de"); instance.locale("en-GB"); expect(spy).toHaveBeenCalledTimes(2); expect(instance.short()).toBe("en"); expect(instance.long()).toBe("en-GB"); }); test("should trigger listener only if language is really changed", () => { const spy = jest.fn(); instance.listen(spy); instance.locale("de"); instance.locale("de"); expect(spy).toHaveBeenCalledTimes(1); expect(instance.short()).toBe("de"); }); test("should not trigger listener on language change after unregister", () => { const spy = jest.fn(); const unregister = instance.listen(spy); instance.locale("de"); unregister(); instance.locale("en"); expect(spy).toHaveBeenCalledTimes(1); expect(instance.short()).toBe("en"); }); test("can register a listener only once", () => { const listener = () => undefined; instance.listen(listener); instance.listen(listener); expect((instance as any)._listener).toHaveLength(1); }); test("can unregister a listener only if it is not unregistered before", () => { const listener = () => undefined; const unregister = instance.listen(listener); unregister(); unregister(); expect((instance as any)._listener).toHaveLength(0); }); describe("resources", () => { test("with one dimension can be added", () => { instance.addResource("de", { "my-string-one": "My string number one", myStringTwo: "My string number two", }); expect((instance as any)._resources).toEqual({ "de.my-string-one": "My string number one", "de.myStringTwo": "My string number two", }); }); test("with multi dimensions can be added", () => { instance.addResource("de", { myStringOne: "My string number one", my: { stringTwo: "My string number two", string: { three: "My string number three", }, stringFour: "My string number four", }, myStringFive: "My string number five", }); expect((instance as any)._resources).toEqual({ "de.myStringOne": "My string number one", "de.my.stringTwo": "My string number two", "de.my.string.three": "My string number three", "de.my.stringFour": "My string number four", "de.myStringFive": "My string number five", }); }); test("only with alpha-numeric with optional underscore keys can be added on top level", () => { const bad = [ {"string:one": "one"}, {"string=two": "two"}, {DJJürgen: "DJ Günther"}, {"DJ Gunther": "DJGunther"}, ]; bad.forEach((data) => { expect(() => instance.addResource("de", data as any)).toThrow( `only a-Z, 0-9, minus sign and underscore allowed: "${Object.keys(data)[0]}"`, ); }); }); test("with underscore can be added on top level", () => { expect(() => instance.addResource("de", {string_three: "three"})).not.toThrow(); }); test("only with alpha-numeric keys (and minus sign) can be added if they have children", () => { const bad = [{string_one: {two: "two"}}, {"string three": {four: "four"}}, {string_five: {six: "six"}}]; bad.forEach((data) => { expect(() => instance.addResource("de", data as any)).toThrow( `only a-Z, minus sign and 0-9 allowed: "${Object.keys(data)[0]}"`, ); }); }); test("only with data of type string or object can be added", () => { const bad = [{one: undefined}, {two: null}, {three: 100}, {four: 0}, {five: {six: Symbol.for("sym")}}]; bad.forEach((data) => { instance.addResource("de", data as any); expect((instance as any)._resources).toEqual({}); }); }); test("only valid language tags can be added", () => { const bad = ["d:e", "e.n", "f-r", " se", "se "]; bad.forEach((data) => { expect(() => instance.addResource(data, {x: "x"})).toThrow(`Incorrect locale information provided`); }); }); }); describe("with loaded resources", () => { beforeEach(() => { instance.addResource("de", testResource.de); instance.addResource("de-CH", testResource["de-CH"]); instance.addResource("en", testResource.en); instance.addResource("ar", testResource.ar); }); test("can translate", () => { instance.locale("de"); expect(instance.t("item")).toBe("Ding"); instance.locale("en"); expect(instance.t("item")).toBe("Item"); }); test("can not translate missing values", () => { expect(instance.t("notExisting")).toBe("notExisting"); expect(instance.t("notExisting:item")).toBe("notExisting:item"); }); test("can translate with count option", () => { instance.locale("de"); expect(instance.t("item", {count: -1})).toBe("-1 Dings"); expect(instance.t("item", {count: 0})).toBe("0 Dingens"); expect(instance.t("item", {count: 1})).toBe("1 Dings"); expect(instance.t("item", {count: 10})).toBe("10 Dingens"); instance.locale("en"); expect(instance.t("item", {count: -1})).toBe("-1 item"); expect(instance.t("item", {count: 0})).toBe("Zero items"); expect(instance.t("item", {count: 1})).toBe("1 item"); expect(instance.t("item", {count: 2})).toBe("2 items"); expect(instance.t("item", {count: 10})).toBe("10 items"); }); test("can translate with count option in fallback language", () => { instance.locale("ar"); expect(instance.t("thing", {count: 2})).toBe("2 things"); //fallback en expect(instance.t("item", {count: 2})).toBe("2 items"); }); test("can translate with replace option", () => { instance.locale("de"); expect(instance.t("hello", {replace: {what: "Welt"}})).toBe("Hallo Welt"); expect(instance.t("hello2", {replace: {what: "Welt"}})).toBe("Hallo Welt Welt"); instance.locale("en"); expect(instance.t("hello", {replace: {what: "World"}})).toBe("Hello World"); expect(instance.t("hello2", {replace: {what: "World"}})).toBe("Hello World World"); }); test("can translate without the right replace option and will leave the pattern", () => { instance.locale("de"); expect(instance.t("hello", {replace: {nottherightone: "Welt"}})).toBe("Hallo {{what}}"); }); test("will simply replace dates with a string", () => { instance.locale("de"); const translated = instance.t("hello", {replace: {what: new Date(2020, 1, 2)}}); expect(translated).not.toContain("{{what}}"); expect(translated).toContain("2020"); expect(translated).toContain("Hallo "); }); test("can translate with context option", () => { instance.locale("de"); expect(instance.t("ownerCar", {context: "male"})).toBe("Sein Auto"); expect(instance.t("ownerCar", {context: "female"})).toBe("Ihr Auto"); instance.locale("en"); expect(instance.t("ownerCar", {context: "male"})).toBe("his car"); expect(instance.t("ownerCar", {context: "female"})).toBe("her car"); }); test("can translate with context and count option", () => { instance.locale("en"); expect(instance.t("ownerCar", {context: "male", count: 1})).toBe("his car"); expect(instance.t("ownerCar", {context: "female", count: 1})).toBe("her car"); expect(instance.t("ownerCar", {context: "male", count: 2})).toBe("his 2 cars"); expect(instance.t("ownerCar", {context: "female", count: 2})).toBe("her 2 cars"); }); test("can translate with context and count option with fallback", () => { instance.locale("de"); expect(instance.t("ownerCar", {count: 1})).toBe("1 Auto"); expect(instance.t("ownerCar", {count: 2})).toBe("2 Autos"); expect(instance.t("ownerCar", {context: "male", count: 2})).toBe("Sein Auto"); expect(instance.t("ownerCar", {context: "female", count: 2})).toBe("Ihr Auto"); }); test("can translate from sub resources", () => { instance.locale("de"); expect(instance.t("sub.something")).toBe("etwas"); expect(instance.t("sub.else")).toBe("anderes"); instance.locale("en"); expect(instance.t("sub.something")).toBe("something"); expect(instance.t("sub.else")).toBe("else"); }); test("can translate with to 2 level fallback", () => { instance.locale("de-DE"); expect(instance.t("item")).toBe("Ding"); instance.locale("de-CH"); expect(instance.t("item")).toBe("Ein anderes Dings"); expect(instance.t("de-only")).toBe("only in german"); expect(instance.t("en-only")).toBe("only in english"); }); test("can translate from sub resources with language tag fallback", () => { instance.locale("de-CH"); expect(instance.t("sub.something")).toBe("etwas"); expect(instance.t("sub.else")).toBe("anderes"); instance.locale("en-FR"); expect(instance.t("sub.something")).toBe("something"); expect(instance.t("sub.else")).toBe("else"); }); }); describe("Plugins", () => { const createPlugin = (result = "test") => jest.fn<string | undefined, [string, Partial<TranslateOptions>, string, TranslatorInterface]>(() => result); beforeEach(() => { instance.addResource("en", {t: "[[test]]"}); }); test("can add and find plugin for long locale", () => { const p = createPlugin(); instance.addPlugin(p, instance.long()); expect(instance.t("t")).toBe("test"); }); test("can add and find plugin for short locale", () => { const p = createPlugin(); instance.addPlugin(p, instance.short()); expect(instance.t("t")).toBe("test"); }); test("can add and find plugin for global", () => { const p = createPlugin(); instance.addPlugin(p); expect(instance.t("t")).toBe("test"); }); test("plugin resolution order: long > short > global", () => { const long = createPlugin("long"); const short = createPlugin("short"); const global = createPlugin("global"); instance.addPlugin(long, instance.long()); instance.addPlugin(short, instance.short()); instance.addPlugin(global); expect(instance.t("t")).toBe("global"); expect(long).toHaveBeenCalledWith("[[test]]", expect.anything(), expect.anything(), expect.anything()); expect(short).toHaveBeenCalledWith("long", expect.anything(), expect.anything(), expect.anything()); expect(global).toHaveBeenCalledWith("short", expect.anything(), expect.anything(), expect.anything()); }); test("plugin is called with used (long)locale or fallback", () => { instance.addResource("de", {de: "[[test]]"}); instance.addResource("de-DE", {dede: "[[test]]"}); const p = createPlugin(); instance.addPlugin(p); instance.locale("de-DE"); expect(instance.t("dede")).toBe("test"); expect(p).toHaveBeenCalledWith("[[test]]", expect.anything(), "de-DE", expect.anything()); expect(instance.t("de")).toBe("test"); expect(p).toHaveBeenCalledWith("[[test]]", expect.anything(), "de-DE", expect.anything()); expect(instance.t("t")).toBe("test"); expect(p).toHaveBeenCalledWith("[[test]]", expect.anything(), "en", expect.anything()); }); test("plugin may return undefined for not changing anything", () => { instance.addPlugin(createPlugin().mockImplementation(() => undefined)); expect(instance.t("t")).toBe("[[test]]"); }); test("can add multiple plugins for same language", () => { instance.addPlugin(jest.fn((t: string) => t.replace("[[te", "foo"))); instance.addPlugin(jest.fn((t: string) => t.replace("st]]", "bar"))); expect(instance.t("t")).toBe("foobar"); }); }); });