i18n-js
Version:
A small library to provide I18n on JavaScript.
159 lines (145 loc) • 4.21 kB
text/typescript
import { Dict, MissingTranslationStrategy, Scope } from "./typing";
import { getFullScope, inferType } from "./helpers";
import { I18n } from "./I18n";
/**
* Generate a human readable version of the scope as the missing translation.
* To use it, you have to set `i18n.missingBehavior` to `"guess"`.
*
* @type {MissingTranslationStrategy}
*
* @param {I18n} i18n The I18n instance.
*
* @param {Scope} scope The translation scope.
*
* @returns {string} The missing translation string.
*/
export const guessStrategy: MissingTranslationStrategy = function (
i18n,
scope,
) {
if (scope instanceof Array) {
scope = scope.join(i18n.defaultSeparator);
}
// Get only the last portion of the scope.
const message = scope.split(i18n.defaultSeparator).slice(-1)[0];
// Replace underscore with space and camelcase with space and
// lowercase letter.
return (
i18n.missingTranslationPrefix +
message
.replace("_", " ")
.replace(
/([a-z])([A-Z])/g,
(_match: string, p1: string, p2: string) => `${p1} ${p2.toLowerCase()}`,
)
);
};
/**
* Generate the missing translation message, which includes the full scope.
* To use it, you have to set `i18n.missingBehavior` to `"message"`.
* This is the default behavior.
*
* @type {MissingTranslationStrategy}
*
* @param {I18n} i18n The I18n instance.
*
* @param {Scope} scope The translation scope.
*
* @param {Dict} options The translations' options.
*
* @returns {string} The missing translation string.
*/
export const messageStrategy: MissingTranslationStrategy = (
i18n,
scope,
options,
) => {
const fullScope = getFullScope(i18n, scope, options);
const locale = "locale" in options ? options.locale : i18n.locale;
const localeType = inferType(locale);
const fullScopeWithLocale = [
localeType == "string" ? locale : localeType,
fullScope,
].join(i18n.defaultSeparator);
return `[missing "${fullScopeWithLocale}" translation]`;
};
/**
* Throw an error whenever a translation cannot be found. The message will
* includes the full scope.
* To use it, you have to set `i18n.missingBehavior` to `"error"`.
*
* @type {MissingTranslationStrategy}
*
* @param {I18n} i18n The I18n instance.
*
* @param {Scope} scope The translation scope.
*
* @param {Dict} options The translations' options.
*
* @returns {void}
*/
export const errorStrategy: MissingTranslationStrategy = (
i18n,
scope,
options,
) => {
const fullScope = getFullScope(i18n, scope, options);
const fullScopeWithLocale = [i18n.locale, fullScope].join(
i18n.defaultSeparator,
);
throw new Error(`Missing translation: ${fullScopeWithLocale}`);
};
export class MissingTranslation {
private i18n: I18n;
private registry: Dict;
constructor(i18n: I18n) {
this.i18n = i18n;
this.registry = {};
this.register("guess", guessStrategy);
this.register("message", messageStrategy);
this.register("error", errorStrategy);
}
/**
* Registers a new missing translation strategy. This is how messages are
* defined when a translation cannot be found.
*
* The follow example registers a strategy that always return the phrase
* "Oops! Missing translation.".
*
* @example
* ```js
* i18n.missingTranslation.register(
* "oops",
* (i18n, scope, options) => "Oops! Missing translation."
* );
*
* i18n.missingBehavior = "oops";
* ```
*
* @param {string} name The strategy name.
*
* @param {MissingTranslationStrategy} strategy A function that returns a
* string the result of a missing translation scope.
*
* @returns {void}
*/
public register(name: string, strategy: MissingTranslationStrategy): void {
this.registry[name] = strategy;
}
/**
* Return a missing translation message for the given parameters.
*
* @param {Scope} scope The translations' scope.
*
* @param {Dict} options The translations' options.
*
* @returns {string} The missing translation.
*/
public get(scope: Scope, options: Dict): string {
return this.registry[options.missingBehavior ?? this.i18n.missingBehavior](
this.i18n,
scope,
options,
);
}
}