const isObject = (val) => val !== null && typeof val === 'object'; class BaseFormatter { constructor() { this._caches = Object.create(null); } interpolate(message, values) { if (!values) { return [message]; } let tokens = this._caches[message]; if (!tokens) { tokens = parse(message); this._caches[message] = tokens; } return compile(tokens, values); } } const RE_TOKEN_LIST_VALUE = /^(?:\d)+/; const RE_TOKEN_NAMED_VALUE = /^(?:\w)+/; function parse(format) { const tokens = []; let position = 0; let text = ''; while (position < format.length) { let char = format[position++]; if (char === '{') { if (text) { tokens.push({ type: 'text', value: text }); } text = ''; let sub = ''; char = format[position++]; while (char !== undefined && char !== '}') { sub += char; char = format[position++]; } const isClosed = char === '}'; const type = RE_TOKEN_LIST_VALUE.test(sub) ? 'list' : isClosed && RE_TOKEN_NAMED_VALUE.test(sub) ? 'named' : 'unknown'; tokens.push({ value: sub, type }); } else if (char === '%') { // when found rails i18n syntax, skip text capture if (format[position] !== '{') { text += char; } } else { text += char; } } text && tokens.push({ type: 'text', value: text }); return tokens; } function compile(tokens, values) { const compiled = []; let index = 0; const mode = Array.isArray(values) ? 'list' : isObject(values) ? 'named' : 'unknown'; if (mode === 'unknown') { return compiled; } while (index < tokens.length) { const token = tokens[index]; switch (token.type) { case 'text': compiled.push(token.value); break; case 'list': compiled.push(values[parseInt(token.value, 10)]); break; case 'named': if (mode === 'named') { compiled.push(values[token.value]); } else { if (process.env.NODE_ENV !== 'production') { console.warn(`Type of token '${token.type}' and format of value '${mode}' don't match!`); } } break; case 'unknown': if (process.env.NODE_ENV !== 'production') { console.warn(`Detect 'unknown' type of token!`); } break; } index++; } return compiled; } const hasOwnProperty = Object.prototype.hasOwnProperty; const hasOwn = (val, key) => hasOwnProperty.call(val, key); const defaultFormatter = new BaseFormatter(); function include(str, parts) { return !!parts.find((part) => str.indexOf(part) !== -1); } function startsWith(str, parts) { return parts.find((part) => str.indexOf(part) === 0); } function normalizeLocale(locale, messages) { if (!locale) { return; } locale = locale.trim().replace(/_/g, '-'); if (messages[locale]) { return locale; } locale = locale.toLowerCase(); if (locale.indexOf('zh') === 0) { if (locale.indexOf('-hans') !== -1) { return 'zh-Hans'; } if (locale.indexOf('-hant') !== -1) { return 'zh-Hant'; } if (include(locale, ['-tw', '-hk', '-mo', '-cht'])) { return 'zh-Hant'; } return 'zh-Hans'; } const lang = startsWith(locale, ['en', 'fr', 'es']); if (lang) { return lang; } } class I18n { constructor({ locale, fallbackLocale, messages, watcher, formater, }) { this.locale = 'en'; this.fallbackLocale = 'en'; this.message = {}; this.messages = {}; this.watchers = []; if (fallbackLocale) { this.fallbackLocale = fallbackLocale; } this.formater = formater || defaultFormatter; this.messages = messages; this.setLocale(locale); if (watcher) { this.watchLocale(watcher); } } setLocale(locale) { const oldLocale = this.locale; this.locale = normalizeLocale(locale, this.messages) || this.fallbackLocale; this.message = this.messages[this.locale]; this.watchers.forEach((watcher) => { watcher(this.locale, oldLocale); }); } getLocale() { return this.locale; } watchLocale(fn) { const index = this.watchers.push(fn) - 1; return () => { this.watchers.splice(index, 1); }; } t(key, locale, values) { let message = this.message; if (typeof locale === 'string') { locale = normalizeLocale(locale, this.messages); locale && (message = this.messages[locale]); } else { values = locale; } if (!hasOwn(message, key)) { console.warn(`Cannot translate the value of keypath ${key}. Use the value of keypath as default.`); return key; } return this.formater.interpolate(message[key], values).join(''); } } function initLocaleWatcher(appVm, i18n) { appVm.$i18n && appVm.$i18n.vm.$watch('locale', (newLocale) => { i18n.setLocale(newLocale); }, { immediate: true, }); } function getDefaultLocale() { if (typeof navigator !== 'undefined') { return navigator.userLanguage || navigator.language; } if (typeof plus !== 'undefined') { // TODO 待调整为最新的获取语言代码 return plus.os.language; } return uni.getSystemInfoSync().language; } function initVueI18n(messages, fallbackLocale = 'en', locale) { const i18n = new I18n({ locale: locale || fallbackLocale, fallbackLocale, messages, }); let t = (key, values) => { if (typeof getApp !== 'function') { // app-plus view /* eslint-disable no-func-assign */ t = function (key, values) { return i18n.t(key, values); }; } else { const appVm = getApp().$vm; if (!appVm.$t || !appVm.$i18n) { if (!locale) { i18n.setLocale(getDefaultLocale()); } /* eslint-disable no-func-assign */ t = function (key, values) { return i18n.t(key, values); }; } else { initLocaleWatcher(appVm, i18n); /* eslint-disable no-func-assign */ t = function (key, values) { const $i18n = appVm.$i18n; const silentTranslationWarn = $i18n.silentTranslationWarn; $i18n.silentTranslationWarn = true; const msg = appVm.$t(key, values); $i18n.silentTranslationWarn = silentTranslationWarn; if (msg !== key) { return msg; } return i18n.t(key, $i18n.locale, values); }; } } return t(key, values); }; return { t(key, values) { return t(key, values); }, getLocale() { return i18n.getLocale(); }, setLocale(newLocale) { return i18n.setLocale(newLocale); }, mixin: { beforeCreate() { const unwatch = i18n.watchLocale(() => { this.$forceUpdate(); }); this.$once('hook:beforeDestroy', function () { unwatch(); }); }, methods: { $$t(key, values) { return t(key, values); }, }, }, }; } export { I18n, initVueI18n };