@owja/i18n
Version:
lightweight internationalization library for javascript
406 lines (350 loc) • 16.6 kB
text/typescript
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");
});
});
});