/** * i18n * 国际化 */ layui.define('lay', function(exports) { 'use strict'; var lay = layui.lay; var hint = layui.hint(); var MOD_NAME = 'i18n'; // 识别预先可能定义的指定全局对象 var GLOBAL = window.LAYUI_GLOBAL || {}; // 简体中文 var zhCN = { code: { copy: '复制代码', copied: '已复制', copyError: '复制失败', maximize: '最大化显示', restore: '还原显示', preview: '在新窗口预览' }, colorpicker: { clear: '清除', confirm: '确定' }, dropdown: { noData: '暂无数据' }, flow: { loadMore: '加载更多', noMore: '没有更多了' }, form: { select: { noData: '暂无数据', noMatch: '无匹配数据', placeholder: '请选择' }, validateMessages: { required: '必填项不能为空', phone: '手机号格式不正确', email: '邮箱格式不正确', url: '链接格式不正确', number: '只能填写数字', date: '日期格式不正确', identity: '身份证号格式不正确' }, verifyErrorPromptTitle: '提示' }, laydate: { months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], weeks: ['日', '一', '二', '三', '四', '五', '六'], time: ['时', '分', '秒'], literal: { year: '年' }, selectDate: '选择日期', selectTime: '选择时间', startTime: '开始时间', endTime: '结束时间', tools: { confirm: '确定', clear: '清空', now: '现在', reset: '重置' }, rangeOrderPrompt: '结束时间不能早于开始时间\n请重新选择', invalidDatePrompt: '不在有效日期或时间范围内\n', formatErrorPrompt: '日期格式不合法\n必须遵循:\n{format}\n', autoResetPrompt: '已自动重置', preview: '当前选中的结果' }, layer: { confirm: '确定', cancel: '取消', defaultTitle: '信息', prompt: { InputLengthPrompt: '最多输入 {length} 个字符' }, photos: { noData: '没有图片', tools:{ rotate: '旋转', scaleX: '水平变换', zoomIn: '放大', zoomOut: '缩小', reset: '还原', close: '关闭' }, viewPicture: '查看原图', urlError: { prompt: '当前图片地址异常,\n是否继续查看下一张?', confirm: '下一张', cancel: '不看了' } } }, laypage: { prev: '上一页', next: '下一页', first: '首页', last: '尾页', total: '共 {total} 条', pagesize: '条/页', goto: '到第', page: '页', confirm: '确定' }, table: { sort: { asc: '升序', desc: '降序' }, noData: '暂无数据', tools:{ filter: { title: '筛选列' }, export: { title: '导出', noDataPrompt: '当前表格无数据', compatPrompt: '导出功能不支持 IE,请用 Chrome 等高级浏览器导出', csvText : '导出 CSV 文件' }, print: { title: '打印', noDataPrompt: '当前表格无数据' } }, dataFormatError: '返回的数据不符合规范,正确的成功状态码应为:"{statusName}": {statusCode}', xhrError: '请求异常,错误提示:{msg}' }, transfer: { noData: '暂无数据', noMatch: '无匹配数据', title: ['列表一', '列表二'], searchPlaceholder: '关键词搜索' }, tree: { defaultNodeName: '未命名', noData: '暂无数据', deleteNodePrompt: '确认删除"{name}"节点吗?' }, upload: { fileType: { file: '文件', image: '图片', video: '视频', audio: '音频' }, validateMessages: { fileExtensionError: '选择的{fileType}中包含不支持的格式', filesOverLengthLimit: '同时最多只能上传: {length} 个文件', currentFilesLength: '当前已经选择了: {length} 个文件', fileOverSizeLimit: '文件大小不能超过 {size}' }, chooseText: '{length} 个文件' }, util: { timeAgo: { days: '{days} 天前', hours: '{hours} 小时前', minutes: '{minutes} 分钟前', future: '未来', justNow: '刚刚' }, toDateString: { // https://www.unicode.org/cldr/charts/47/supplemental/day_periods.html meridiem: function(hours, minutes){ var hm = hours * 100 + minutes; if (hm < 500) { return '凌晨'; } else if (hm < 800) { return '早上'; } else if (hm < 1200) { return '上午'; } else if (hm < 1300) { return '中午'; } else if (hm < 1900) { return '下午'; } return '晚上'; } } } }; // 默认配置 var config = lay.extend({ locale: 'zh-CN', // 全局内置语言 messages: { // 全局国际化消息对象 'zh-CN': zhCN } }, GLOBAL.i18n); // 读取全局预设配置,确保打包后的版本初始调用时机 var OBJECT_REPLACE_REGEX = /\{(\w+)\}/g; /** * 获取对象中指定路径的值,类似于 lodash 的 _.get 方法(简易版) * @param {Record} obj - 要查找的对象 * @param {string} path - 要查找的路径,支持类似 'a[0].b.c' 的格式 * @param {any} defaultValue - 若未找到对应值时返回的默认值 * @returns {any} - 找到的值或默认值 */ function get(obj, path, defaultValue) { // 'a[0].b.c' ==> ['a', '0', 'b', 'c'] var casePath = path.replace(/\[(\d+)\]/g, '.$1').split('.'); var result = obj; for(var i = 0; i < casePath.length; i++) { result = result && result[casePath[i]]; if(result === null || result === undefined){ return defaultValue; } } return result; } /** * 为纯函数创建具有缓存功能的版本,类似于 lodash 的 _.memoize 方法(简易版) * @template T * @param {(key: string, ...args) => T} fn - 需要缓存的函数,第一个参数为键 * @returns {{(key: string, ...args): T, cleanup: () => void}} - 带有缓存的函数 */ function memoize(fn){ /** @type Record */ var cache = Object.create(null); function cachedFn(key) { var hit = cache[key]; return hit || (cache[key] = fn.apply(cache, arguments)); } cachedFn.cleanup = function() { cache = Object.create(null); } return cachedFn; } /** * 对传入的值进行转义处理 * 若值为字符串,直接进行转义;若为函数,对函数返回的字符串进行转义;若为数组,对数组中的字符串元素进行转义 * @param {any} value - 需要进行转义处理的值 * @returns {any} - 转义后的结果 */ function escape(value) { if(typeof value === 'string'){ value = lay.escape(value); }else if(typeof value === 'function'){ var origFn = value; value = function(){ var val = origFn.apply(this, arguments) return typeof val === 'string' ? lay.escape(val) : val; } }else if(layui.type(value) === 'array'){ value = value.map(function(v){ return typeof v === 'string' ? lay.escape(v) : v; }); } return value } function isDef(value) { return value !== null && value !== undefined; } var resolveValue = memoize(function(path, obj, defaultValue){ var pathParts = path.split(':'); var locale = pathParts[0]; path = pathParts[1]; var value = get(obj, path, defaultValue); if (layui.cache.debug) { var isFallback = defaultValue === value || value === path; var isNotFound = !isDef(value) || isFallback; if (isNotFound) { hint.errorOnce("Not found '" + path + "' key in '" + locale + "' locale messages.", 'warn'); } if (isFallback) { hint.errorOnce("Fallback to default message for key: '" + path + "'", 'warn'); } } return isDef(value) ? value : path; }); var i18n = { config: config, set: function(options) { lay.extend(config, options); resolveValue.cleanup(); } }; /** * 根据给定的键从国际化消息中获取翻译后的内容 * 未文档化的私有方法,仅限内部使用 * * @internal * @param {string} keypath 要翻译的键路径 * @param {Record | any[]} [parameters] 可选的占位符替换参数: * - 对象形式:用于替换 `{key}` 形式的占位符; * - 数组形式:用于替换 `{0}`, `{1}` 等占位符; * @param {{locale: string, default: string}} [options] 翻译选项 * @returns {string} 翻译后的文本 * * @example 使用对象替换命名占位符 * message: { * hello: '{msg} world' * } * i18n.$t('message.hello', { msg: 'Hello' }) * * @example 使用数组替换索引占位符 * message: { * hello: '{0} world' * } * i18n.$t('message.hello', ['Hello']) */ i18n.translation = function(keypath, parameters, options) { var locale = (options && options.locale) || config.locale; var i18nMessages = config.messages[locale]; var namespace = locale + ':'; var fallbackMessage = (options && lay.hasOwn(options, 'default')) ? options.default : undefined; if (!i18nMessages) { hint.errorOnce("Locale '" + locale + "' not found. Please add i18n messages for this locale first.", 'error'); } var result = resolveValue(namespace + keypath, i18nMessages, fallbackMessage); // 替换占位符 if (typeof result === 'string' && parameters) { // 第二个参数为对象或数组,替换占位符 {key} 或 {0}, {1}... result = result.replace(OBJECT_REPLACE_REGEX, function(match, key) { return parameters[key] !== undefined ? parameters[key] : match; }); } return escape(result); }; /** * i18n.translation 的别名,用于简化代码书写,未文档化仅限内部使用 */ i18n.$t = i18n.translation; exports(MOD_NAME, i18n); });