UNPKG

ez-localize

Version:

Super-simple localization of strings in a Node/Browserify application

195 lines (162 loc) 5.67 kB
import { Locale, LocalizedString, LocalizerData, LocalizeString } from "." /** * Localizer is a function that sets up global variable "T" which is * used to translate strings. Also sets up Handlebars helper with same name * Function "T" maps to Localizer "localizeString" function * Helper "T" maps to Localizer "localizeString" function */ export default class Localizer { data: LocalizerData /** Current locale */ locale: string englishMap: { [english: string]: LocalizedString } T: LocalizeString /** Locale defaults to "en" */ constructor(data: LocalizerData, locale?: string) { this.data = data this.locale = locale || "en" // Index strings by English if data present this.englishMap = {} if (data != null) { for (let str of this.data.strings) { this.englishMap[str.en] = str } } const localizer = this Object.defineProperty(this.localizeString, "locale", { get() { return localizer.locale }}) Object.defineProperty(this.localizeString, "localizer", { get() { return localizer }}) this.T = this.localizeString as LocalizeString } /** Set the current locale */ setLocale(locale: string): void { this.locale = locale } getLocales(): Locale[] { return this.data.locales } /** * Localize a single string of the form "some text {0} more text {1} etc", replacing the * parts with the arguments. * * Can also replace where the first parameter is an array for ES6 tagged templates. * * Can also localize to a specified locale with LocalizationRequest. */ localizeString = (str: TemplateStringsArray | LocalizedString | LocalizationRequest | string | null | undefined, ...args: any[]): string | null => { // Null is just pass-through if (str == null) { return str ?? null } // Handle localization request if (typeof str === "object" && "text" in str) { return this.localizeStringRequest(str as LocalizationRequest) } // Handle localized string if (typeof str === "object" && !Array.isArray(str) && (str as LocalizedString)._base) { // Get localized string const locStr = (str as LocalizedString)[this.locale] || (str as LocalizedString).en return this.localizeString(locStr, ...args) } // True if object passed in as arg (react style) let hasObject = args.some(arg => arg && typeof arg === "object") // Handle ES6-style if (Array.isArray(str)) { // Change to format of standard localizer string let newStr = str[0] for (let i = 1 ; i < str.length ; i++) { newStr += "{" + (i - 1) + "}" newStr += str[i] } str = newStr } if (!hasObject) { return this.localizeStringRequest({ locale: this.locale, text: str as string, args }) } else { // Find string, falling back to English let locstr const item = this.englishMap[str as string] if (item && item[this.locale]) { locstr = item[this.locale] } else { locstr = str as string } // Split and do react-style replacement where string is made into array const parts = locstr.split(/(\{\d+\})/) const output = [] for (let part of parts) { if (part.match(/^\{\d+\}$/)) { output.push(args[parseInt(part.substr(1, part.length - 2))]) } else { output.push(part) } } return (output as unknown) as string } } /** * Localizes a plain string without React-style interpretation. Needed for handlebars as it passes extra arguments */ localizePlainString = (str: any, ...args: any[]) => { return this.localizeStringRequest({ locale: this.locale, text: str, args }) } /** * Localizes a string based on a localization request. */ localizeStringRequest(request: LocalizationRequest): string | null { let { locale, text, args } = request if (text == null) { return null } let textStr: string if (typeof text === "object" && (text as LocalizedString)._base) { // Get localized string const localizedStr = (text as LocalizedString)[locale ?? this.locale] ?? (text as LocalizedString).en if (localizedStr == null) { return null } textStr = localizedStr } else { textStr = text as string } // Find string, falling back to English let locstr: string const item = this.englishMap[textStr] if (item && item[locale ?? this.locale]) { locstr = item[locale ?? this.locale] } else { locstr = textStr } // Strip context if present locstr = locstr.split("|")[0] // Fill in arguments if (args) { for (let i = 0; i < args.length ; i++) { locstr = locstr.replace("{" + i + "}", args[i]) } } return locstr } /** Determines if a string is localized */ isLocalized(str: string): boolean { return str != null && this.englishMap[str] != null && this.englishMap[str][this.locale] != null } // Makes this localizer global. handlebars is instance to register // helper on, null for none makeGlobal(handlebars: any) { (global as any).T = this.T; if (handlebars != null) { handlebars.registerHelper("T", this.localizePlainString) } } } /** * Localization request */ export interface LocalizationRequest { /** Locale to localize to. e.g. "en" or "fr". Default is current locale. */ locale?: string /** Text to localize in format of "some text {0} more text {1} etc" */ text: string | LocalizedString | null | undefined /** Arguments to substitute into the localized string */ args?: any[] }