UNPKG

i18n-pro

Version:

An out-of-the-box, lightweight JavaScript i18n auto-translation solution

525 lines (512 loc) 15.3 kB
/* * i18n-pro * v3.0.0 * 2025/9/6 17:04:13 * Copyright (c) 2022-present Eyelly Wu <https://github.com/eyelly-wu> */ ;(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : ((global = typeof globalThis !== 'undefined' ? globalThis : global || self), factory((global.i18nPro = {}))) })(this, function (exports) { 'use strict' /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i] for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p] } return t } return __assign.apply(this, arguments) } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value) }) } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)) } catch (e) { reject(e) } } function rejected(value) { try { step(generator['throw'](value)) } catch (e) { reject(e) } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected) } step((generator = generator.apply(thisArg, _arguments || [])).next()) }) } function __generator(thisArg, body) { var _ = { label: 0, sent: function () { if (t[0] & 1) throw t[1] return t[1] }, trys: [], ops: [], }, f, y, t, g = Object.create( (typeof Iterator === 'function' ? Iterator : Object).prototype, ) return ( (g.next = verb(0)), (g['throw'] = verb(1)), (g['return'] = verb(2)), typeof Symbol === 'function' && (g[Symbol.iterator] = function () { return this }), g ) function verb(n) { return function (v) { return step([n, v]) } } function step(op) { if (f) throw new TypeError('Generator is already executing.') while ((g && ((g = 0), op[0] && (_ = 0)), _)) try { if ( ((f = 1), y && (t = op[0] & 2 ? y['return'] : op[0] ? y['throw'] || ((t = y['return']) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) ) return t if (((y = 0), t)) op = [op[0] & 2, t.value] switch (op[0]) { case 0: case 1: t = op break case 4: _.label++ return { value: op[1], done: false } case 5: _.label++ y = op[1] op = [0] continue case 7: op = _.ops.pop() _.trys.pop() continue default: if ( !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2) ) { _ = 0 continue } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1] break } if (op[0] === 6 && _.label < t[1]) { _.label = t[1] t = op break } if (t && _.label < t[2]) { _.label = t[2] _.ops.push(op) break } if (t[2]) _.ops.pop() _.trys.pop() continue } op = body.call(thisArg, _) } catch (e) { op = [6, e] y = 0 } finally { f = t = 0 } if (op[0] & 5) throw op[1] return { value: op[0] ? op[1] : void 0, done: true } } } typeof SuppressedError === 'function' ? SuppressedError : function (error, suppressed, message) { var e = new Error(message) return ( (e.name = 'SuppressedError'), (e.error = error), (e.suppressed = suppressed), e ) } /** * 匹配公共格式化文案的正则 */ var commonFormatterRegex = /(\{([ncdt])number\})/ /** * 匹配复数格式化文案的正则 */ var pluralFormatterRegex = /(\{(p)number([^}]+)\})/ /** * 匹配非正确复数格式化文案的正则 */ var invalidPluralFormatterRegex = /(\{(p)number\})/ /** * 内置标记与对应格式化回调函数名的映射关系 */ var tagFormatterNameMap = { n: 'formatNumber', c: 'formatCurrency', d: 'formatDate', t: 'formatTime', p: 'formatPlural', } var state = {} function getCurrentState(namespace) { return state[namespace] || {} } /** * 获取目标正则 * @param regExp 基础正则 * @param index 动态参数的起始下标 * @returns */ function getTargetRegExp(regExp, index) { return new RegExp(regExp.source.replace('number', index + ''), 'i') } /** * 定义 t 函数的属性 * @param t * @param condition * @returns */ function defineTranslateProperties(t, condition) { Object.defineProperties(t, { t: { get: function () { return generateTranslate(condition, true) }, }, /** * 绑定 locale 返回新的 t 方法 * t.t 属性还存在,同时也适用于服务端场景 */ withLocale: { get: function () { return function (locale) { return generateTranslate( __assign(__assign({}, condition), { // 未设置 locale 需要保持之前的状态 locale: locale || condition.locale, }), ) } }, }, }) return t } /** * 生成 t 函数 * @param condition * @param isDotT 是否是 t.t 场景 * @returns */ function generateTranslate(condition, isDotT) { if (isDotT === void 0) { isDotT = false } var t = isDotT ? translateImpl.bind(null, condition) : translateImpl.bind(null, condition, null) if (!isDotT) { defineTranslateProperties(t, condition) } return t } /** * 获取基于格式化回调处理后的文本 * @param props * @returns */ function getTextFromFormatter(props) { var type = props.type, originText = props.originText, matchTagRes = props.matchTagRes, arg = props.arg, textProp = props.text, state = props.state, condition = props.condition var locale_ = state.locale var locale = condition.locale || locale_ var matchTemplate = matchTagRes[1], f = matchTagRes[2], _a = matchTagRes[3], keyword = _a === void 0 ? '' : _a var formatterName = tagFormatterNameMap[f === null || f === void 0 ? void 0 : f.toLowerCase()] var formatter = state[formatterName] var text = textProp if (typeof formatter !== 'function') { console.warn( 'Missing formatter `' .concat(formatterName, '` for `') .concat(matchTemplate, '` in `') .concat(originText, '`.'), ) return text.replace(matchTemplate, ''.concat(arg).concat(keyword)) } if (typeof locale === 'undefined') { console.warn( 'The locale is not configured and may affect the logic in formatter `'.concat( formatterName, '`.', ), ) } try { var content = formatter( __assign( { locale: locale, payload: arg, t: generateTranslate(condition) }, type === 'plural' ? { keyword: keyword.trim(), text: ''.concat(arg).concat(keyword) } : {}, ), ) text = text.replace(matchTemplate, content) } catch (error) { console.error( 'Error in formatter `' .concat(formatterName, '` for `') .concat(matchTemplate, '` in `') .concat(originText, '`:'), error, ) text = text.replace(matchTemplate, ''.concat(arg).concat(keyword)) } return text } /** * translate 函数 API的具体实现 * @param i18nState 当前i18n所有状态 * @param key key * @param text Original text * @param args Dynamic parameter * @returns */ function translateImpl(condition, key, text) { if (key === void 0) { key = null } var args = [] for (var _i = 3; _i < arguments.length; _i++) { args[_i - 3] = arguments[_i] } var i18nState = getCurrentState(condition.namespace) var locale_ = i18nState.locale, langs = i18nState.langs, _a = i18nState.beginIndex, beginIndex = _a === void 0 ? 0 : _a var locale = condition.locale || locale_ var lang = langs === null || langs === void 0 ? void 0 : langs[locale] var originText = text key = key === null ? text : key if (lang === null || lang === void 0 ? void 0 : lang[key]) { text = lang[key] originText = text } args.forEach(function (arg, index) { var currentIndex = beginIndex + index var currentInvalidPluralFormatterRegex = getTargetRegExp( invalidPluralFormatterRegex, currentIndex, ) var invalidPluralMatchTagRes = text.match( currentInvalidPluralFormatterRegex, ) if (invalidPluralMatchTagRes) { console.warn( 'Invalid plural parameter `' .concat(invalidPluralMatchTagRes[1], '` in `') .concat(originText, '`.'), ) return } var currentCommonFormatterRegex = getTargetRegExp( commonFormatterRegex, currentIndex, ) var currentPluralFormatterRegex = getTargetRegExp( pluralFormatterRegex, currentIndex, ) var commonMatchTagRes = text.match(currentCommonFormatterRegex) var pluralMatchTagRes = text.match(currentPluralFormatterRegex) if (!commonMatchTagRes && !pluralMatchTagRes) { text = text.replace('{'.concat(currentIndex, '}'), ''.concat(arg)) return } text = getTextFromFormatter({ type: pluralMatchTagRes ? 'plural' : 'normal', originText: originText, matchTagRes: pluralMatchTagRes || commonMatchTagRes, index: currentIndex, arg: arg, text: text, state: i18nState, condition: condition, }) }) return text } function isObject(object) { return typeof object === 'object' && object !== null } /** * Sets or updates the internationalization state * @param namespace Current namespace * @param state Internationalization state * @returns Updated internationalization state */ function setI18n(namespace, stateProp) { if (stateProp === void 0) { stateProp = {} } return __awaiter(this, void 0, void 0, function () { var currentState, locale, langs, newState, currentLangs, currentLang, loadedLang var _a return __generator(this, function (_b) { switch (_b.label) { case 0: currentState = getCurrentState(namespace) ;(locale = stateProp.locale), (langs = stateProp.langs) newState = __assign({}, currentState) if (!locale) return [3 /*break*/, 2] newState.locale = locale currentLangs = (currentState === null || currentState === void 0 ? void 0 : currentState.langs) || {} currentLang = currentLangs === null || currentLangs === void 0 ? void 0 : currentLangs[locale] if (!(typeof currentLang === 'function')) return [3 /*break*/, 2] return [4 /*yield*/, currentLang()] case 1: loadedLang = _b.sent() if (isObject(loadedLang)) { newState.langs = __assign( __assign({}, currentLangs), ((_a = {}), (_a[locale] = loadedLang), _a), ) } else { console.error( 'Failed to load language pack for `'.concat(locale, '`'), loadedLang, ) } _b.label = 2 case 2: if (langs) { newState.langs = __assign( __assign({}, newState.langs || {}), Object.entries(langs).reduce(function (res, _a) { var _b var langCode = _a[0], lang = _a[1] res[langCode] = __assign( __assign( {}, ((_b = newState.langs) === null || _b === void 0 ? void 0 : _b[langCode]) || {}, ), lang, ) return res }, {}), ) } state[namespace] = Object.freeze(newState) return [2 /*return*/, newState] } }) }) } /** * Initialize the internationalization state * @param state Internationalization state */ function initI18n(stateProp) { var namespace = stateProp.namespace || 'default' if (state[namespace]) { console.error('Namespace `'.concat(namespace, '` already exists.')) } if ( typeof stateProp.beginIndex != 'undefined' && typeof stateProp.beginIndex !== 'number' ) { console.error('`beginIndex` must be a number.') delete stateProp.beginIndex } state[namespace] = __assign({}, stateProp) var condition = { namespace: namespace, locale: null } return { setI18n: setI18n.bind(null, namespace), t: generateTranslate(condition), } } exports.initI18n = initI18n Object.defineProperty(exports, '__esModule', { value: true }) })