/
I18n.ts
59 lines (47 loc) · 1.89 KB
/
I18n.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import {get} from '../get';
import {merge} from '../merge';
const REPLACE_REGEX = /{([^}]*)}/g;
interface TranslationDictionary {
[key: string]: string | TranslationDictionary;
}
export class I18n {
private translation: TranslationDictionary = {};
/**
* @param translation A locale object or array of locale objects that overrides default translations. If specifying an array then your desired language dictionary should come first, followed by your fallback language dictionaries
*/
constructor(translation: TranslationDictionary | TranslationDictionary[]) {
// slice the array to make a shallow copy of it, so we don't accidentally
// modify the original translation array
this.translation = Array.isArray(translation)
? merge(...translation.slice().reverse())
: translation;
}
translate(
id: string,
replacements?: {[key: string]: string | number},
): string {
const text: string = get(this.translation, id, '');
if (!text) {
return '';
}
if (replacements) {
return text.replace(REPLACE_REGEX, (match: string) => {
const replacement: string = match.substring(1, match.length - 1)!;
if (replacements[replacement] === undefined) {
const replacementData = JSON.stringify(replacements);
throw new Error(
`Error in translation for key '${id}'. No replacement found for key '${replacement}'. The following replacements were passed: '${replacementData}'`,
);
}
// This could be a string or a number, but JS doesn't mind which it gets
// and can handle that cast internally. So let it, to save us calling
// toString() on what's already a string in 90% of cases.
return replacements[replacement] as string;
});
}
return text;
}
translationKeyExists(path: string): boolean {
return Boolean(get(this.translation, path));
}
}