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, ); } }