diff --git a/src/layui.js b/src/layui.js index e2862ae1..3eaaa923 100644 --- a/src/layui.js +++ b/src/layui.js @@ -55,12 +55,25 @@ // 异常提示 var error = function(msg, type) { type = type || 'log'; - msg = 'Layui message: ' + msg; + msg = '[Layui warn]: ' + msg; if (window.console) { console[type] ? console[type](msg) : console.log(msg); } }; + var warned = Object.create(null); + + var errorOnce = function (msg, type) { + if(warned._size && warned._size > 100){ + warned = Object.create(null); + warned._size = 0; + } + if (!warned[msg]) { + warned[msg] = true; + warned._size = (warned._size || 0) + 1; + error(msg, type) + } + } // 内置模块 var builtinModules = config.builtin = { @@ -715,7 +728,8 @@ // 提示 Class.prototype.hint = function() { return { - error: error + error: error, + errorOnce: errorOnce }; }; diff --git a/src/modules/i18n.js b/src/modules/i18n.js index bd27e199..32588caf 100644 --- a/src/modules/i18n.js +++ b/src/modules/i18n.js @@ -262,27 +262,46 @@ layui.define('lay', function(exports) { return value } + 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 = !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 value || path; + }); + var i18n = { config: config, set: function(options) { lay.extend(config, options); - getCachedValueForKeyPath.cleanup(); + resolveValue.cleanup(); } }; - var getCachedValueForKeyPath = memoize(function(key, obj){ - return get(obj, key, key); - }); - /** * 根据给定的键从国际化消息中获取翻译后的内容 * 未文档化的私有方法,仅限内部使用 * * @internal - * @param {string} key 要翻译的键 - * @param {any[]} [args] 可选的占位符替换参数: + * @param {string} keypath 要翻译的键路径 + * @param {Record | any[]} [parameters] 可选的占位符替换参数: * - 对象形式:用于替换 `{key}` 形式的占位符; * - 数组形式:用于替换 `{0}`, `{1}` 等占位符; + * @param {{locale: string, default: string}} [options] 翻译选项 * @returns {string} 翻译后的文本 * * @example 使用对象替换命名占位符 @@ -297,27 +316,24 @@ layui.define('lay', function(exports) { * } * i18n.$t('message.hello', ['Hello']) */ - i18n.translation = function(key) { - var options = config; - var args = arguments; - var i18nMessages = options.messages[options.locale]; + i18n.translation = function(keypath, parameters, options) { + var locale = (options && options.locale) || config.locale; + var i18nMessages = config.messages[locale]; + var namespace = locale + ':'; + var fallbackMessage = options && options.default; if (!i18nMessages) { - hint.error('Locale "' + options.locale + '" not found. Please add i18n messages for this locale first.', 'warn'); - return key; + hint.errorOnce("Locale '" + locale + "' not found. Please add i18n messages for this locale first.", 'error'); } - - var result = getCachedValueForKeyPath(key, i18nMessages); + + var result = resolveValue(namespace + keypath, i18nMessages, fallbackMessage); // 替换占位符 - if (typeof result === 'string' && args.length > 1) { - var opts = args[1]; + if (typeof result === 'string' && parameters) { // 第二个参数为对象或数组,替换占位符 {key} 或 {0}, {1}... - if (opts !== null && typeof opts === 'object') { - result = result.replace(OBJECT_REPLACE_REGEX, function(match, key) { - return opts[key] !== undefined ? opts[key] : match; - }); - } + result = result.replace(OBJECT_REPLACE_REGEX, function(match, key) { + return parameters[key] !== undefined ? parameters[key] : match; + }); } return escape(result); diff --git a/src/modules/laydate.js b/src/modules/laydate.js index 85943c67..ead40fc0 100644 --- a/src/modules/laydate.js +++ b/src/modules/laydate.js @@ -111,6 +111,9 @@ layui.define(['lay', 'i18n'], function(exports) { // 初始化属性 options = lay.extend(that.config, lay.options(elem[0])); // 继承节点上的属性 + // 更新 i18n 消息对象 + that.i18nMessages = that.getI18nMessages(); + // 若重复执行 render,则视为 reload 处理 if(elem[0] && elem.attr(MOD_ID)){ var newThat = thisModule.getThis(elem.attr(MOD_ID)); @@ -163,7 +166,7 @@ layui.define(['lay', 'i18n'], function(exports) { showBottom: true, // 是否显示底部栏 isPreview: true, // 是否显示值预览 btns: ['clear', 'now', 'confirm'], // 右下角显示的按钮,会按照数组顺序排列 - // 为实现 lang 选项就近生效,去除此处的默认值,原型 lang() 方法中有兜底值 + // 为实现 lang 选项就近生效,去除此处的默认值,$t 设置了英文回退值 lang: '', // 语言,只支持 cn/en,即中文和英文 theme: 'default', // 主题 position: null, // 控件定位方式定位, 默认absolute,支持:fixed/absolute/static @@ -177,51 +180,84 @@ layui.define(['lay', 'i18n'], function(exports) { shade: 0 }; - // 多语言 - Class.prototype.lang = function() { + Class.prototype.getI18nMessages = function () { var that = this; var options = that.config; - var locale = i18n.config.locale.replace(/^\s+|\s+$/g, ''); - var i18nMessages = i18n.config.messages[locale]; - var laydateMessages = { - // 保留原版 en - en: { - month: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - weeks: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], - time: ['Hour', 'Minute', 'Second'], - selectDate: 'Select Date', - selectTime: 'Select Time', - startTime: 'Start Time', - endTime: 'End Time', - tools: { - confirm: 'Confirm', - clear: 'Clear', - now: 'Now', - reset: 'Reset' - }, - timeout: 'End time cannot be less than start Time\nPlease re-select', - invalidDate: 'Invalid date', - formatError: ['The date format error\nMust be followed:\n', '\nIt has been reset'], - preview: 'The selected result' - } - }; - - // 同步 message - if (i18nMessages) { - laydateMessages[locale] = i18nMessages.laydate; - } + var locale = i18n.config.locale; // 纠正旧版「简体中文」语言码 if (options.lang === 'cn') { options.lang = zhCN; - } else if (!options.lang) { // 若未传 lang 选项,则取 locale - options.lang = locale; + }else if(!options.lang){ + options.lang = i18n.config.locale; } + locale = options.lang; - // 获取当前语言的 laydate 消息 - // 若无消息数据,则取上述内置 en,确保组件正常显示。注:此非默认值逻辑,默认值已由 i18n 统一控制 - return laydateMessages[options.lang] || laydateMessages['en'];; - }; + return { + month: i18n.$t('laydate.month', null, { + locale: locale, + default: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + }), + weeks: i18n.$t('laydate.weeks', null, { + locale: locale, + default: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] + }), + time: i18n.$t('laydate.time', null, { + locale: locale, + default: ['Hour', 'Minute', 'Second'] + }), + selectDate: i18n.$t('laydate.selectDate', null, { + locale: locale, + default: 'Select Date' + }), + selectTime: i18n.$t('laydate.selectTime', null, { + locale: locale, + default: 'Select Time' + }), + startTime: i18n.$t('laydate.startTime', null, { + locale: locale, + default: 'Start Time' + }), + endTime: i18n.$t('laydate.endTime', null, { + locale: locale, + default: 'End Time' + }), + tools: { + confirm: i18n.$t('laydate.tools.confirm', null, { + locale: locale, + default: 'Confirm' + }), + clear: i18n.$t('laydate.tools.clear', null, { + locale: locale, + default: 'Clear' + }), + now: i18n.$t('laydate.tools.now', null, { + locale: locale, + default: 'Now' + }), + reset: i18n.$t('laydate.tools.reset', null, { + locale: locale, + default: 'Reset' + }) + }, + timeout: i18n.$t('laydate.timeout', null, { + locale: locale, + default: 'End time cannot be less than start Time\nPlease re-select' + }), + invalidDate: i18n.$t('laydate.invalidDate', null, { + locale: locale, + default: 'Invalid date' + }), + formatError: i18n.$t('laydate.formatError', null, { + locale: locale, + default: ['The date format error\nMust be followed:\n', '\nIt has been reset'] + }), + preview: i18n.$t('laydate.preview', null, { + locale: locale, + default: 'The selected result' + }) + } + } // 仅简体中文生效,不做国际化 Class.prototype.markerOfChineseFestivals = { @@ -317,7 +353,7 @@ layui.define(['lay', 'i18n'], function(exports) { // 设置了一周的开始是周几,此处做一个控制 if (options.weekStart) { if (!/^[0-6]$/.test(options.weekStart)) { - var lang = that.lang(); + var lang = that.i18nMessages; options.weekStart = lang.weeks.indexOf(options.weekStart); if (options.weekStart === -1) options.weekStart = 0; } @@ -434,7 +470,7 @@ layui.define(['lay', 'i18n'], function(exports) { Class.prototype.render = function(){ var that = this ,options = that.config - ,lang = that.lang() + ,lang = that.i18nMessages ,isStatic = options.position === 'static' //主面板 @@ -830,7 +866,7 @@ layui.define(['lay', 'i18n'], function(exports) { var that = this ,thisDate = new Date() ,options = that.config - ,lang = that.lang() + ,lang = that.i18nMessages ,dateTime = options.dateTime = options.dateTime || that.systemDate() ,thisMaxDate, error @@ -1431,7 +1467,7 @@ layui.define(['lay', 'i18n'], function(exports) { ,options = that.config ,dateTime = value || that.thisDateTime(index) ,thisDate = new Date(), startWeek, prevMaxDate, thisMaxDate - ,lang = that.lang() + ,lang = that.i18nMessages ,isAlone = options.type !== 'date' && options.type !== 'datetime' ,tds = lay(that.table[index]).find('td') @@ -1577,7 +1613,7 @@ layui.define(['lay', 'i18n'], function(exports) { var that = this ,options = that.config ,dateTime = that.rangeLinked ? options.dateTime : [options.dateTime, that.endDate][index] - ,lang = that.lang() + ,lang = that.i18nMessages ,isAlone = options.range && options.type !== 'date' && options.type !== 'datetime' //独立范围选择器 ,ul = lay.elem('ul', { @@ -1900,7 +1936,7 @@ layui.define(['lay', 'i18n'], function(exports) { Class.prototype.setBtnStatus = function(tips, start, end){ var that = this ,options = that.config - ,lang = that.lang() + ,lang = that.i18nMessages ,isOut ,elemBtn = lay(that.footer).find(ELEM_CONFIRM) ,timeParams = options.type === 'datetime' || options.type === 'time' ? ['hours', 'minutes', 'seconds'] : undefined; @@ -2307,7 +2343,7 @@ layui.define(['lay', 'i18n'], function(exports) { Class.prototype.tool = function(btn, type){ var that = this ,options = that.config - ,lang = that.lang() + ,lang = that.i18nMessages ,dateTime = options.dateTime ,isStatic = options.position === 'static' ,active = { @@ -2316,13 +2352,13 @@ layui.define(['lay', 'i18n'], function(exports) { if(lay(btn).hasClass(DISABLED)) return; that.list('time', 0); options.range && that.list('time', 1); - lay(btn).attr('lay-type', 'date').html(that.lang().selectDate); + lay(btn).attr('lay-type', 'date').html(that.i18nMessages.selectDate); } //选择日期 ,date: function(){ that.closeList(); - lay(btn).attr('lay-type', 'datetime').html(that.lang().selectTime); + lay(btn).attr('lay-type', 'datetime').html(that.i18nMessages.selectTime); } //清空、重置