Initial commit of akmon project
This commit is contained in:
19
uni_modules/ak-i18n/changelog.md
Normal file
19
uni_modules/ak-i18n/changelog.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## 0.0.8(2025-03-07)
|
||||
- fix: 修复hbx 4.54版本报错问题
|
||||
## 0.0.7(2025-02-06)
|
||||
- fix: 修复hbx 4.51版本报错问题
|
||||
## 0.0.6(2025-01-14)
|
||||
- fix: 修复uniapp x ios报错问题
|
||||
## 0.0.5(2024-12-16)
|
||||
- chore: 更新文档
|
||||
## 0.0.4(2024-07-25)
|
||||
- chore: 更新文档
|
||||
## 0.0.3(2024-07-05)
|
||||
- feat: 增加 缓存key `uVueI18nLocale`,可通过`uni.getStorageSync('uVueI18nLocale')`获取上次缓存语言,保持应用退出再启动语言不变
|
||||
- fix: 修复 ios 报错的问题
|
||||
## 0.0.2(2024-06-30)
|
||||
- feat: 增加`$t`插值功能
|
||||
- feat: 增加`$tc`复数功能
|
||||
- feat: 增加`tabbar`规则
|
||||
## 0.0.1(2024-06-25)
|
||||
- init
|
||||
642
uni_modules/ak-i18n/common/composer.uts
Normal file
642
uni_modules/ak-i18n/common/composer.uts
Normal file
@@ -0,0 +1,642 @@
|
||||
import BaseFormatter from './format'
|
||||
import { warn, error, isString, getAllKeys } from './util'
|
||||
import { Composer, Interpolate, Link, WarnDefault, LinkedModify, PluralizationRule, StringOrNull, NumberOrNull, Availabilities } from './types'
|
||||
|
||||
// #ifndef APP
|
||||
type Interceptor = any
|
||||
// #endif
|
||||
export class AvailabilitiesImpl implements Availabilities {
|
||||
dateTimeFormat : boolean = false
|
||||
numberFormat : boolean = false
|
||||
constructor() {
|
||||
// #ifndef APP
|
||||
const intlDefined = typeof Intl !== 'undefined'
|
||||
this.dateTimeFormat = intlDefined && typeof Intl.DateTimeFormat !== 'undefined'
|
||||
this.numberFormat = intlDefined && typeof Intl.NumberFormat !== 'undefined'
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
const htmlTagMatcher = /<\/?[\w\s="/.':;#-\/]+>/;
|
||||
const linkKeyMatcher = /(?:@(?:\.[a-zA-Z0-9_-]+)?:)(?:[\w\-_|./]+|\([\w\-_:|./]+\)|(?:\{[^}]+?\}))/g;
|
||||
const linkKeyPrefixMatcher = /^@(?:\.([a-zA-Z]+))?:/;
|
||||
const bracketsMatcher = /[()\{\}\']/g;
|
||||
const defaultModifiers : Map<string, LinkedModify> = new Map([
|
||||
['upper', (str : string) : string => str.toLocaleUpperCase()],
|
||||
['lower', (str : string) : string => str.toLocaleLowerCase()],
|
||||
['capitalize', (str : string) : string => `${str.charAt(0).toLocaleUpperCase()}${str.substring(1)}`]
|
||||
])
|
||||
|
||||
const DEFAULT_LOCALE = "en-US"
|
||||
const defaultFormatter = new BaseFormatter()
|
||||
const availabilities = new AvailabilitiesImpl()
|
||||
|
||||
function setTabBarItems(tabbar : string[] | null) {
|
||||
if (tabbar == null) return
|
||||
const pages = getCurrentPages()
|
||||
const page = pages.length > 0 ? pages[pages.length - 1]: null;
|
||||
// @ts-ignore
|
||||
// #ifndef APP-ANDROID
|
||||
const isTabBar = page != null //page.$vm.$basePage.openType == 'switchTab'// page != null && page.$page.meta.isTabBar
|
||||
// #endif
|
||||
// #ifdef APP-ANDROID
|
||||
const isTabBar = page != null
|
||||
// #endif
|
||||
if(!isTabBar) return
|
||||
tabbar.forEach((text, index) => {
|
||||
uni.setTabBarItem({
|
||||
text,
|
||||
index,
|
||||
// success() {},
|
||||
fail(err) {
|
||||
warn(err.errMsg)
|
||||
}
|
||||
} as SetTabBarItemOptions)
|
||||
})
|
||||
}
|
||||
|
||||
function getLocaleMap(locale : string, key : string, options : UTSJSONObject, root : Composer | null = null) : Map<string, UTSJSONObject> {
|
||||
//'messages'
|
||||
const __messages = UTSJSONObject.assign({}, options.getJSON(key) ?? {})
|
||||
// #ifdef APP
|
||||
let map = new Map<string, UTSJSONObject>()
|
||||
__messages.toMap().forEach((value, key) => {
|
||||
if (value instanceof UTSJSONObject) {
|
||||
map.set(key, value)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef APP
|
||||
let map = __messages.toMap()
|
||||
// #endif
|
||||
|
||||
if (map.size == 0 && root != null) {
|
||||
// map = root.messages.value
|
||||
if (!map.has(locale)) {
|
||||
map.set(locale, {})
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
function getLocaleTabbarMap(locale : string, key : string, options : UTSJSONObject) : Map<string, string[]> {
|
||||
const __messages = options.getJSON(key) ?? {}
|
||||
let map = new Map<string, string[]>()
|
||||
__messages.toMap().forEach((tabbar, key) => {
|
||||
if (Array.isArray(tabbar)) {
|
||||
map.set(key, tabbar as string[]);
|
||||
if (key == locale) {
|
||||
setTimeout(()=>{
|
||||
setTabBarItems(tabbar as string[])
|
||||
},500)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return map
|
||||
}
|
||||
function getModifiers(options : UTSJSONObject) : Map<string, LinkedModify> {
|
||||
const __modifiers = (options.getJSON('modifiers') ?? {}).toMap();
|
||||
const _modifiers = new Map<string, LinkedModify>()
|
||||
__modifiers.forEach((value, key) => {
|
||||
if (typeof value == 'function') {
|
||||
try {
|
||||
_modifiers.set(key, value as LinkedModify)
|
||||
} catch (e) {
|
||||
error(35, '自定义修饰器函数必须是类型:(str: string) => string')
|
||||
}
|
||||
}
|
||||
})
|
||||
return _modifiers
|
||||
}
|
||||
function getPluralizationRules(options : UTSJSONObject) : Map<string, PluralizationRule> {
|
||||
const __pluralizationRules = (options.getJSON('pluralizationRules') ?? {}).toMap()
|
||||
const _pluralizationRules = new Map<string, PluralizationRule>()
|
||||
__pluralizationRules.forEach((value, key) => {
|
||||
if (typeof value == 'function') {
|
||||
try {
|
||||
_pluralizationRules.set(key, value as PluralizationRule)
|
||||
} catch (e) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
error(35, '自定义复数化规则函数必须是类型: ( choice: number, choicesLength: number) => number')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return _pluralizationRules
|
||||
}
|
||||
function getFormatter(options : UTSJSONObject) : BaseFormatter {
|
||||
const __formatter = options.get('formatter')
|
||||
return __formatter != null && __formatter instanceof BaseFormatter ? __formatter : defaultFormatter;
|
||||
}
|
||||
|
||||
let composerID = 0;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 创建一个Composer实例,用于处理国际化信息。
|
||||
* @param {UTSJSONObject} [options={}] - 配置对象,包含语言环境、格式化器等设置。
|
||||
* @param {Composer | null} [__root=null] - 根Composer实例,用于继承语言环境等信息。
|
||||
* @returns {Composer} 返回一个新的Composer实例。
|
||||
*/
|
||||
export function createComposer(options : UTSJSONObject = {}, __root : Composer | null = null) : Composer {
|
||||
|
||||
let _interpolate : Interpolate | null = null;
|
||||
let _link : Link | null;
|
||||
let _warnDefault : WarnDefault | null = null;
|
||||
|
||||
const _inheritLocale = options.getBoolean('inheritLocale') ?? true;
|
||||
const _formatter = getFormatter(options);
|
||||
const _modifiers = getModifiers(options)
|
||||
const _pluralizationRules = getPluralizationRules(options)
|
||||
|
||||
// const flatJson = options.getBoolean('flatJson') ?? false;
|
||||
const useRoot = __root != null && _inheritLocale
|
||||
|
||||
const __locale = ref<string>(
|
||||
useRoot
|
||||
? __root!.locale.value
|
||||
: options.getString('locale') ?? DEFAULT_LOCALE
|
||||
)
|
||||
const _fallbackLocale = ref<any | null>(
|
||||
useRoot
|
||||
? __root!.fallbackLocale.value
|
||||
: options.get('fallbackLocale')
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
const _messages = ref<Map<string, UTSJSONObject>>(getLocaleMap(__locale.value, 'messages', options, __root))
|
||||
const _numberFormats = ref<Map<string, UTSJSONObject>>(getLocaleMap(__locale.value, 'numberFormats', options, __root))
|
||||
const _datetimeFormats = ref<Map<string, UTSJSONObject>>(getLocaleMap(__locale.value, 'datetimeFormats', options, __root))
|
||||
const _tabBars = ref<Map<string, string[]>>(getLocaleTabbarMap(__locale.value, 'tabBars', options))
|
||||
|
||||
const _locale = computed<string>({
|
||||
set(val : string) {
|
||||
__locale.value = val;
|
||||
// 设置缓存 只有全局才会缓存
|
||||
if (__root == null) {
|
||||
uni.setStorageSync('uVueI18nLocale', val)
|
||||
}
|
||||
// 设置tabbar
|
||||
setTabBarItems(_tabBars.value.get(val))
|
||||
},
|
||||
get() : string {
|
||||
return __locale.value
|
||||
}
|
||||
} as WritableComputedOptions<string>)
|
||||
|
||||
const fallbackLocale = computed<any>({
|
||||
set(val : any) {
|
||||
_fallbackLocale.value = val
|
||||
},
|
||||
get() : any {
|
||||
return _fallbackLocale.value ?? false
|
||||
}
|
||||
} as WritableComputedOptions<any>)
|
||||
let availableLocales : string[] = getAllKeys(_messages.value).sort()
|
||||
|
||||
/**
|
||||
* 处理字符串中的链接并返回翻译后的字符串。
|
||||
* @param {string} str - 要处理的字符串。
|
||||
* @param {StringOrNull} [locale=null] - 指定语言环境。
|
||||
* @param {any} values - 用于插值的变量。
|
||||
* @param {string[]} visitedLinkStack - 已访问过的链接堆栈。
|
||||
* @param {string} interpolateMode - 插值模式。
|
||||
* @returns {StringOrNull} 返回处理后的字符串或null。
|
||||
*/
|
||||
_link = (str : string, locale : StringOrNull, values : any, visitedLinkStack : string[], interpolateMode : string) : StringOrNull => {
|
||||
const matches = str.match(linkKeyMatcher)
|
||||
let ret : string = str
|
||||
if (matches == null) return str
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
const link = matches[i]
|
||||
const linkKeyPrefixMatches = link!.match(linkKeyPrefixMatcher)
|
||||
if (linkKeyPrefixMatches == null) continue;
|
||||
const [linkPrefix, formatterName] = linkKeyPrefixMatches
|
||||
|
||||
// 去掉字符串前面的 @:、@.case: 、括号及大括号
|
||||
const linkPlaceholder : string = link.replace(linkPrefix!, '').replace(bracketsMatcher, '')
|
||||
if (visitedLinkStack.includes(linkPlaceholder)) {
|
||||
warn(`发现循环引用。"${link}"已经在link"已经在${visitedLinkStack.reverse().join(' <- ')}链中访问过`)
|
||||
return ret
|
||||
}
|
||||
if (_interpolate == null || _warnDefault == null) {
|
||||
return ret
|
||||
}
|
||||
visitedLinkStack.push(linkPlaceholder)
|
||||
let translated = _interpolate!(linkPlaceholder, locale, values, visitedLinkStack, interpolateMode)
|
||||
|
||||
translated = _warnDefault!(linkPlaceholder, translated, values, interpolateMode)
|
||||
|
||||
// 如果有自定义_modifiers 否则使用默认defaultModifiers
|
||||
if (_modifiers.size > 0 && formatterName != null && _modifiers.has(formatterName)) {
|
||||
} else if (translated != null && formatterName != null && defaultModifiers.has(formatterName)) {
|
||||
const modifier = defaultModifiers.get(formatterName) as LinkedModify
|
||||
translated = modifier(translated)
|
||||
}
|
||||
visitedLinkStack.pop()
|
||||
|
||||
// 将链接替换为已翻译的
|
||||
ret = translated == null ? ret : ret.replace(link, translated)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
/**
|
||||
* 获取指定语言字符。
|
||||
* @param {string} key - 要翻译的键。
|
||||
* @param {StringOrNull} [locale=null] - 指定语言环境。
|
||||
* @param {any} values - 用于插值的变量。
|
||||
* @param {string[]} visitedLinkStack - 已访问过的链接堆栈。
|
||||
* @param {string} interpolateMode - 插值模式。
|
||||
* @returns {StringOrNull} 返回翻译后的字符串或null。
|
||||
*/
|
||||
_interpolate = (key : string, locale : StringOrNull, values : any, visitedLinkStack : string[], interpolateMode : string) : StringOrNull => {
|
||||
const ___locale = locale ?? _locale.value
|
||||
let ret = _messages.value.get(___locale)?.getString(key)
|
||||
// console.log(_messages)
|
||||
// console.log(key,ret)
|
||||
if (fallbackLocale.value != false && ret == null) {
|
||||
if (typeof fallbackLocale.value == 'string' && ___locale != fallbackLocale.value) {
|
||||
ret = _messages.value.get(fallbackLocale.value as string)?.getString(key) ?? ret
|
||||
} else if (Array.isArray(fallbackLocale.value)) {
|
||||
const arr = (fallbackLocale.value as string[])
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const _ret = _messages.value.get(arr[i])?.getString(key)
|
||||
if (_ret != null) {
|
||||
ret = _ret
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 检查翻译后的字符串中是否存在链接
|
||||
if (typeof ret == 'string' && (ret!.indexOf('@:') >= 0 || ret!.indexOf('@.') >= 0)) {
|
||||
// @ts-ignore
|
||||
ret = _link(ret!, locale, values, visitedLinkStack, interpolateMode)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
/**
|
||||
* 获取指定语言字符并渲染。
|
||||
* @param {string} message - 要翻译的字符串。
|
||||
* @param {any} values - 用于插值的变量。
|
||||
* @param {string} interpolateMode - 插值模式。
|
||||
* @returns {string} 返回渲染后的字符串。
|
||||
*/
|
||||
const _render = (message : string, values : any, interpolateMode : string) : string => {
|
||||
const ret = _formatter.interpolate(message, values)
|
||||
return interpolateMode == 'string' ? `${ret.join('')}` : JSON.stringify(ret)
|
||||
}
|
||||
/**
|
||||
* 在无法翻译的情况下发出警告并提供默认值。
|
||||
* @param {string} key - 要翻译的键。
|
||||
* @param {StringOrNull} message - 翻译后的字符串或null。
|
||||
* @param {any} values - 用于插值的变量。
|
||||
* @param {string} interpolateMode - 插值模式。
|
||||
* @returns {StringOrNull} 返回警告信息或默认值。
|
||||
*/
|
||||
_warnDefault = (key : string, message : StringOrNull, values : any, interpolateMode : string) : StringOrNull => {
|
||||
if (message == null) {
|
||||
warn(`无法翻译键路径 '${key}'. ` + '使用键路径的值作为默认值.')
|
||||
}
|
||||
if (message == null) return null
|
||||
if (key == message) return key
|
||||
return _render(message, values, interpolateMode)
|
||||
}
|
||||
/**
|
||||
* 获取复数形式的选择。
|
||||
* @param {string} message - 包含复数选择的字符串。
|
||||
* @param {number | null} [choice=null] - 复数形式的选择。
|
||||
* @param {string | null} [locale=null] - 指定语言环境。
|
||||
* @returns {string} 返回选择后的字符串。
|
||||
*/
|
||||
const fetchChoice = (message : string, choice ?: number, locale ?: string) : string => {
|
||||
if (message == '') return message;
|
||||
const choices : Array<string> = message.split('|');
|
||||
|
||||
// 默认 vue-i18n(旧)getChoiceIndex实现 - 兼容英文
|
||||
const defaultImpl = (_choice : NumberOrNull, _choicesLength : number) : number => {
|
||||
_choice = Math.abs(_choice ?? 1)
|
||||
if (_choicesLength == 2) {
|
||||
return _choice != 0
|
||||
? _choice > 1
|
||||
? 1
|
||||
: 0
|
||||
: 1
|
||||
}
|
||||
return _choice != 0 ? Math.min(_choice, 2) : 0
|
||||
}
|
||||
let index : number;
|
||||
if (_pluralizationRules.has(locale ?? _locale.value)) {
|
||||
index = _pluralizationRules.get(locale ?? _locale.value)!(choice ?? 1, choices.length)
|
||||
} else {
|
||||
index = defaultImpl(choice, choices.length)
|
||||
}
|
||||
|
||||
if (choices[index] == '') return message
|
||||
return choices[index].trim()
|
||||
}
|
||||
/**
|
||||
* 翻译指定的键。
|
||||
* @param {string} key - 要翻译的键。
|
||||
* @param {any} [values=null] - 用于插值的变量。
|
||||
* @param {string | null} [locale=null] - 指定语言环境。
|
||||
* @returns {string} 返回翻译后的字符串。
|
||||
*/
|
||||
const t = (key : string, values ?: any, locale ?: string) : string => {
|
||||
const parsedArgs = values ?? {}
|
||||
// #ifndef APP
|
||||
if (_warnDefault == null || _interpolate == null) return ''
|
||||
// #endif
|
||||
const msg = _warnDefault(
|
||||
key,
|
||||
_interpolate(
|
||||
key,
|
||||
locale,
|
||||
parsedArgs,
|
||||
[key],
|
||||
'string'),
|
||||
parsedArgs,
|
||||
'string'
|
||||
)
|
||||
return msg ?? ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译指定的键并获取复数形式的选择。
|
||||
* @param {string} key - 要翻译的键。
|
||||
* @param {number | null} [choice=null] - 复数形式的选择。
|
||||
* @param {any} [values=null] - 用于插值的变量。
|
||||
* @param {string | null} [locale=null] - 指定语言环境。
|
||||
* @returns {string} 返回翻译后的复数形式选择字符串。
|
||||
*/
|
||||
const tc = (key : string, choice ?: number, values ?: any, locale ?: string) : string => {
|
||||
// 预定义的count和n参数
|
||||
const _obj = { 'count': choice, 'n': choice }
|
||||
const predefined = values == null
|
||||
? _obj
|
||||
: values instanceof UTSJSONObject
|
||||
? UTSJSONObject.assign(_obj, values as UTSJSONObject)
|
||||
: values;
|
||||
|
||||
return fetchChoice(t(key, predefined, locale), choice, locale)
|
||||
}
|
||||
/**
|
||||
* 格式化日期。
|
||||
* @param {any} date - 要格式化的日期。
|
||||
* @param {StringOrNull} [key=null] - 日期格式化的键。
|
||||
* @param {StringOrNull} [locale=null] - 指定语言环境。
|
||||
* @param {UTSJSONObject | null} [options=null] - 日期格式化的选项。
|
||||
* @returns {string} 返回格式化后的日期字符串。
|
||||
*/
|
||||
const d = (date : any, key : StringOrNull, locale : StringOrNull, options : UTSJSONObject | null) : string => {
|
||||
if (!availabilities.dateTimeFormat) {
|
||||
warn('无法格式化日期值,因为不支持 Intl.DateTimeFormat. ' + `key: ${key}, locale: ${locale}, options: ${options}`)
|
||||
return `${date}`
|
||||
}
|
||||
|
||||
// #ifndef APP
|
||||
const __locale = locale ?? _locale.value
|
||||
if (key == null) {
|
||||
// @ts-ignore
|
||||
const dtf = options == null ? new Intl.DateTimeFormat(__locale) : new Intl.DateTimeFormat(__locale, options)
|
||||
return dtf.format(date)
|
||||
}
|
||||
const formats = _datetimeFormats.value!.get(__locale)
|
||||
let formatter;
|
||||
if (formats == null || formats!.getJSON(key) == null) {
|
||||
warn(`回退到根号下的日期时间本地化:key '${key}'。`)
|
||||
return `${date}`
|
||||
}
|
||||
const format = formats!.getJSON(key) ?? {}
|
||||
if (options != null) {
|
||||
// @ts-ignore
|
||||
formatter = new Intl.DateTimeFormat(__locale, Object.assign({}, format, options))
|
||||
} else {
|
||||
// @ts-ignore
|
||||
formatter = new Intl.DateTimeFormat(__locale, format)
|
||||
}
|
||||
return formatter.format(date)
|
||||
// #endif
|
||||
return `${date}`
|
||||
}
|
||||
/**
|
||||
* 格式化数字。
|
||||
* @param {number} number - 要格式化的数字。
|
||||
* @param {StringOrNull} [key=null] - 数字格式化的键。
|
||||
* @param {StringOrNull} [locale=null] - 指定语言环境。
|
||||
* @param {UTSJSONObject | null} [options=null] - 数字格式化的选项。
|
||||
* @returns {string} 返回格式化后的数字字符串。
|
||||
*/
|
||||
const n = (number : number, key : StringOrNull, locale : StringOrNull, options : UTSJSONObject | null) : string => {
|
||||
if (!availabilities.numberFormat) {
|
||||
warn('无法格式化数字值,因为不支持 Intl.NumberFormat. ' + `key: ${key}, locale: ${locale}, options: ${options}`)
|
||||
return number.toString()
|
||||
}
|
||||
|
||||
// #ifndef APP
|
||||
const __locale = locale ?? _locale.value
|
||||
if (key == null) {
|
||||
// @ts-ignore
|
||||
const nf = options == null ? new Intl.NumberFormat(__locale) : new Intl.NumberFormat(locale, options)
|
||||
return nf.format(number)
|
||||
}
|
||||
const formats = _numberFormats.value!.get(__locale)
|
||||
let formatter;
|
||||
if (formats == null || formats!.getJSON(key) == null) {
|
||||
warn(`回退到根号下的数字本地化:key '${key}'`)
|
||||
return number.toString()
|
||||
}
|
||||
const format = formats!.getJSON(key)
|
||||
if (options != null) {
|
||||
// @ts-ignore
|
||||
formatter = new Intl.NumberFormat(__locale, Object.assign({}, format, options))
|
||||
} else {
|
||||
// @ts-ignore
|
||||
formatter = new Intl.NumberFormat(__locale, format)
|
||||
}
|
||||
if (formatter) {
|
||||
return formatter.format(number)
|
||||
}
|
||||
// #endif
|
||||
return number.toString()
|
||||
}
|
||||
/**
|
||||
* 设置语言环境的locale信息。
|
||||
* @param {string} locale - 语言。
|
||||
* @param {UTSJSONObject} message - locale信息。
|
||||
*/
|
||||
const setLocaleMessage = (locale : string, message : UTSJSONObject) => {
|
||||
const map = new Map<string, UTSJSONObject>();
|
||||
_messages.value.forEach((value, key) => {
|
||||
map.set(key, value)
|
||||
})
|
||||
map.set(locale, message)
|
||||
_messages.value = map
|
||||
availableLocales = getAllKeys(map).sort()
|
||||
}
|
||||
/**
|
||||
* 获取语言环境的locale信息。
|
||||
* @param {string} locale - 语言。
|
||||
* @returns {UTSJSONObject} - locale信息。
|
||||
*/
|
||||
const getLocaleMessage = (locale : string) : UTSJSONObject => {
|
||||
return _messages.value.get(locale) ?? {}
|
||||
}
|
||||
/**
|
||||
* 将语言环境信息locale合并到已注册的语言环境信息中。
|
||||
* @param {string} locale - 语言。
|
||||
* @param {UTSJSONObject} message - locale信息。
|
||||
*/
|
||||
const mergeLocaleMessage = (locale : string, message : UTSJSONObject) => {
|
||||
const map = new Map<string, UTSJSONObject>();
|
||||
_messages.value.forEach((value, key) => {
|
||||
if (key == locale) {
|
||||
map.set(key, UTSJSONObject.assign({}, value, message))
|
||||
} else {
|
||||
map.set(key, value)
|
||||
}
|
||||
})
|
||||
_messages.value = map
|
||||
availableLocales = getAllKeys(map).sort()
|
||||
}
|
||||
/**
|
||||
* 设置日期时间格式。
|
||||
* @param {string} locale - 语言。
|
||||
* @param {UTSJSONObject} format - 日期时间格式。
|
||||
*/
|
||||
const setDateTimeFormat = (locale : string, format : UTSJSONObject) => {
|
||||
const map = new Map<string, UTSJSONObject>();
|
||||
_datetimeFormats.value.forEach((value, key) => {
|
||||
map.set(key, value)
|
||||
})
|
||||
map.set(locale, format)
|
||||
_datetimeFormats.value = map
|
||||
}
|
||||
/**
|
||||
* 获取日期时间格式。
|
||||
* @param {string} locale - 语言。
|
||||
* @returns {UTSJSONObject} - 日期时间格式。
|
||||
*/
|
||||
const getDateTimeFormat = (locale : string) : UTSJSONObject => {
|
||||
return _datetimeFormats.value.get(locale) ?? {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并日期时间格式到已注册的日期时间格式中。
|
||||
* @param {string} locale - 语言。
|
||||
* @param {UTSJSONObject} format - 日期时间格式。
|
||||
*/
|
||||
const mergeDateTimeFormat = (locale : string, format : UTSJSONObject) => {
|
||||
const map = new Map<string, UTSJSONObject>();
|
||||
_datetimeFormats.value.forEach((value, key) => {
|
||||
if (key == locale) {
|
||||
map.set(key, UTSJSONObject.assign({}, value, format))
|
||||
} else {
|
||||
map.set(key, value)
|
||||
}
|
||||
})
|
||||
_datetimeFormats.value = map
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数字格式。
|
||||
* @param {string} locale - 语言。
|
||||
* @param {UTSJSONObject} format - 数字格式。
|
||||
*/
|
||||
const setNumberFormat = (locale : string, format : UTSJSONObject) => {
|
||||
const map = new Map<string, UTSJSONObject>();
|
||||
_numberFormats.value.forEach((value, key) => {
|
||||
map.set(key, value)
|
||||
})
|
||||
map.set(locale, format)
|
||||
_numberFormats.value = map
|
||||
}
|
||||
/**
|
||||
* 获取数字格式。
|
||||
* @param {string} locale - 语言。
|
||||
* @returns {UTSJSONObject} - 数字格式。
|
||||
*/
|
||||
const getNumberFormat = (locale : string) : UTSJSONObject => {
|
||||
return _numberFormats.value.get(locale) ?? {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并数字格式到已注册的数字格式中。
|
||||
* @param {string} locale - 语言。
|
||||
* @param {UTSJSONObject} format - 数字格式。
|
||||
*/
|
||||
const mergeNumberFormat = (locale : string, format : UTSJSONObject) => {
|
||||
const map = new Map<string, UTSJSONObject>();
|
||||
_numberFormats.value.forEach((value, key) => {
|
||||
if (key == locale) {
|
||||
map.set(key, UTSJSONObject.assign({}, value, format))
|
||||
} else {
|
||||
map.set(key, value)
|
||||
}
|
||||
})
|
||||
_numberFormats.value = map
|
||||
}
|
||||
/**
|
||||
* 设置TabBar。
|
||||
* @param {string} locale - 语言。
|
||||
* @param {string[]} tabbar - TabBar项目。
|
||||
*/
|
||||
const setTabBar = (locale : string, tabbar : string[]) => {
|
||||
const map = new Map<string, string[]>();
|
||||
_tabBars.value.forEach((value, key) => {
|
||||
map.set(key, value)
|
||||
})
|
||||
map.set(locale, tabbar)
|
||||
_tabBars.value = map
|
||||
}
|
||||
/**
|
||||
* 获取TabBar。
|
||||
* @param {string} locale - 语言。
|
||||
* @returns {string[]} - TabBar项目。
|
||||
*/
|
||||
const getTabBar = (locale : string) : string[] => {
|
||||
return _tabBars.value.get(locale) ?? []
|
||||
}
|
||||
|
||||
composerID++;
|
||||
const interceptor = {
|
||||
complete: (_ : NavigateToComplete) => {
|
||||
setTimeout(()=>{
|
||||
setTabBarItems(_tabBars.value.get(_locale.value))
|
||||
},50)
|
||||
}
|
||||
} as Interceptor
|
||||
if(__root == null) {
|
||||
uni.addInterceptor('switchTab', interceptor);
|
||||
}
|
||||
const composer : Composer = {
|
||||
id: composerID,
|
||||
locale: _locale,
|
||||
fallbackLocale,
|
||||
messages: _messages,
|
||||
setLocaleMessage,
|
||||
getLocaleMessage,
|
||||
mergeLocaleMessage,
|
||||
setDateTimeFormat,
|
||||
getDateTimeFormat,
|
||||
mergeDateTimeFormat,
|
||||
setNumberFormat,
|
||||
getNumberFormat,
|
||||
mergeNumberFormat,
|
||||
setTabBar,
|
||||
getTabBar,
|
||||
t,
|
||||
tc,
|
||||
d,
|
||||
n,
|
||||
availableLocales,
|
||||
availabilities
|
||||
}
|
||||
|
||||
return composer
|
||||
}
|
||||
60
uni_modules/ak-i18n/common/errors.uts
Normal file
60
uni_modules/ak-i18n/common/errors.uts
Normal file
@@ -0,0 +1,60 @@
|
||||
type I18nErrorCodesTypes = {
|
||||
UNEXPECTED_RETURN_TYPE: number
|
||||
INVALID_ARGUMENT: number
|
||||
MUST_BE_CALL_SETUP_TOP: number
|
||||
NOT_INSTALLED: number
|
||||
REQUIRED_VALUE: number
|
||||
INVALID_VALUE: number
|
||||
CANNOT_SETUP_VUE_DEVTOOLS_PLUGIN: number
|
||||
NOT_INSTALLED_WITH_PROVIDE: number
|
||||
UNEXPECTED_ERROR: number
|
||||
NOT_COMPATIBLE_LEGACY_VUE_I18N: number
|
||||
NOT_AVAILABLE_COMPOSITION_IN_LEGACY: number
|
||||
TYPE_MISMATCH: number
|
||||
}
|
||||
|
||||
export const I18nErrorCodes: I18nErrorCodesTypes = {
|
||||
// composer模块错误
|
||||
UNEXPECTED_RETURN_TYPE: 24,
|
||||
// legacy模块错误
|
||||
INVALID_ARGUMENT: 25,
|
||||
// i18n模块错误
|
||||
MUST_BE_CALL_SETUP_TOP: 26,
|
||||
NOT_INSTALLED: 27,
|
||||
// directive模块错误
|
||||
REQUIRED_VALUE: 28,
|
||||
INVALID_VALUE: 29,
|
||||
// vue-devtools错误
|
||||
CANNOT_SETUP_VUE_DEVTOOLS_PLUGIN: 30,
|
||||
NOT_INSTALLED_WITH_PROVIDE: 31,
|
||||
// 意外错误
|
||||
UNEXPECTED_ERROR: 32,
|
||||
// 不兼容的旧版vue-i18n构造函数
|
||||
NOT_COMPATIBLE_LEGACY_VUE_I18N: 33,
|
||||
// 在旧版API模式下,Compostion API不可用。请确保旧版API模式正常工作
|
||||
NOT_AVAILABLE_COMPOSITION_IN_LEGACY: 34,
|
||||
// 类型不匹配
|
||||
TYPE_MISMATCH: 35
|
||||
}
|
||||
|
||||
export const errorMessages: Map<number, string> = new Map<number, string>([
|
||||
[I18nErrorCodes.UNEXPECTED_RETURN_TYPE, 'composer中返回类型异常'],
|
||||
[I18nErrorCodes.INVALID_ARGUMENT, '参数无效'],
|
||||
[I18nErrorCodes.MUST_BE_CALL_SETUP_TOP, '必须在`setup`函数的顶部调用'],
|
||||
[I18nErrorCodes.NOT_INSTALLED, '需要用`app.use`函数安装'],
|
||||
[I18nErrorCodes.UNEXPECTED_ERROR, '意外错误'],
|
||||
[I18nErrorCodes.REQUIRED_VALUE, `值中必需,{0}`],
|
||||
[I18nErrorCodes.INVALID_VALUE, `值无效`],
|
||||
[I18nErrorCodes.CANNOT_SETUP_VUE_DEVTOOLS_PLUGIN, `无法设置vue-devtools插件`],
|
||||
[I18nErrorCodes.NOT_INSTALLED_WITH_PROVIDE, '需要用`provide`函数安装'],
|
||||
[I18nErrorCodes.NOT_COMPATIBLE_LEGACY_VUE_I18N, '不兼容的旧版VueI18n。'],
|
||||
[I18nErrorCodes.NOT_AVAILABLE_COMPOSITION_IN_LEGACY, '在旧版API模式下,Compostion API不可用。请确保旧版API模式正常工作'],
|
||||
[I18nErrorCodes.TYPE_MISMATCH, '类型不匹配']
|
||||
])
|
||||
|
||||
// export function createI18nError(code: number, msg?: string) {
|
||||
// if(process.env.NODE_ENV !== 'production') {
|
||||
// console.warn(`[vue-i18n] : ${msg ?? errorMessages.get(code)}`)
|
||||
// }
|
||||
// new Error(errorMessages.get(code) ?? 'code error')
|
||||
// }
|
||||
149
uni_modules/ak-i18n/common/format.uts
Normal file
149
uni_modules/ak-i18n/common/format.uts
Normal file
@@ -0,0 +1,149 @@
|
||||
// @ts-nocheck
|
||||
import { warn, isObject } from './util'
|
||||
|
||||
type Token = {
|
||||
type : 'text' | 'named' | 'list' | 'unknown',
|
||||
value : string
|
||||
}
|
||||
|
||||
const RE_TOKEN_LIST_VALUE = /^(?:\d)+/
|
||||
const RE_TOKEN_NAMED_VALUE = /^(?:\w)+/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 解析格式化字符串并生成一个包含标记(Token)的数组。
|
||||
* 这些标记可以是文本、列表或命名值。
|
||||
*
|
||||
* @param {string} format - 需要解析的格式化字符串。
|
||||
* @returns {Array<Token>} 返回一个包含解析后的标记的数组。
|
||||
*/
|
||||
export function parse(format : string) : Array<Token> {
|
||||
const tokens : Array<Token> = []
|
||||
let position : number = 0
|
||||
|
||||
let text : string = ''
|
||||
while (position < format.length) {
|
||||
let char : string = format.charAt(position++)
|
||||
if (char == '{') {
|
||||
if (text.length > 0) {
|
||||
const token : Token = { type: 'text', value: text }
|
||||
tokens.push(token)
|
||||
}
|
||||
text = ''
|
||||
let sub : string = ''
|
||||
char = format.charAt(position++)
|
||||
|
||||
while (char != '}') {
|
||||
sub += char
|
||||
char = format.charAt(position++)
|
||||
}
|
||||
const isClosed = char == '}'
|
||||
|
||||
const type = RE_TOKEN_LIST_VALUE.test(sub)
|
||||
? 'list'
|
||||
: isClosed && RE_TOKEN_NAMED_VALUE.test(sub)
|
||||
? 'named'
|
||||
: 'unknown'
|
||||
const token : Token = { type, value: sub }
|
||||
tokens.push(token)
|
||||
} else if (char == '%') {
|
||||
// when found rails i18n syntax, skip text capture
|
||||
if (format.charAt(position) != '{') {
|
||||
text += char
|
||||
}
|
||||
} else {
|
||||
text += char
|
||||
}
|
||||
}
|
||||
|
||||
if (text.length > 0) {
|
||||
const token : Token = { type: 'text', value: text }
|
||||
tokens.push(token)
|
||||
}
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据给定的标记数组和值对象或数组,编译出相应的值数组。
|
||||
*
|
||||
* @param {Array<Token>} tokens - 标记数组,包含文本、列表和命名值。
|
||||
* @param {Object | Array<any>} values - 值对象或数组,用于替换标记中的占位符。
|
||||
* @returns {Array<any>} 返回编译后的值数组。
|
||||
*/
|
||||
|
||||
function compile(tokens : Array<Token>, values : UTSJSONObject) : Array<any>
|
||||
function compile(tokens : Array<Token>, values : Array<any>) : Array<any>
|
||||
function compile(tokens : Array<Token>, values : any) : Array<any> {
|
||||
const compiled : Array<any> = []
|
||||
let index : number = 0;
|
||||
const mode : string = Array.isArray(values)
|
||||
? 'list'
|
||||
: isObject(values)
|
||||
? 'named'
|
||||
: 'unknown'
|
||||
if (mode == 'unknown') {
|
||||
return compiled
|
||||
}
|
||||
while (index < tokens.length) {
|
||||
const token : Token = tokens[index]
|
||||
switch (token.type) {
|
||||
case 'text':
|
||||
compiled.push(token.value)
|
||||
break
|
||||
case 'list':
|
||||
const index = parseInt(token.value, 10)
|
||||
if(mode == 'list') {
|
||||
const value = (values as any[])[index]
|
||||
compiled.push(value)
|
||||
} else {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warn('list did not receive a valid values array')
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'named':
|
||||
if (mode == 'named') {
|
||||
const value = (values as UTSJSONObject)[token.value] ?? ''
|
||||
compiled.push(`${value}`)
|
||||
} else {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warn(`Type of token '${token.type}' and format of value '${mode}' don't match!`)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'unknown':
|
||||
if(token.value.startsWith("'") && token.value.endsWith("'")) {
|
||||
compiled.push(token.value.slice(1, -1))
|
||||
} else if (process.env.NODE_ENV !== 'production') {
|
||||
warn(`Detect 'unknown' type of token!`)
|
||||
}
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
return compiled
|
||||
}
|
||||
|
||||
export {compile}
|
||||
|
||||
export default class BaseFormatter {
|
||||
private _caches : Map<string, Token[]>
|
||||
constructor() {
|
||||
this._caches = new Map<string, Token[]>()
|
||||
}
|
||||
interpolate(message : string, values : any | null) : any[] {
|
||||
if (values == null) {
|
||||
return [message]
|
||||
}
|
||||
let tokens : Array<Token> | null = this._caches.get(message)
|
||||
if (tokens == null) {
|
||||
tokens = parse(message)
|
||||
this._caches.set(message, tokens)
|
||||
}
|
||||
return compile(tokens, values)
|
||||
}
|
||||
}
|
||||
79
uni_modules/ak-i18n/common/index.uts
Normal file
79
uni_modules/ak-i18n/common/index.uts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { createComposer } from './composer'
|
||||
import { error, warn, getAllKeys } from './util'
|
||||
import { I18nErrorCodes } from './errors'
|
||||
import { AnyOrNull, NumberOrNull, StringOrNull, Composer } from './types'
|
||||
type I18nMode = "legacy" | "composition"
|
||||
|
||||
// #ifndef APP
|
||||
type VuePlugin = any
|
||||
// #endif
|
||||
|
||||
let i18n : UvueI18n | null = null
|
||||
class UvueI18n {
|
||||
private __global : Composer
|
||||
private __scope : EffectScope
|
||||
constructor(options : UTSJSONObject = {}, root : Composer | null = null) {
|
||||
this.__scope = effectScope()
|
||||
this.__global = this.__scope.run(() : Composer => createComposer(UTSJSONObject.assign({}, options), root))!
|
||||
}
|
||||
get mode() : I18nMode {
|
||||
return "composition"
|
||||
}
|
||||
get global() : Composer {
|
||||
return this.__global
|
||||
}
|
||||
get availableLocales():string[] {
|
||||
return getAllKeys(this.global.messages.value).sort()
|
||||
}
|
||||
dispose() {
|
||||
this.__scope.stop()
|
||||
}
|
||||
get install() : VuePlugin {
|
||||
const _install = (app : VueApp) => {
|
||||
app.config.globalProperties.$i18n = i18n!
|
||||
app.config.globalProperties.$t = function (key : string, values : AnyOrNull = null, locale : StringOrNull = null) : string {
|
||||
const isLocale = typeof values == 'string'
|
||||
const _values = isLocale ? null : values
|
||||
const _locale = isLocale ? values as string : locale
|
||||
return i18n!.global.t(key, _values, _locale)
|
||||
}
|
||||
app.config.globalProperties.$tc = function (key : string, choice : NumberOrNull = null, values : AnyOrNull = null, locale : StringOrNull = null) : string {
|
||||
const isLocale = typeof values == 'string'
|
||||
const _values = isLocale ? null : values
|
||||
const _locale = isLocale ? values as string : locale
|
||||
return i18n!.global.tc(key, choice, _values, _locale)
|
||||
}
|
||||
app.config.globalProperties.$d = function(date: any, key: StringOrNull = null, locale : StringOrNull = null, options: UTSJSONObject | null = null):string {
|
||||
return i18n!.global.d(date, key, locale, options)
|
||||
}
|
||||
app.config.globalProperties.$n = function(number: number, key: StringOrNull = null, locale : AnyOrNull = null, options: UTSJSONObject | null = null):string {
|
||||
const _locale = typeof locale == 'string' ? locale as string : null
|
||||
const _options = typeof locale == 'object' && locale != null ? locale as UTSJSONObject : options
|
||||
return i18n!.global.n(number, key, _locale, _options)
|
||||
}
|
||||
app.config.globalProperties.$locale = i18n!.global.locale
|
||||
}
|
||||
// #ifdef APP-ANDROID
|
||||
return definePlugin({
|
||||
install: _install
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP-ANDROID
|
||||
return _install
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
export function createI18n(options : UTSJSONObject = {}) : UvueI18n {
|
||||
// const __legacyMode = true
|
||||
i18n = new UvueI18n(options)
|
||||
return i18n!
|
||||
}
|
||||
|
||||
export function useI18n(options : UTSJSONObject = {}) : Composer {
|
||||
const instance = getCurrentInstance()
|
||||
if (instance == null) {
|
||||
error(I18nErrorCodes.MUST_BE_CALL_SETUP_TOP)
|
||||
}
|
||||
return new UvueI18n(options, i18n!.global).global
|
||||
}
|
||||
29
uni_modules/ak-i18n/common/test/format.uts
Normal file
29
uni_modules/ak-i18n/common/test/format.uts
Normal file
@@ -0,0 +1,29 @@
|
||||
console.log('i18n format test:::::::::::::::::::')
|
||||
|
||||
import { parse, compile } from '../format'
|
||||
function appTest() {
|
||||
// 示例1:文本插值
|
||||
const tokens = parse('Hello, {name}!')
|
||||
const values = { name: 'Alice' }
|
||||
console.log('tokens app', tokens)
|
||||
console.log('compile app', compile(tokens, values)) // 输出:['Hello, ', 'Alice', '!']
|
||||
|
||||
// 示例2:列表插值
|
||||
const tokens2 = parse('The {0}st person is {1}.')
|
||||
const values2 = ['first', 'Alice']
|
||||
console.log('tokens2 app', tokens2)
|
||||
console.log('compile2 app', compile(tokens2, values2))
|
||||
|
||||
// 示例3:混合插值
|
||||
const tokens3 = parse('The {0}st person is {name}.')
|
||||
const values3 = ['first', { name: 'Alice' }]
|
||||
console.log('tokens3 app', tokens3)
|
||||
console.log('compile2 app',compile(tokens3, values3)) // 输出:['The ', 'first', 'st person is ', 'Alice', '.']
|
||||
|
||||
// 示例4:未知类型
|
||||
const tokens4 = parse('Hello, {unknown}!')
|
||||
const values4 = { name: 'Alice' }
|
||||
console.log('tokens4 web', tokens4)
|
||||
console.log('compile4 web',compile(tokens4, values4)) // 输出:['Hello, ', 'unknown', '!']
|
||||
}
|
||||
appTest()
|
||||
3
uni_modules/ak-i18n/common/test/index.uts
Normal file
3
uni_modules/ak-i18n/common/test/index.uts
Normal file
@@ -0,0 +1,3 @@
|
||||
// import * as utils from './utils.uts';
|
||||
// import * as format from './format.uts';
|
||||
console.log('i18n test:::::::::::::::::::')
|
||||
29
uni_modules/ak-i18n/common/test/utils.uts
Normal file
29
uni_modules/ak-i18n/common/test/utils.uts
Normal file
@@ -0,0 +1,29 @@
|
||||
console.log('i18n utils test:::::::::::::::::::')
|
||||
import { warn, error, isObject, isBoolean, isString, isPlainObject, isNull, isFunction, parseArgs, arrayFrom, hasOwn, merge, looseEqual } from '../util'
|
||||
|
||||
console.log('warn', warn('test warn'))
|
||||
console.log('error', error('test error'))
|
||||
console.log('isArray', isArray('test isArray'))
|
||||
console.log('isObject', isObject({}))
|
||||
console.log('isBoolean', isBoolean(false))
|
||||
console.log('isString', isString('false'))
|
||||
console.log('isPlainObject', isPlainObject({}))
|
||||
console.log('isNull', isNull(null))
|
||||
console.log('isFunction', isFunction(null))
|
||||
console.log('parseArgs', parseArgs(1,2,23,5))
|
||||
console.log('parseArgs', parseArgs('zh-CN'))
|
||||
console.log('parseArgs', parseArgs({ a: 1, b: 2 }))
|
||||
console.log('parseArgs', parseArgs('zh-CN', { a: 1, b: 2 }))
|
||||
console.log('parseArgs', parseArgs({ a: 1, b: 2 }, 'zh-CN'))
|
||||
console.log('arrayFrom', arrayFrom(new Set([1, 2, 3, 4, 5])))
|
||||
console.log('hasOwn', hasOwn({ a: { b: 2 }, c: 3 }, 'a'))
|
||||
console.log('hasOwn', hasOwn({ a: { b: 2 }, c: 3 }, 'd'))
|
||||
console.log('merge', merge({ a: { b: 2 }, c: 3 }, { b: 2 }))
|
||||
console.log('looseEqual', looseEqual(123,123))
|
||||
console.log('looseEqual', looseEqual('hello','hello'))
|
||||
console.log('looseEqual', looseEqual([1, 2, 3],[1, 2, 3]))
|
||||
console.log('looseEqual', looseEqual([1, 2, 3],[1, 2, 4]))
|
||||
console.log('looseEqual', looseEqual({},[1, 2, 4]))
|
||||
console.log('looseEqual', looseEqual({},{}))
|
||||
console.log('looseEqual', looseEqual({},{a:1}))
|
||||
console.log('looseEqual', looseEqual({a:1},{a:1}))
|
||||
53
uni_modules/ak-i18n/common/types.uts
Normal file
53
uni_modules/ak-i18n/common/types.uts
Normal file
@@ -0,0 +1,53 @@
|
||||
// #ifndef APP
|
||||
import { ComputedRef, WritableComputedRef } from 'vue'
|
||||
type ComputedRefImpl<T> = WritableComputedRef<T>;
|
||||
// #endif
|
||||
|
||||
|
||||
export type AnyOrNull = any | null;
|
||||
export type NumberOrNull = number | null;
|
||||
export type StringOrNull = string | null;
|
||||
|
||||
// 定义特定的函数类型别名
|
||||
export type Interpolate = (key : string, locale : StringOrNull, values : any, visitedLinkStack : string[], interpolateMode : string) => StringOrNull;
|
||||
export type Link = (str : string, locale : StringOrNull, values : any, visitedLinkStack : string[], interpolateMode : string) => StringOrNull;
|
||||
export type WarnDefault = (key : string, message : StringOrNull, values : any, interpolateMode : string) => StringOrNull;
|
||||
|
||||
export type LinkedModify = (str : string) => string;
|
||||
export type PluralizationRule = (choice : number, choicesLength : number) => number
|
||||
|
||||
|
||||
export interface Availabilities {
|
||||
dateTimeFormat : boolean
|
||||
numberFormat : boolean
|
||||
}
|
||||
|
||||
export type Composer = {
|
||||
id : number,
|
||||
locale : Ref<string>,
|
||||
fallbackLocale : ComputedRefImpl<any>,
|
||||
messages : Ref<Map<string, UTSJSONObject>>,
|
||||
t(key : string, values ?: any, locale ?: string) : string,
|
||||
tc(key : string, choice ?: number, values ?: any, locale ?: string) : string,
|
||||
d(date : any, key : StringOrNull, locale : StringOrNull, options : UTSJSONObject | null) : string
|
||||
n(number : number, key : StringOrNull, locale : StringOrNull, options : UTSJSONObject | null) : string
|
||||
|
||||
setLocaleMessage(locale : string, message : UTSJSONObject) : void,
|
||||
getLocaleMessage(locale : string) : UTSJSONObject,
|
||||
mergeLocaleMessage(locale : string, message : UTSJSONObject) : void,
|
||||
|
||||
setDateTimeFormat(locale : string, format : UTSJSONObject) : void,
|
||||
getDateTimeFormat(locale : string) : UTSJSONObject,
|
||||
mergeDateTimeFormat(locale : string, format : UTSJSONObject) : void,
|
||||
|
||||
setNumberFormat(locale : string, format : UTSJSONObject) : void,
|
||||
getNumberFormat(locale : string) : UTSJSONObject,
|
||||
mergeNumberFormat(locale : string, format : UTSJSONObject) : void,
|
||||
|
||||
setTabBar(locale : string, tabbar : string[]) : void,
|
||||
getTabBar(locale : string) : string[],
|
||||
// 可用的语言环境列表。
|
||||
availableLocales : string[],
|
||||
// 可用的功能列表。
|
||||
availabilities : Availabilities
|
||||
}
|
||||
371
uni_modules/ak-i18n/common/util.uts
Normal file
371
uni_modules/ak-i18n/common/util.uts
Normal file
@@ -0,0 +1,371 @@
|
||||
// @ts-nocheck
|
||||
/* @flow */
|
||||
|
||||
/**
|
||||
* constants
|
||||
*/
|
||||
import { errorMessages } from './errors'
|
||||
import { warnMessages } from './warnings'
|
||||
export const numberFormatKeys = [
|
||||
'compactDisplay',
|
||||
'currency',
|
||||
'currencyDisplay',
|
||||
'currencySign',
|
||||
'localeMatcher',
|
||||
'notation',
|
||||
'numberingSystem',
|
||||
'signDisplay',
|
||||
'style',
|
||||
'unit',
|
||||
'unitDisplay',
|
||||
'useGrouping',
|
||||
'minimumIntegerDigits',
|
||||
'minimumFractionDigits',
|
||||
'maximumFractionDigits',
|
||||
'minimumSignificantDigits',
|
||||
'maximumSignificantDigits'
|
||||
]
|
||||
|
||||
export const dateTimeFormatKeys = [
|
||||
'dateStyle',
|
||||
'timeStyle',
|
||||
'calendar',
|
||||
'localeMatcher',
|
||||
"hour12",
|
||||
"hourCycle",
|
||||
"timeZone",
|
||||
"formatMatcher",
|
||||
'weekday',
|
||||
'era',
|
||||
'year',
|
||||
'month',
|
||||
'day',
|
||||
'hour',
|
||||
'minute',
|
||||
'second',
|
||||
'timeZoneName',
|
||||
]
|
||||
|
||||
/**
|
||||
* utilities
|
||||
*/
|
||||
|
||||
export function getAllKeys(map:Map<string, UTSJSONObject>):string[] {
|
||||
let keys:string[] = []
|
||||
map.forEach((_, key) => {
|
||||
keys.push(key)
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印警告信息
|
||||
* @param {string} msg - 警告信息
|
||||
* @param {Error} err - 可选的错误对象
|
||||
*/
|
||||
export function warn(msg : string, code:number = -1) {
|
||||
if(process.env.NODE_ENV !== 'production') {
|
||||
console.warn(`[uvue-i18n] : ${code!=-1?warnMessages.get(code):msg}`)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 打印错误信息
|
||||
* @param {string} msg - 错误信息
|
||||
* @param {Error} err - 可选的错误对象
|
||||
*/
|
||||
export function error(code: number, msg : string|null = null) {
|
||||
if(process.env.NODE_ENV !== 'production') {
|
||||
console.error(`[uvue-i18n] : ${msg ?? errorMessages.get(code)}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function isArray(value : any) : boolean {
|
||||
return Array.isArray(value)
|
||||
}
|
||||
/**
|
||||
* 判断一个值是否为对象
|
||||
* @param {mixed} obj - 需要判断的值
|
||||
* @returns {boolean} - 如果值为对象,则返回 true,否则返回 false
|
||||
*/
|
||||
export function isObject(obj : any | null) : boolean {
|
||||
return obj != null && typeof obj == 'object'
|
||||
}
|
||||
/**
|
||||
* 判断一个值是否为布尔值
|
||||
* @param {mixed} val - 需要判断的值
|
||||
* @returns {boolean} - 如果值为布尔值,则返回 true,否则返回 false
|
||||
*/
|
||||
export function isBoolean(val : any) : boolean {
|
||||
return typeof val == 'boolean'
|
||||
}
|
||||
/**
|
||||
* 判断一个值是否为字符串
|
||||
* @param {mixed} val - 需要判断的值
|
||||
* @returns {boolean} - 如果值为字符串,则返回 true,否则返回 false
|
||||
*/
|
||||
export function isString(val : any) : boolean {
|
||||
return typeof val == 'string'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断一个值是否为普通对象
|
||||
* @param {any} obj - 需要判断的值
|
||||
* @returns {boolean} - 如果值为普通对象,则返回 true,否则返回 false
|
||||
*/
|
||||
export function isPlainObject(obj : any) : boolean {
|
||||
|
||||
// #ifndef APP-ANDROID || APP-IOS
|
||||
const toString = Object.prototype.toString
|
||||
const OBJECT_STRING : string = '[object Object]'
|
||||
return toString.call(obj) === OBJECT_STRING
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-ANDROID || APP-IOS
|
||||
return typeof obj == 'object' && obj instanceof UTSJSONObject
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断一个值是否为 null 或 undefined
|
||||
* @param {mixed} val - 需要判断的值
|
||||
* @returns {boolean} - 如果值为 null 或 undefined,则返回 true,否则返回 false
|
||||
*/
|
||||
// #ifndef APP-ANDROID || APP-IOS
|
||||
export function isNull(val : any | null | undefined) : boolean {
|
||||
return val == null || val == undefined
|
||||
}
|
||||
// #endif
|
||||
// #ifdef APP-ANDROID || APP-IOS
|
||||
export function isNull(val : any | null) : boolean {
|
||||
return val == null
|
||||
}
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 判断一个值是否为函数
|
||||
* @param {mixed} val - 需要判断的值
|
||||
* @returns {boolean} - 如果值为函数,则返回 true,否则返回 false
|
||||
*/
|
||||
export function isFunction(val : any) : boolean {
|
||||
return typeof val == 'function'
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析参数
|
||||
* @param {...mixed} args - 输入的参数
|
||||
* @returns {Object} - 包含 locale 和 params 的对象
|
||||
*/
|
||||
|
||||
export function parseArgs(...args : Array<any>) : Map<string, UTSJSONObject> {
|
||||
let locale : string | null = null
|
||||
let params : UTSJSONObject | null = null
|
||||
|
||||
if (args.length == 1) {
|
||||
if (isObject(args[0]) || isArray(args[0]) ) {
|
||||
params = args[0] //as UTSJSONObject
|
||||
} else if (typeof args[0] == 'string') {
|
||||
locale = args[0] as string
|
||||
}
|
||||
} else if (args.length == 2) {
|
||||
if (typeof args[0] == 'string') {
|
||||
locale = args[0] as string
|
||||
}
|
||||
if (isObject(args[1]) || isArray(args[1])) {
|
||||
params = args[1] //as UTSJSONObject
|
||||
}
|
||||
}
|
||||
if(locale == null || params == null) return new Map<string, UTSJSONObject>()
|
||||
return new Map([
|
||||
[locale, params]
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* looseClone 函数用于对一个对象进行浅拷贝。
|
||||
* 它通过将对象序列化为 JSON 字符串,然后再将其解析回对象来实现这一目的。
|
||||
* 请注意,这种方法仅适用于可序列化的对象,不适用于包含循环引用或特殊对象(如函数、Date 对象等)的对象。
|
||||
*
|
||||
* @param {Object} obj - 需要进行浅拷贝的对象。
|
||||
* @returns {Object} 返回一个新的对象,它是原始对象的浅拷贝。
|
||||
*/
|
||||
export function looseClone(obj : UTSJSONObject) : UTSJSONObject {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
}
|
||||
|
||||
/**
|
||||
* remove 函数用于从数组中删除指定的元素。
|
||||
* 如果成功删除元素,则返回修改后的数组;否则,不返回任何值。
|
||||
*
|
||||
* @param {Array} arr - 需要操作的数组。
|
||||
* @param {*} item - 需要删除的元素。
|
||||
* @returns {Array} 返回修改后的数组,或者不返回任何值。
|
||||
*/
|
||||
export function remove(arr : Set<any>, item : any) : Set<any> | null {
|
||||
if (arr.delete(item)) {
|
||||
return arr
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* arrayFrom 函数用于将类数组对象(如 Set 集合)转换为数组。
|
||||
*
|
||||
* @param {Set} arr - 需要转换的类数组对象。
|
||||
* @returns {Array} 返回一个新数组,其中包含原类数组对象的所有元素。
|
||||
*/
|
||||
export function arrayFrom(arr : Set<any>) : Array<any> {
|
||||
const ret : any[] = []
|
||||
arr.forEach(a => {
|
||||
ret.push(a)
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* includes 函数用于检查数组中是否包含指定的元素。
|
||||
*
|
||||
* @param {Array} arr - 需要检查的数组。
|
||||
* @param {*} item - 需要查找的元素。
|
||||
* @returns {boolean} 如果数组中包含指定元素,则返回 true,否则返回 false。
|
||||
*/
|
||||
export function includes(arr : Array<any>, item : any) : boolean {
|
||||
return arr.indexOf(item)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* hasOwn 函数用于检查对象是否具有指定的属性。
|
||||
* 与直接使用 `obj.hasOwnProperty` 不同,此函数可以正确处理通过原型链继承的属性。
|
||||
*
|
||||
* @param {Object|Array} obj - 需要检查的对象或数组。
|
||||
* @param {string} key - 需要检查的属性名。
|
||||
* @returns {boolean} 如果对象具有指定的属性,则返回 true,否则返回 false。
|
||||
*/
|
||||
export function hasOwn(obj : UTSJSONObject, key : string) : boolean {
|
||||
return obj[key] != null
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* merge 函数用于合并多个对象。
|
||||
* 它会将源对象的所有可枚举属性值复制到目标对象。
|
||||
* 如果目标对象和源对象有相同的属性,且它们的属性值都是对象,则会递归地合并这两个属性值。
|
||||
*
|
||||
* @param {Object} target - 目标对象,将被合并的对象。
|
||||
* @returns {Object} 返回合并后的新对象。
|
||||
*/
|
||||
export function merge(...target : UTSJSONObject[]) : UTSJSONObject {
|
||||
return UTSJSONObject.assign(...target)
|
||||
// const output = Object(target)
|
||||
// for (let i = 1; i < arguments.length; i++) {
|
||||
// const source = arguments[i]
|
||||
// if (source !== undefined && source !== null) {
|
||||
// let key
|
||||
// for (key in source) {
|
||||
// if (hasOwn(source, key)) {
|
||||
// if (isObject(source[key])) {
|
||||
// output[key] = merge(output[key], source[key])
|
||||
// } else {
|
||||
// output[key] = source[key]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return output
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* looseEqual 函数用于比较两个值是否宽松相等。
|
||||
* 宽松相等意味着在比较时会进行类型转换,例如将字符串转换为数字。
|
||||
* 该函数可以处理对象、数组和其他基本数据类型的值。
|
||||
*
|
||||
* @param {any} a - 要比较的第一个值。
|
||||
* @param {any} b - 要比较的第二个值。
|
||||
* @returns {boolean} 如果两个值宽松相等,则返回 true,否则返回 false。
|
||||
*/
|
||||
export function looseEqual(a : any, b : any) : boolean {
|
||||
// 如果 a 和 b 严格相等,直接返回 true
|
||||
if (a == b) { return true }
|
||||
|
||||
// 检查 a 和 b 是否都是对象
|
||||
const isObjectA : boolean = isObject(a)
|
||||
const isObjectB : boolean = isObject(b)
|
||||
|
||||
// 如果 a 和 b 都是对象
|
||||
if (isObjectA && isObjectB) {
|
||||
try {
|
||||
// 检查 a 和 b 是否都是数组
|
||||
const isArrayA : boolean = Array.isArray(a)
|
||||
const isArrayB : boolean = Array.isArray(b)
|
||||
|
||||
// 如果 a 和 b 都是数组
|
||||
if (isArrayA && isArrayB) {
|
||||
// 比较它们的长度是否相等,以及它们的每个元素是否宽松相等
|
||||
return (a as any[]).length == (b as any[]).length && a.every((e : any, i : number) : boolean => {
|
||||
return looseEqual(e, b[i])
|
||||
})
|
||||
} else if (!isArrayA && !isArrayB) { // 如果 a 和 b 都不是数组
|
||||
// 比较它们的键的数量是否相等,以及对应的键对应的值是否宽松相等
|
||||
const keysA : Array<string> = UTSJSONObject.keys(a as UTSJSONObject)
|
||||
const keysB : Array<string> = UTSJSONObject.keys(b as UTSJSONObject)
|
||||
return keysA.length == keysB.length && keysA.every((key : string) : boolean => {
|
||||
const valueA = a[key]
|
||||
const valueB = b[key]
|
||||
if(valueA == null || valueB == null) {
|
||||
return false
|
||||
}
|
||||
return looseEqual(valueA, valueB)
|
||||
})
|
||||
} else {
|
||||
// 如果 a 和 b 类型不同(一个是数组,另一个不是),返回 false
|
||||
return false
|
||||
}
|
||||
} catch (e) {
|
||||
// 如果在比较过程中发生异常,返回 false
|
||||
return false
|
||||
}
|
||||
} else if (!isObjectA && !isObjectB) { // 如果 a 和 b 都不是对象
|
||||
// 尝试将它们转换为字符串并比较
|
||||
return `${a}` == `${b}`
|
||||
} else {
|
||||
// 如果 a 和 b 类型不同(一个是对象,另一个不是),返回 false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 对用户输入的原始文本进行 HTML 特殊字符转义,以降低 XSS 攻击的风险。
|
||||
* @param {string} rawText - 需要转义的原始用户输入文本。
|
||||
* @returns {string} 返回转义后的文本。
|
||||
*/
|
||||
function escapeHtml(rawText: string): string {
|
||||
return rawText
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 `parseArgs().params` 返回的所有提供的参数中转义 HTML 标签和特殊符号。
|
||||
* 此方法对 `params` 对象执行原地操作。
|
||||
*
|
||||
* @param {any} params - 从 `parseArgs().params` 提供的参数。
|
||||
* 可能是字符串数组或字符串到任意值的映射。
|
||||
* @returns {any} 返回被操纵过的 `params` 对象。
|
||||
*/
|
||||
export function escapeParams(params: UTSJSONObject|null): UTSJSONObject|null {
|
||||
if(params != null) {
|
||||
UTSJSONObject.keys(params).forEach(key => {
|
||||
if(typeof(params[key]) == 'string') {
|
||||
params[key] = escapeHtml(params[key])
|
||||
}
|
||||
})
|
||||
}
|
||||
return params
|
||||
}
|
||||
25
uni_modules/ak-i18n/common/warnings.uts
Normal file
25
uni_modules/ak-i18n/common/warnings.uts
Normal file
@@ -0,0 +1,25 @@
|
||||
type warnMessagesTypes = {
|
||||
FALLBACK_TO_ROOT: number
|
||||
NOT_FOUND_PARENT_SCOPE: number
|
||||
IGNORE_OBJ_FLATTEN: number
|
||||
DEPRECATE_TC: number
|
||||
}
|
||||
export const I18nWarnCodes:warnMessagesTypes = {
|
||||
// 使用根语言环境回退到{type} '{key}'
|
||||
FALLBACK_TO_ROOT: 8,
|
||||
// 未找到父作用域,使用全局作用域
|
||||
NOT_FOUND_PARENT_SCOPE: 9,
|
||||
// 忽略对象扁平化:'{key}'键具有字符串值
|
||||
IGNORE_OBJ_FLATTEN: 10,
|
||||
// 'tc'和'$tc'已在v10中被弃用,请使用't'或'$t'代替。'tc'和'$tc'将在v11中移除
|
||||
DEPRECATE_TC: 11
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const warnMessages : Map<number, string> = new Map<number, string>([
|
||||
[I18nWarnCodes.FALLBACK_TO_ROOT, `使用根语言环境回退到{type} '{key}'。`],
|
||||
[I18nWarnCodes.NOT_FOUND_PARENT_SCOPE, `未找到父作用域,使用全局作用域。`],
|
||||
[I18nWarnCodes.IGNORE_OBJ_FLATTEN, `忽略对象扁平化:'{key}'键具有字符串值。`],
|
||||
[I18nWarnCodes.DEPRECATE_TC, `'tc'和'$tc'已在v10中被弃用,请使用't'或'$t'代替。'tc'和'$tc'将在v11中移除。`],
|
||||
])
|
||||
13
uni_modules/ak-i18n/components/l-i18n-n/l-i18n-n.uvue
Normal file
13
uni_modules/ak-i18n/components/l-i18n-n/l-i18n-n.uvue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<text>
|
||||
<09>ݲ<EFBFBD>֧<EFBFBD><D6A7>
|
||||
</text>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
89
uni_modules/ak-i18n/components/lime-i18n/lime-i18n.uvue
Normal file
89
uni_modules/ak-i18n/components/lime-i18n/lime-i18n.uvue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<view>
|
||||
<text>测试:{{ $t('headMenus.userName') }}</text>
|
||||
<text>测试:{{ $t('common.hello', {msg: '隔壁老王'}) }}</text>
|
||||
<text>测试:{{ $t('message.link') }}</text>
|
||||
<text>测试:{{ $t('message.linkHelloName', {name: '隔壁老王'}) }}</text>
|
||||
<text>测试:{{ $t('message.linkLinkHelloName', {name: '隔壁老王'}) }}</text>
|
||||
<text>测试:{{ $t('message.linkEnd') }}</text>
|
||||
<text>测试:{{ $t('message.linkWithin') }}</text>
|
||||
<text>测试:{{ $t('message.linkMultiple') }}</text>
|
||||
<text>测试:{{ $t('message.linkBrackets') }}</text>
|
||||
<text>测试:{{ $t('message.linkHyphen') }}</text>
|
||||
<text>测试:{{ $t('message.linkUnderscore') }}</text>
|
||||
<text>测试:{{ $t('message.linkPipe') }}</text>
|
||||
<text>测试:{{ $t('message.linkList', ['数组值1', '数组值2']) }}</text>
|
||||
<text>测试:{{ $t('message.linkCaseLower') }}</text>
|
||||
<text>测试:{{ $t('message.linkCaseUpper') }}</text>
|
||||
<text>测试:{{ $t('message.linkCaseCapitalize') }}</text>
|
||||
<text>测试:{{ $t('message.linkCaseUnknown') }}</text>
|
||||
<text>测试:{{ $t('message.linkCaseCustom') }}</text>
|
||||
<text>测试:{{ $t('message.circular1') }}</text>
|
||||
<text>测试:{{ $t('message.linkTwice') }}</text>
|
||||
<text>测试:{{ $t('address', { account: 'foo', domain: 'domain.com' }) }}</text>
|
||||
<text>测试:{{ $t('message.linked') }}</text>
|
||||
<text>测试:{{ $t('message.missingHomeAddress') }}</text>
|
||||
<text>测试:{{ $t('message.custom_modifier') }}</text>
|
||||
<text>测试:{{ $t('headMenus.userName', {}) }}</text>
|
||||
<text>复数:{{ $tc('plurals.apple') }}</text>
|
||||
<text>复数:{{ $tc('plurals.apple', 0) }}</text>
|
||||
<text>复数:{{ $tc('plurals.apple', 1) }}</text>
|
||||
<text>复数:{{ $tc('plurals.apple', 10, { count: 10 }) }}</text>
|
||||
<text>复数:{{ $tc('plurals.apple', 10, { count: 'Many' }) }}</text>
|
||||
<text>复数:{{ $tc('plurals.apple', 10) }}</text>
|
||||
<text>复数:{{ $tc('plurals.car', 1) }}</text>
|
||||
<text>复数:{{ $tc('plurals.car', 2) }}</text>
|
||||
<text>复数自定义规则:{{ $tc('car', 1, 'ru') }}</text>
|
||||
<text>复数自定义规则:{{ $tc('car', 2, 'ru') }}</text>
|
||||
<text>复数自定义规则:{{ $tc('car', 4, 'ru') }}</text>
|
||||
<text>复数自定义规则:{{ $tc('car', 12, 'ru') }}</text>
|
||||
<text>复数自定义规则:{{ $tc('car', 21, 'ru') }}</text>
|
||||
|
||||
<text>复数自定义规则:{{ $tc('banana', 0, 'ru') }}</text>
|
||||
<text>复数自定义规则:{{ $tc('banana', 4, 'ru') }}</text>
|
||||
<text>复数自定义规则:{{ $tc('banana', 11, 'ru') }}</text>
|
||||
<text>复数自定义规则:{{ $tc('banana', 31, 'ru') }}</text>
|
||||
<text>
|
||||
<text v-for="item in $i18n.availableLocales">{{item}}</text>
|
||||
</text>
|
||||
|
||||
<text>{{ $d(new Date(), 'short') }}</text>
|
||||
<text>{{ $d(new Date(), 'long', 'zh-CN') }}</text>
|
||||
<text>{{ $n(10000, 'currency') }}</text>
|
||||
<text>{{ $n(10000, 'currency', 'zh-CN') }}</text>
|
||||
<text>{{ $n(10000, 'currency', 'zh-CN', { useGrouping: false }) }}</text>
|
||||
<text>{{ $n(987654321, 'currency', { notation: 'compact' }) }}</text>
|
||||
<text>{{ $n(0.99123, 'percent') }}</text>
|
||||
<text>{{ $n(0.99123, 'percent', { minimumFractionDigits: 2 }) }}</text>
|
||||
<text>{{ $n(12.11612345, 'decimal') }}</text>
|
||||
<text>{{ $n(12145281111, 'decimal', 'zh-CN') }}</text>
|
||||
<!-- <button @click="onClick">切换{{locale}}</button> -->
|
||||
<!-- <button @click="$locale.value = $locale.value != 'zh-CN' ? 'zh-CN' : 'en-US'">切换,当前:{{$locale}}</button> -->
|
||||
<button @click="onClick">切换,当前:{{$locale}}</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from '@/uni_modules/lime-i18n';
|
||||
// import * as test from '@/uni_modules/lime-i18n/message-compiler/test'
|
||||
// import * as test from '@/uni_modules/lime-i18n/common/test'
|
||||
|
||||
|
||||
import { getCurrentInstance } from 'vue'
|
||||
const context = getCurrentInstance()
|
||||
// 局部国际化
|
||||
const {locale, setLocaleMessage} = useI18n()
|
||||
|
||||
const onClick = () => {
|
||||
if(context == null) return
|
||||
context.proxy!.$locale.value = context.proxy!.$locale.value != 'zh-CN' ? 'zh-CN' : 'en-US'
|
||||
// context!.proxy!.$locale.value = 'zh-CN' ? 'zh-CN' : 'en-US'
|
||||
// locale.value = locale.value != 'zh-CN' ? 'zh-CN' : 'en-US'
|
||||
// console.log(`$i18n.t`, context?.proxy?.$i18n.t('headMenus.userName', {}))
|
||||
console.log(`$i18n.t`, context!.proxy!.$t('headMenus.userName'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
1
uni_modules/ak-i18n/index.uts
Normal file
1
uni_modules/ak-i18n/index.uts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './common'
|
||||
87
uni_modules/ak-i18n/package.json
Normal file
87
uni_modules/ak-i18n/package.json
Normal file
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"id": "ak-i18n",
|
||||
"displayName": "ak-i18n",
|
||||
"version": "0.0.8",
|
||||
"description": "lime-i18n 系参考 vue-i18n 实现的 UTS 国际化插件",
|
||||
"keywords": [
|
||||
"lime-i18n",
|
||||
"vue-i18n",
|
||||
"uts",
|
||||
"国际化",
|
||||
"多语言"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^4.21"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "uts",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "u",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-android": "y",
|
||||
"app-ios": "y",
|
||||
"app-harmony": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "u",
|
||||
"Android Browser": "u",
|
||||
"微信浏览器(Android)": "u",
|
||||
"QQ浏览器(Android)": "u"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "u",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
549
uni_modules/ak-i18n/readme.md
Normal file
549
uni_modules/ak-i18n/readme.md
Normal file
@@ -0,0 +1,549 @@
|
||||
# lime-i18n 国际化
|
||||
- 参考vue-i18n实现的uts国际化插件
|
||||
|
||||
## 文档
|
||||
[i18n](https://limex.qcoon.cn/native/i18n.html)
|
||||
|
||||
## 安装
|
||||
在插件市场导入即可
|
||||
|
||||
## 基础使用
|
||||
|
||||
```js
|
||||
// main.uts
|
||||
import { createI18n } from '@/uni_modules/lime-i18n'
|
||||
|
||||
//目录自己决定
|
||||
import zhCN from './locales/zh-CN'
|
||||
import enUS from './locales/en_US'
|
||||
|
||||
const i18n = createI18n({
|
||||
// 使用uni.getStorageSync('uVueI18nLocale') 能获取上次退出应用后保存的语言
|
||||
locale: 'zh-CN', // 默认显示语言
|
||||
fallbackLocale: 'en-US',
|
||||
messages: {
|
||||
'zh-CN': zhCN,
|
||||
'en-US': enUS
|
||||
}
|
||||
})
|
||||
|
||||
export function createApp(){
|
||||
const app = createSSRApp(App);
|
||||
app.use(i18n)
|
||||
//....
|
||||
}
|
||||
```
|
||||
|
||||
### 切换语言
|
||||
|
||||
使用创建的`i18n`切换
|
||||
```js
|
||||
// 假设在这个文件里使用createI18n创建i18n,创建步骤就是上面 基础使用
|
||||
import i18n from 'xxx/locales';
|
||||
|
||||
i18n.global.locale.value = 'zh-CN'
|
||||
```
|
||||
|
||||
模板中
|
||||
```html
|
||||
<template>
|
||||
<view>
|
||||
<text>测试:{{ $t('headMenus.userName') }}</text>
|
||||
<button @click="$locale.value = $locale.value != 'zh-CN' ? 'zh-CN' : 'en-US'">切换{{$locale}}</button>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
|
||||
|
||||
选项式API
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$locale.value = this.$locale.value != 'zh-CN' ? 'zh-CN' : 'en-US'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
组合式API
|
||||
```js
|
||||
import { getCurrentInstance } from 'vue'
|
||||
const instance = getCurrentInstance()!
|
||||
const onClick = () => {
|
||||
if(instance == null) return
|
||||
instance.proxy!.$locale.value = instance.proxy!.$locale.value != 'zh-CN' ? 'zh-CN' : 'en-US'
|
||||
}
|
||||
```
|
||||
|
||||
### 延迟加载
|
||||
直接调用创建的
|
||||
```js
|
||||
// 模拟请求后端接口返回
|
||||
setTimeout(() => {
|
||||
// 直接调用创建的
|
||||
i18n.global.setLocaleMessage('zh-CN', zhCN)
|
||||
}, 5000)
|
||||
```
|
||||
|
||||
选项组合API
|
||||
```js
|
||||
// 模拟请求后端接口返回
|
||||
setTimeout(() => {
|
||||
this.$i18n.global.setLocaleMessage('zh-CN', zhCN)
|
||||
}, 5000)
|
||||
```
|
||||
|
||||
组合式API
|
||||
```js
|
||||
// 模拟请求后端接口返回
|
||||
import { getCurrentInstance } from 'vue'
|
||||
const instance = getCurrentInstance()
|
||||
setTimeout(() => {
|
||||
instance.proxy!.$i18n.global.setLocaleMessage('zh-CN', zhCN)
|
||||
}, 5000)
|
||||
```
|
||||
|
||||
## 格式化
|
||||
|
||||
### 具名插值
|
||||
语言环境信息如下:
|
||||
```js
|
||||
const messages = {
|
||||
en: {
|
||||
message: {
|
||||
hello: '{msg} world'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
模板如下:
|
||||
```html
|
||||
<text>{{ $t('message.hello', { msg: 'hello' }) }}</text>
|
||||
```
|
||||
输出如下:
|
||||
```html
|
||||
<text>hello world</text>
|
||||
```
|
||||
|
||||
### 列表插值
|
||||
语言环境信息如下:
|
||||
|
||||
```js
|
||||
const messages = {
|
||||
en: {
|
||||
message: {
|
||||
hello: '{0} world'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
模板如下:
|
||||
```html
|
||||
<text>{{ $t('message.hello', ['hello']) }}</text>
|
||||
```
|
||||
输出如下:
|
||||
```html
|
||||
|
||||
<text>hello world</text>
|
||||
```
|
||||
|
||||
### 链接插值
|
||||
如果有一个翻译关键字总是与另一个具有相同的具体文本,你可以链接到它。要链接到另一个翻译关键字,你所要做的就是在其内容前加上一个` @: `符号后跟完整的翻译键名,包括你要链接到的命名空间。<br>
|
||||
语言环境信息如下:
|
||||
```js
|
||||
const messages = {
|
||||
en: {
|
||||
message: {
|
||||
the_world: 'the world',
|
||||
dio: 'DIO:',
|
||||
linked: '@:message.dio @:message.the_world !!!!'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
模板如下:
|
||||
```html
|
||||
<text>{{ $t('message.linked') }}</text>
|
||||
```
|
||||
输出如下:
|
||||
```html
|
||||
<text>DIO: the world !!!!</text>
|
||||
```
|
||||
#### 内置修饰符
|
||||
如果语言区分字符大小写,则可能需要控制链接的区域设置消息的大小写。链接邮件可以使用修饰符` @.modifier:key` 进行格式化<br><br>
|
||||
|
||||
以下修饰符当前可用
|
||||
- `upper:` 链接消息中的所有字符均大写
|
||||
- `lower:` 小写链接消息中的所有字符
|
||||
- `capitalize:` 大写链接消息中的第一个字符
|
||||
|
||||
语言环境信息如下:
|
||||
```js
|
||||
const messages = {
|
||||
en: {
|
||||
message: {
|
||||
homeAddress: 'Home address',
|
||||
missingHomeAddress: 'Please provide @.lower:message.homeAddress'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
模板如下:
|
||||
```html
|
||||
<text>{{ $t('message.homeAddress') }}</text>
|
||||
<text class="error">{{ $t('message.missingHomeAddress') }}</text
|
||||
```
|
||||
输出如下:
|
||||
```html
|
||||
<text>Home address</text>
|
||||
<text class="error">Please provide home address</text>
|
||||
```
|
||||
|
||||
### 自定义修饰符
|
||||
如果要使用非内置修饰符,则可以使用自定义修饰符。
|
||||
```js
|
||||
const i18n = createI18n({
|
||||
locale: 'en',
|
||||
messages: {
|
||||
// set something locale messages ...
|
||||
},
|
||||
// set custom modifiers at `modifiers` option
|
||||
modifiers: {
|
||||
snakeCase: (str:string):string => str.split(' ').join('_')
|
||||
}
|
||||
})
|
||||
```
|
||||
区域设置消息如下:
|
||||
```js
|
||||
const messages = {
|
||||
en: {
|
||||
message: {
|
||||
snake: 'snake case',
|
||||
custom_modifier: "custom modifiers example: @.snakeCase:{'message.snake'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 复数
|
||||
你可以使用复数进行翻译。你必须定义具有管道 | 分隔符的语言环境,并在管道分隔符中定义复数。<br>
|
||||
|
||||
*您的模板将需要使用 `$tc()` 而不是 `$t()`<br>
|
||||
语言环境信息如下:
|
||||
```js
|
||||
const messages = {
|
||||
en: {
|
||||
car: 'car | cars',
|
||||
apple: 'no apples | one apple | {count} apples'
|
||||
}
|
||||
}
|
||||
```
|
||||
模板如下:
|
||||
```html
|
||||
<text>{{ $tc('car', 1) }}</text>
|
||||
<text>{{ $tc('car', 2) }}</text>
|
||||
|
||||
<text>{{ $tc('apple', 0) }}</text>
|
||||
<text>{{ $tc('apple', 1) }}</text>
|
||||
<text>{{ $tc('apple', 10, { count: 10 }) }}</text>
|
||||
```
|
||||
|
||||
输出如下:
|
||||
```html
|
||||
<text>car</text>
|
||||
<text>cars</text>
|
||||
|
||||
<text>no apples</text>
|
||||
<text>one apple</text>
|
||||
<text>10 apples</text>
|
||||
```
|
||||
|
||||
### 通过预定义的参数访问该数字
|
||||
你无需明确指定复数的数字。可以通过预定义的命名参数 `{count}` 和/或 `{n}` 在语言环境信息中访问该数字。如有必要,你可以覆盖这些预定义的命名参数。<br>
|
||||
语言环境信息如下:
|
||||
|
||||
```js
|
||||
const messages = {
|
||||
en: {
|
||||
apple: 'no apples | one apple | {count} apples',
|
||||
banana: 'no bananas | {n} banana | {n} bananas'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
模板如下:
|
||||
```html
|
||||
<text>{{ $tc('apple', 10, { count: 10 }) }}</text>
|
||||
<text>{{ $tc('apple', 10) }}</text>
|
||||
|
||||
<text>{{ $tc('banana', 1, { n: 1 }) }}</text>
|
||||
<text>{{ $tc('banana', 1) }}</text>
|
||||
<text>{{ $tc('banana', 100, { n: 'too many' }) }}</text>
|
||||
```
|
||||
|
||||
输出如下:
|
||||
```html
|
||||
<text>10 apples</text>
|
||||
<text>10 apples</text>
|
||||
|
||||
<text>1 banana</text>
|
||||
<text>1 banana</text>
|
||||
<text>too many bananas</text>
|
||||
```
|
||||
|
||||
### 自定义复数
|
||||
但是,这种多元化并不适用于所有语言(例如,斯拉夫语言具有不同的多元化规则)。
|
||||
|
||||
为了实现这些规则,您可以将可选的 `pluralizationRules` 对象传递给 `UvueI18n` 构造函数选项。
|
||||
|
||||
使用针对斯拉夫语言(俄语,乌克兰语等)的规则的非常简化的示例:
|
||||
|
||||
```js
|
||||
function customRule(choice:number, choicesLength:number):number {
|
||||
if (choice == 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const teen = choice > 10 && choice < 20
|
||||
const endsWithOne = choice % 10 == 1
|
||||
if (!teen && endsWithOne) {
|
||||
return 1
|
||||
}
|
||||
if (!teen && choice % 10 >= 2 && choice % 10 <= 4) {
|
||||
return 2
|
||||
}
|
||||
|
||||
return choicesLength < 4 ? 2 : 3
|
||||
}
|
||||
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: 'ru',
|
||||
// the custom rules here ...
|
||||
pluralizationRules: {
|
||||
ru: customRule
|
||||
},
|
||||
messages: {
|
||||
ru: {
|
||||
car: '0 машин | {n} машина | {n} машины | {n} машин',
|
||||
banana: 'нет бананов | {n} банан | {n} банана | {n} бананов'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
这将有效地实现以下目的:
|
||||
```html
|
||||
<view>Car:</view>
|
||||
<text>{{ $tc('car', 1) }}</text>
|
||||
<text>{{ $tc('car', 2) }}</text>
|
||||
<text>{{ $tc('car', 4) }}</text>
|
||||
<text>{{ $tc('car', 12) }}</text>
|
||||
<text>{{ $tc('car', 21) }}</text>
|
||||
|
||||
<view>Banana:</view>
|
||||
<text>{{ $tc('banana', 0) }}</text>
|
||||
<text>{{ $tc('banana', 4) }}</text>
|
||||
<text>{{ $tc('banana', 11) }}</text>
|
||||
<text>{{ $tc('banana', 31) }}</text>
|
||||
```
|
||||
|
||||
结果如下:
|
||||
```html
|
||||
<view>Car:</view>
|
||||
<text>1 машина</text>
|
||||
<text>2 машины</text>
|
||||
<text>4 машины</text>
|
||||
<text>12 машин</text>
|
||||
<text>21 машина</text>
|
||||
|
||||
<view>Banana:</view>
|
||||
<text>нет бананов</text>
|
||||
<text>4 банана</text>
|
||||
<text>11 бананов</text>
|
||||
<text>31 банан</text>
|
||||
```
|
||||
|
||||
## ~~切换tabBar文本~~
|
||||
~~这是`UvueI18n`独有的功能,方便切换`tabBar`上的文本,暂定这个规则~~,目前没有找到可以判断当前页面是不为tabbar页的方法,暂时不推荐使用。如果你有能获取到当前页面为tabbar页的方式可以告诉我。
|
||||
|
||||
```js
|
||||
const i18n = createI18n({
|
||||
tabBars: {
|
||||
'en-US': ['home','User Center'],
|
||||
'zh-CN': ['首页','用户中心'],
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 日期时间本地化
|
||||
由于APP不支持`Intl.DateTimeFormat`,故这功能无法在APP上使用。
|
||||
|
||||
日期时间格式如下:
|
||||
```js
|
||||
const datetimeFormats = {
|
||||
'en-US': {
|
||||
short: {
|
||||
year: 'numeric', month: 'short', day: 'numeric'
|
||||
},
|
||||
long: {
|
||||
year: 'numeric', month: 'short', day: 'numeric',
|
||||
weekday: 'short', hour: 'numeric', minute: 'numeric'
|
||||
}
|
||||
},
|
||||
'zh-CN': {
|
||||
short: {
|
||||
year: 'numeric', month: 'short', day: 'numeric'
|
||||
},
|
||||
long: {
|
||||
year: 'numeric', month: 'short', day: 'numeric',
|
||||
weekday: 'short', hour: 'numeric', minute: 'numeric', hour12: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如上,你可以定义具名的 (例如:short、long 等) 日期时间格式,并需要使用 [ECMA-402 Intl.DateTimeFormat 的选项](http://www.ecma-international.org/ecma-402/2.0/#sec-intl-datetimeformat-constructor)。
|
||||
|
||||
之后就像语言环境信息一样,你需要指定 `UvueI18n` 构造函数的 `dateTimeFormats` 选项:
|
||||
```js
|
||||
const i18n = createI18n({
|
||||
datetimeFormats
|
||||
})
|
||||
```
|
||||
模板如下:
|
||||
```html
|
||||
<text>{{ $d(new Date(), 'short') }}</text>
|
||||
<text>{{ $d(new Date(), 'long', 'zh-CN') }}</text>
|
||||
```
|
||||
|
||||
第一个参数是日期时间可用值(例如,timestamp)作为参数,第二个参数是日期时间格式名称作为参数。最后一个参数 locale 值作为参数。`Date`
|
||||
```html
|
||||
<text>Jun 30, 2024</text>
|
||||
<text>2024年6月30日周日 下午6:35</text>
|
||||
```
|
||||
|
||||
## 数字格式
|
||||
由于APP不支持`Intl.NumberFormat`,故这功能无法在APP上使用。
|
||||
|
||||
你可以使用你定义的格式来本地化数字。
|
||||
```js
|
||||
const numberFormats = {
|
||||
'en-US': {
|
||||
currency: {
|
||||
style: 'currency', currency: 'USD', notation: 'standard'
|
||||
},
|
||||
decimal: {
|
||||
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
|
||||
},
|
||||
percent: {
|
||||
style: 'percent', useGrouping: false
|
||||
}
|
||||
},
|
||||
'zh-CN': {
|
||||
currency: {
|
||||
style: 'currency', currency: 'CNY', useGrouping: true, currencyDisplay: 'symbol'
|
||||
},
|
||||
decimal: {
|
||||
style: 'decimal', minimumSignificantDigits: 3, maximumSignificantDigits: 5
|
||||
},
|
||||
percent: {
|
||||
style: 'percent', useGrouping: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
如上,你可以指定具名的 (例如:currency 等) 的数字格式,并且需要使用 [ECMA-402 Intl.NumberFormat 的选项](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat)。
|
||||
|
||||
之后,在使用区域设置消息时,您需要指定以下选项:`numberFormatscreateI18n`
|
||||
```js
|
||||
const i18n = createI18n({
|
||||
numberFormats
|
||||
})
|
||||
```
|
||||
以下是在模板中使用的示例:`$n`
|
||||
```html
|
||||
<text>{{ $n(10000, 'currency') }}</text>
|
||||
<text>{{ $n(10000, 'currency', 'zh-CN') }}</text>
|
||||
<text>{{ $n(10000, 'currency', 'zh-CN', { useGrouping: false }) }}</text>
|
||||
<text>{{ $n(987654321, 'currency', { notation: 'compact' }) }}</text>
|
||||
<text>{{ $n(0.99123, 'percent') }}</text>
|
||||
<text>{{ $n(0.99123, 'percent', { minimumFractionDigits: 2 }) }}</text>
|
||||
<text>{{ $n(12.11612345, 'decimal') }}</text>
|
||||
<text>{{ $n(12145281111, 'decimal', 'zh-CN') }}</text>
|
||||
```
|
||||
|
||||
第一个参数是作为参数的数值,第二个参数是作为参数的数字格式名称。最后一个参数 locale 值作为参数。
|
||||
|
||||
结果如下:
|
||||
```html
|
||||
<text>$10,000.00</text>
|
||||
<text>¥10,000.00</text>
|
||||
<text>¥10,000.00</text>
|
||||
<text>$988M</text>
|
||||
<text>99%</text>
|
||||
<text>99.12%</text>
|
||||
<text>12.12</text>
|
||||
<text>12,145,000,000</text>
|
||||
```
|
||||
|
||||
## API
|
||||
获取方法 传参 `locale`,设置方法 传参`locale` `format`
|
||||
|
||||
例如:
|
||||
```js
|
||||
// 可以手动引入i18n,例如你单独文件创建了i18n,就可以导入这个文件使用i18n
|
||||
i18n.global.setLocaleMessage('zh-CN', zhCN)
|
||||
i18n.global.getLocaleMessage('zh-CN')
|
||||
|
||||
// 如果是选择项API 可以使用
|
||||
this.$i18n.global.setLocaleMessage
|
||||
|
||||
// 如果是组合式API 可以先获取当前组件实例
|
||||
import { getCurrentInstance } from 'vue'
|
||||
const instance = getCurrentInstance()
|
||||
instance.proxy!.$i18n.global.setLocaleMessage
|
||||
```
|
||||
|
||||
### setLocaleMessage
|
||||
### getLocaleMessage
|
||||
### mergeLocaleMessage
|
||||
### getDateTimeFormat
|
||||
### setDateTimeFormat
|
||||
### mergeDateTimeFormat
|
||||
### getNumberFormat
|
||||
### setNumberFormat
|
||||
### mergeNumberFormat
|
||||
### setTabBar
|
||||
### getTabBar
|
||||
|
||||
<!-- ## useI18n
|
||||
创建一个小范围的i18n,例如在当前页面下使用,本功能未实测过,因为`useI18n`导出的方法需要写全参数,不能省略。不建议使用。
|
||||
|
||||
```html
|
||||
<text>{{ t('headMenus.userName') }}</text>
|
||||
```
|
||||
|
||||
```js
|
||||
import { useI18n } from '@/uni_modules/lime-i18n';
|
||||
const {locale, setLocaleMessage, t} = useI18n()
|
||||
``` -->
|
||||
|
||||
|
||||
|
||||
## 打赏
|
||||
|
||||
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
|
||||

|
||||

|
||||
9
uni_modules/ak-i18n/static/data.json
Normal file
9
uni_modules/ak-i18n/static/data.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"zh-CN": {
|
||||
"code": "CNY",
|
||||
"num": "156",
|
||||
"nativeName": "人民币",
|
||||
"englishName": "Chinese Yuan",
|
||||
"symbol": "¥"
|
||||
}
|
||||
}
|
||||
112
uni_modules/ak-i18n/test/index.uts
Normal file
112
uni_modules/ak-i18n/test/index.uts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { createI18n } from '@/uni_modules/lime-i18n/index.uts'
|
||||
// import { createI18n, setLocaleMessage } from '../index.uts'
|
||||
|
||||
import zhCN from './locales/zh-CN'
|
||||
import enUS from './locales/en_US'
|
||||
|
||||
|
||||
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: uni.getStorageSync('uVueI18nLocale').toString().length != 0 ? uni.getStorageSync('uVueI18nLocale') : 'zh-CN', // 默认显示语言
|
||||
fallbackLocale: 'en-US',
|
||||
// Key - 在这种情况下,用于规则 `'ru'` 的语言
|
||||
// Value - 选择正确的复数形式的功能
|
||||
pluralizationRules: {
|
||||
/**
|
||||
* @param choice {number} 输入给$的选择索引 $tc:`$tc('path.to.rule', choiceIndex)`
|
||||
* @param choicesLength {number} 可用选择总数
|
||||
* @returns 最终选择索引以选择复数单词
|
||||
*/
|
||||
'ru': function (choice : number, choicesLength : number) : number {
|
||||
if (choice == 0) {
|
||||
return 0;
|
||||
}
|
||||
const teen = choice > 10 && choice < 20;
|
||||
const endsWithOne = (choice % 10) == 1;
|
||||
|
||||
if (choicesLength < 4) {
|
||||
return (!teen && endsWithOne) ? 1 : 2;
|
||||
}
|
||||
if (!teen && endsWithOne) {
|
||||
return 1;
|
||||
}
|
||||
if (!teen && (choice % 10) >= 2 && (choice % 10) <= 4) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return (choicesLength < 4) ? 2 : 3;
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
'zh-CN': zhCN,
|
||||
'en-US': enUS,
|
||||
'ru': {
|
||||
car: '0 машин | {n} машина | {n} машины | {n} машин',
|
||||
banana: 'нет бананов | {n} банан | {n} банана | {n} бананов'
|
||||
}
|
||||
},
|
||||
modifiers: {
|
||||
snakeCase: (str : string) : string => str.split(' ').join('_')
|
||||
},
|
||||
numberFormats: {
|
||||
'en-US': {
|
||||
currency: {
|
||||
style: 'currency', currency: 'USD', notation: 'standard'
|
||||
},
|
||||
decimal: {
|
||||
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
|
||||
},
|
||||
percent: {
|
||||
style: 'percent', useGrouping: false
|
||||
}
|
||||
},
|
||||
'zh-CN': {
|
||||
currency: {
|
||||
style: 'currency', currency: 'CNY', useGrouping: true, currencyDisplay: 'symbol'
|
||||
},
|
||||
decimal: {
|
||||
style: 'decimal', minimumSignificantDigits: 3, maximumSignificantDigits: 5
|
||||
},
|
||||
percent: {
|
||||
style: 'percent', useGrouping: false
|
||||
}
|
||||
}
|
||||
},
|
||||
datetimeFormats: {
|
||||
'en-US': {
|
||||
short: {
|
||||
year: 'numeric', month: 'short', day: 'numeric'
|
||||
},
|
||||
long: {
|
||||
year: 'numeric', month: 'short', day: 'numeric',
|
||||
weekday: 'short', hour: 'numeric', minute: 'numeric'
|
||||
}
|
||||
},
|
||||
'zh-CN': {
|
||||
short: {
|
||||
year: 'numeric', month: 'short', day: 'numeric'
|
||||
},
|
||||
long: {
|
||||
year: 'numeric', month: 'short', day: 'numeric',
|
||||
weekday: 'short', hour: 'numeric', minute: 'numeric', hour12: true
|
||||
}
|
||||
}
|
||||
},
|
||||
tabBars: {
|
||||
'en-US': ['home','User Center'],
|
||||
'zh-CN': ['首页','用户中心'],
|
||||
}
|
||||
})
|
||||
|
||||
export default i18n
|
||||
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
// console.log('getLocale:::', uni.getLocale())
|
||||
console.log('getLocale:::',typeof uni.getStorageSync('lllluVueI18nLocale'))
|
||||
// console.log('i18n install', i18n.global)
|
||||
// setLocaleMessage('zh-CN', zhCN)
|
||||
i18n.global.locale.value = 'zh-CN'
|
||||
}, 5000)
|
||||
0
uni_modules/ak-i18n/test/locales/en_US.fixed.json
Normal file
0
uni_modules/ak-i18n/test/locales/en_US.fixed.json
Normal file
0
uni_modules/ak-i18n/test/locales/en_US.json
Normal file
0
uni_modules/ak-i18n/test/locales/en_US.json
Normal file
66
uni_modules/ak-i18n/test/locales/en_US.uts
Normal file
66
uni_modules/ak-i18n/test/locales/en_US.uts
Normal file
@@ -0,0 +1,66 @@
|
||||
export default {
|
||||
common: {
|
||||
more: "Look More",
|
||||
hello: '{msg} world'
|
||||
},
|
||||
leftMenus: {
|
||||
// "/": "Home",
|
||||
// Home: "Home",
|
||||
home: "Home",
|
||||
},
|
||||
headMenus: {
|
||||
"subTitle": "Organization service platform",
|
||||
"userName": "ZhangSan"
|
||||
},
|
||||
login: {
|
||||
"personal_center": "personal center",
|
||||
"sign_out": "sign out"
|
||||
},
|
||||
plurals: {
|
||||
car: 'car | cars',
|
||||
apple: 'no apples | one apple | {count} apples',
|
||||
format: {
|
||||
named: 'Hello {name}, how are you? | Hi {name}, you look fine',
|
||||
list: 'Hello {0}, how are you? | Hi {0}, you look fine'
|
||||
},
|
||||
fallback: 'this is fallback | this is a plural fallback'
|
||||
},
|
||||
message: {
|
||||
hello: 'the world',
|
||||
helloName: 'Hello {name}',
|
||||
hoge: 'hoge',
|
||||
link: '@:message.hello',
|
||||
linkHelloName: '@:message.helloName',
|
||||
linkLinkHelloName: '@:message.linkHelloName',
|
||||
linkEnd: 'This is a linked translation to @:message.hello',
|
||||
linkWithin: 'Isn\'t @:message.hello we live in great?',
|
||||
linkMultiple: 'Hello @:message.hoge!, isn\'t @:message.hello great?',
|
||||
linkBrackets: 'Hello @:(message.hoge). Isn\'t @:(message.hello) great?',
|
||||
linkHyphen: '@:hyphen-hello',
|
||||
linkUnderscore: '@:underscore_hello',
|
||||
linkPipe: '@:pipe|hello',
|
||||
linkColon: '@:(colon:hello)',
|
||||
linkList: '@:message.hello: {0} {1}',
|
||||
linkCaseLower: 'Please provide @.lower:message.homeAddress',
|
||||
linkCaseUpper: '@.upper:message.homeAddress',
|
||||
linkCaseCapitalize: '@.capitalize:message.homeAddress',
|
||||
linkCaseUnknown: '@.unknown:message.homeAddress',
|
||||
linkCaseCustom: '@.custom:message.homeAddress',
|
||||
homeAddress: 'home Address',
|
||||
circular1: 'Foo @:message.circular2',
|
||||
circular2: 'Bar @:message.circular3',
|
||||
circular3: 'Buz @:message.circular1',
|
||||
linkTwice: '@:message.hello: @:message.hello',
|
||||
the_world: 'the world',
|
||||
dio: 'DIO:',
|
||||
linked: '@:message.dio @:message.the_world !!!!',
|
||||
missingHomeAddress: 'Please provide @.lower:message.homeAddress',
|
||||
snake: 'snake case',
|
||||
custom_modifier: "custom modifiers example: @.snakeCase:{'message.snake'}"
|
||||
},
|
||||
address: "{account}{'@'}{domain}",
|
||||
'hyphen-hello': 'hyphen the wolrd',
|
||||
underscore_hello: 'underscore the wolrd',
|
||||
// 'colon:hello': 'hello colon',
|
||||
// 'pipe|hello': 'hello pipe',
|
||||
};
|
||||
53
uni_modules/ak-i18n/test/locales/zh-CN.uts
Normal file
53
uni_modules/ak-i18n/test/locales/zh-CN.uts
Normal file
@@ -0,0 +1,53 @@
|
||||
export default {
|
||||
common: {
|
||||
more: "查看更多",
|
||||
hello: "{msg} 世界"
|
||||
},
|
||||
leftMenus: {
|
||||
home: "首页"
|
||||
},
|
||||
headMenus: {
|
||||
subTitle: "组织服务平台",
|
||||
userName: "张三"
|
||||
},
|
||||
login: {
|
||||
personal_center: "个人中心",
|
||||
sign_out: "退出登录"
|
||||
},
|
||||
plurals: {
|
||||
car: '车 | 辆车',
|
||||
apple: '没有苹果 | 一个苹果 | {count} 个苹果',
|
||||
format: {
|
||||
named: '你好 {name},你好吗? | 嗨 {name},你看起来不错',
|
||||
list: '你好 {0},你好吗? | 嗨 {0},你看起来不错'
|
||||
},
|
||||
fallback: '这是备选 | 这是一个复数备选'
|
||||
},
|
||||
message: {
|
||||
hello: '世界',
|
||||
helloName: '你好,{name}',
|
||||
hoge: 'hoge',
|
||||
link: '@:message.hello',
|
||||
linkHelloName: '@:message.helloName',
|
||||
linkLinkHelloName: '@:message.linkHelloName',
|
||||
linkEnd: '这是一个链接翻译到 @:message.hello',
|
||||
linkWithin: '难道我们不是生活在美好的世界里吗?',
|
||||
linkMultiple: '你好,@:message.hoge!难道 @:message.hello 不美好吗?',
|
||||
linkBrackets: '你好,@:(message.hoge)。难道 @:(message.hello) 不美好吗?',
|
||||
linkHyphen: '@:hyphen-hello',
|
||||
linkUnderscore: '@:underscore_hello',
|
||||
linkPipe: '@:pipe|hello',
|
||||
linkColon: '@:(colon:hello)',
|
||||
linkList: '@:message.hello:{0} {1}',
|
||||
linkCaseLower: '请提供 @.lower:message.homeAddress',
|
||||
linkCaseUpper: '@.upper:message.homeAddress',
|
||||
linkCaseCapitalize: '@.capitalize:message.homeAddress',
|
||||
linkCaseUnknown: '@.unknown:message.homeAddress',
|
||||
linkCaseCustom: '@.custom:message.homeAddress',
|
||||
homeAddress: '家庭地址',
|
||||
circular1: 'Foo @:message.circular2',
|
||||
circular2: 'Bar @:message.circular3',
|
||||
circular3: 'Buz @:message.circular1',
|
||||
linkTwice: '@:message.hello:@:message.hello',
|
||||
}
|
||||
};
|
||||
0
uni_modules/ak-i18n/test/locales/zh_CN.json
Normal file
0
uni_modules/ak-i18n/test/locales/zh_CN.json
Normal file
39
uni_modules/ak-i18n/utssdk/unierror.uts
Normal file
39
uni_modules/ak-i18n/utssdk/unierror.uts
Normal file
@@ -0,0 +1,39 @@
|
||||
/* 此规范为 uni 规范,可以按照自己的需要选择是否实现 */
|
||||
import { MyApiErrorCode, MyApiFail } from "./interface.uts"
|
||||
/**
|
||||
* 错误主题
|
||||
* 注意:错误主题一般为插件名称,每个组件不同,需要使用时请更改。
|
||||
* [可选实现]
|
||||
*/
|
||||
export const UniErrorSubject = 'uts-api';
|
||||
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
* @UniError
|
||||
* [可选实现]
|
||||
*/
|
||||
export const MyAPIErrors : Map<MyApiErrorCode, string> = new Map([
|
||||
/**
|
||||
* 错误码及对应的错误信息
|
||||
*/
|
||||
[9010001, 'custom error mseeage1'],
|
||||
[9010002, 'custom error mseeage2'],
|
||||
]);
|
||||
|
||||
|
||||
/**
|
||||
* 错误对象实现
|
||||
*/
|
||||
export class MyApiFailImpl extends UniError implements MyApiFail {
|
||||
|
||||
/**
|
||||
* 错误对象构造函数
|
||||
*/
|
||||
constructor(errCode : MyApiErrorCode) {
|
||||
super();
|
||||
this.errSubject = UniErrorSubject;
|
||||
this.errCode = errCode;
|
||||
this.errMsg = MyAPIErrors.get(errCode) ?? "";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user