UNPKG

4.21 kBPlain TextView Raw
1import { Dict, MissingTranslationStrategy, Scope } from "./typing";
2import { getFullScope, inferType } from "./helpers";
3import { I18n } from "./I18n";
4
5/**
6 * Generate a human readable version of the scope as the missing translation.
7 * To use it, you have to set `i18n.missingBehavior` to `"guess"`.
8 *
9 * @type {MissingTranslationStrategy}
10 *
11 * @param {I18n} i18n The I18n instance.
12 *
13 * @param {Scope} scope The translation scope.
14 *
15 * @returns {string} The missing translation string.
16 */
17export const guessStrategy: MissingTranslationStrategy = function (
18 i18n,
19 scope,
20) {
21 if (scope instanceof Array) {
22 scope = scope.join(i18n.defaultSeparator);
23 }
24
25 // Get only the last portion of the scope.
26 const message = scope.split(i18n.defaultSeparator).slice(-1)[0];
27
28 // Replace underscore with space and camelcase with space and
29 // lowercase letter.
30 return (
31 i18n.missingTranslationPrefix +
32 message
33 .replace("_", " ")
34 .replace(
35 /([a-z])([A-Z])/g,
36 (_match: string, p1: string, p2: string) => `${p1} ${p2.toLowerCase()}`,
37 )
38 );
39};
40
41/**
42 * Generate the missing translation message, which includes the full scope.
43 * To use it, you have to set `i18n.missingBehavior` to `"message"`.
44 * This is the default behavior.
45 *
46 * @type {MissingTranslationStrategy}
47 *
48 * @param {I18n} i18n The I18n instance.
49 *
50 * @param {Scope} scope The translation scope.
51 *
52 * @param {Dict} options The translations' options.
53 *
54 * @returns {string} The missing translation string.
55 */
56export const messageStrategy: MissingTranslationStrategy = (
57 i18n,
58 scope,
59 options,
60) => {
61 const fullScope = getFullScope(i18n, scope, options);
62 const locale = "locale" in options ? options.locale : i18n.locale;
63 const localeType = inferType(locale);
64
65 const fullScopeWithLocale = [
66 localeType == "string" ? locale : localeType,
67 fullScope,
68 ].join(i18n.defaultSeparator);
69
70 return `[missing "${fullScopeWithLocale}" translation]`;
71};
72
73/**
74 * Throw an error whenever a translation cannot be found. The message will
75 * includes the full scope.
76 * To use it, you have to set `i18n.missingBehavior` to `"error"`.
77 *
78 * @type {MissingTranslationStrategy}
79 *
80 * @param {I18n} i18n The I18n instance.
81 *
82 * @param {Scope} scope The translation scope.
83 *
84 * @param {Dict} options The translations' options.
85 *
86 * @returns {void}
87 */
88export const errorStrategy: MissingTranslationStrategy = (
89 i18n,
90 scope,
91 options,
92) => {
93 const fullScope = getFullScope(i18n, scope, options);
94 const fullScopeWithLocale = [i18n.locale, fullScope].join(
95 i18n.defaultSeparator,
96 );
97
98 throw new Error(`Missing translation: ${fullScopeWithLocale}`);
99};
100
101export class MissingTranslation {
102 private i18n: I18n;
103 private registry: Dict;
104
105 constructor(i18n: I18n) {
106 this.i18n = i18n;
107 this.registry = {};
108
109 this.register("guess", guessStrategy);
110 this.register("message", messageStrategy);
111 this.register("error", errorStrategy);
112 }
113
114 /**
115 * Registers a new missing translation strategy. This is how messages are
116 * defined when a translation cannot be found.
117 *
118 * The follow example registers a strategy that always return the phrase
119 * "Oops! Missing translation.".
120 *
121 * @example
122 * ```js
123 * i18n.missingTranslation.register(
124 * "oops",
125 * (i18n, scope, options) => "Oops! Missing translation."
126 * );
127 *
128 * i18n.missingBehavior = "oops";
129 * ```
130 *
131 * @param {string} name The strategy name.
132 *
133 * @param {MissingTranslationStrategy} strategy A function that returns a
134 * string the result of a missing translation scope.
135 *
136 * @returns {void}
137 */
138 public register(name: string, strategy: MissingTranslationStrategy): void {
139 this.registry[name] = strategy;
140 }
141
142 /**
143 * Return a missing translation message for the given parameters.
144 *
145 * @param {Scope} scope The translations' scope.
146 *
147 * @param {Dict} options The translations' options.
148 *
149 * @returns {string} The missing translation.
150 */
151 public get(scope: Scope, options: Dict): string {
152 return this.registry[options.missingBehavior ?? this.i18n.missingBehavior](
153 this.i18n,
154 scope,
155 options,
156 );
157 }
158}