From 6edd483f5d9b70df75683d66feb2cc03711830e1 Mon Sep 17 00:00:00 2001 From: zhixin Date: Wed, 21 Aug 2019 11:09:09 +0800 Subject: [PATCH] Added locale support --- .eslintrc.js | 2 +- docs/_i18n/en/documentation/options.md | 20 ++++ docs/_i18n/zh-cn/documentation/options.md | 22 ++++ docs/_includes/example-list.md | 1 + docs/examples/locale.html | 61 +++++++++++ package.json | 39 +++---- rollup.config.js | 42 +++++++- src/MultipleSelect.js | 118 +++++---------------- src/constants/index.js | 121 ++++++++++++++++++++++ src/locale/multiple-select-en-US.js | 21 ++++ src/locale/multiple-select-zh-CN.js | 21 ++++ src/locale/multiple-select-zh-TW.js | 21 ++++ src/multiple-select.js | 17 ++- src/utils/removeDiacritics.js | 4 +- tools/template.js | 2 +- 15 files changed, 386 insertions(+), 126 deletions(-) create mode 100644 docs/examples/locale.html create mode 100644 src/constants/index.js create mode 100644 src/locale/multiple-select-en-US.js create mode 100644 src/locale/multiple-select-zh-CN.js create mode 100644 src/locale/multiple-select-zh-TW.js diff --git a/.eslintrc.js b/.eslintrc.js index c3a17b04..ed48cc9c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,7 +22,7 @@ module.exports = { } }, { - 'files': ['tools/template.js'], + 'files': ['src/locale/*.js', 'tools/*.js'], // These are being overwritten for some reason, despite `node: true` 'globals': { require: 'readonly', diff --git a/docs/_i18n/en/documentation/options.md b/docs/_i18n/en/documentation/options.md index b25c0730..4ec58c85 100644 --- a/docs/_i18n/en/documentation/options.md +++ b/docs/_i18n/en/documentation/options.md @@ -60,6 +60,26 @@ Option format: **Example:** Basic Data and Optgroup Data. +## locale + +- **Type:** `String` + +- **Detail:** + + Sets the locale to use (i.e. `'zh-CN'`). Locale files must be pre-loaded. + Allows for fallback locales, if loaded, in the following order: + + * First tries for the locale as specified, + * Then tries the locale with '_' translated to '-' and the region code upper cased, + * Then tries the short locale code (i.e. `'zh'` instead of `'zh-CN'`), + * And finally will use the last locale file loaded (or the default locale if no locales loaded). + + If left `undefined` or an empty string, use the last locale loaded (or `'en-US'` if no locale files loaded). + +- **Default:** `undefined` + +**Example:** The Locale + ## selectAll **Type:** Boolean diff --git a/docs/_i18n/zh-cn/documentation/options.md b/docs/_i18n/zh-cn/documentation/options.md index 19db9e9e..78c15e1f 100644 --- a/docs/_i18n/zh-cn/documentation/options.md +++ b/docs/_i18n/zh-cn/documentation/options.md @@ -60,6 +60,28 @@ Option 格式: **例子:** Basic DataOptgroup Data +## locale + +- **标签属性:** `data-locale` + +- **类型:** `String` + +- **详细描述:** + + 设置要使用的多语言(例如 `'zh-CN'`)。设置的多语言文件必须预先加载。 + 允许同时加载多个多语言文件,假如已加载,按照以下的顺序: + + * 首先尝试指定的多语言, + * 然后尝试将 '_' 翻译为 '-' 并将区域代码设置为大写的多语言, + * 然后尝试短的多语言代码(即 `'zh'` 而不是 `'zh-CN'`), + * 最后将使用加载的最后一个多语言文件(如果没有加载多语言,则使用默认的多语言)。 + + 如果保留 `undefined` 或空字符串,请使用最后加载的多语言(如果没有加载多语言文件,则使用 'en-US')。 + +- **默认:** `undefined` + +**例子:** The Locale + ## selectAll **类型:** Boolean diff --git a/docs/_includes/example-list.md b/docs/_includes/example-list.md index 65391a07..52a3ba3d 100644 --- a/docs/_includes/example-list.md +++ b/docs/_includes/example-list.md @@ -7,6 +7,7 @@
  • Two Select
  • Basic Data
  • Optgroup Data
  • +
  • The Locale
  • The Placeholder
  • diff --git a/docs/examples/locale.html b/docs/examples/locale.html new file mode 100644 index 00000000..e23cb637 --- /dev/null +++ b/docs/examples/locale.html @@ -0,0 +1,61 @@ + + + + +
    + +
    + +
    + +
    + + diff --git a/package.json b/package.json index e799f586..38ee5857 100644 --- a/package.json +++ b/package.json @@ -52,38 +52,39 @@ "docs": "http://multiple-select.wenzhixin.net.cn/documentation", "download": "https://github.com/wenzhixin/multiple-select/archive/master.zip", "engines": {}, - "dependencies": {}, "devDependencies": { - "@babel/preset-env": "^7.4.5", - "@mysticatea/eslint-plugin": "^10.0.3", + "@babel/preset-env": "^7.5.5", + "@mysticatea/eslint-plugin": "^11.0.0", "axe-testcafe": "^3.0.0", - "core-js": "^3.1.4", + "core-js": "^3.2.1", "cssmin-cli": "^0.0.5", - "eslint": "^6.0.1", - "eslint-config-ash-nazg": "^7.0.1", - "eslint-config-standard": "^12.0.0", - "eslint-plugin-compat": "^3.2.0", + "eslint": "^6.2.1", + "eslint-config-ash-nazg": "^8.7.0", + "eslint-config-standard": "^14.0.0", + "eslint-plugin-compat": "^3.3.0", "eslint-plugin-eslint-comments": "^3.1.2", - "eslint-plugin-import": "^2.18.0", - "eslint-plugin-jsdoc": "^10.0.1", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-jsdoc": "^15.8.3", "eslint-plugin-markdown": "^1.0.0", "eslint-plugin-no-use-extend-native": "^0.4.1", "eslint-plugin-node": "^9.1.0", "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-standard": "^4.0.0", + "eslint-plugin-standard": "^4.0.1", "eslint-plugin-testcafe": "^0.2.1", - "eslint-plugin-unicorn": "^9.1.1", + "eslint-plugin-unicorn": "^10.0.0", "getopts": "^2.2.5", + "glob": "^7.1.4", "headr": "^0.0.4", "npm-run-all": "^4.1.5", - "rollup": "^1.16.3", + "rollup": "^1.19.4", "rollup-plugin-babel": "^4.3.3", - "rollup-plugin-babel-minify": "^8.0.0", - "rollup-plugin-commonjs": "^10.0.1", - "rollup-plugin-inject": "^3.0.0", + "rollup-plugin-babel-minify": "^9.0.0", + "rollup-plugin-commonjs": "^10.0.2", + "rollup-plugin-inject": "^3.0.1", + "rollup-plugin-multi-entry": "^2.1.0", "rollup-plugin-node-resolve": "^5.2.0", - "sass": "^1.22.1", - "testcafe": "^1.2.1", - "typescript": "^3.5.2" + "sass": "^1.22.10", + "testcafe": "^1.4.1", + "typescript": "^3.5.3" } } diff --git a/rollup.config.js b/rollup.config.js index d0da793e..8a22f06a 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,9 +1,10 @@ +import glob from 'glob' import babel from 'rollup-plugin-babel' import resolve from 'rollup-plugin-node-resolve' import commonjs from 'rollup-plugin-commonjs' import minify from 'rollup-plugin-babel-minify' import inject from 'rollup-plugin-inject' -// import vue from 'rollup-plugin-vue' +import multiEntry from 'rollup-plugin-multi-entry' let found const env = process.argv.find(flag => { @@ -74,4 +75,43 @@ if (!dev) { }) } +out = 'dist/multiple-select-locale-all.js' +if (production) { + out = out.replace(/.js$/, '.min.js') +} +config.push({ + input: 'src/locale/**/*.js', + output: { + name: 'MultipleSelect', + file: out, + format: 'umd', + globals + }, + external, + plugins: [ + multiEntry(), + ...plugins + ] +}) + +const files = glob.sync('src/locale/**/*.js') + +for (const file of files) { + out = `dist/${file.replace('src/', '')}` + if (production) { + out = out.replace(/.js$/, '.min.js') + } + config.push({ + input: file, + output: { + name: 'MultipleSelect', + file: out, + format: 'umd', + globals + }, + external, + plugins + }) +} + export default config diff --git a/src/MultipleSelect.js b/src/MultipleSelect.js index 5db67f93..bac4892c 100644 --- a/src/MultipleSelect.js +++ b/src/MultipleSelect.js @@ -1,20 +1,42 @@ /* eslint-disable unicorn/no-fn-reference-in-iterator */ +import Constants from './constants/index.js' import removeDiacritics from './utils/removeDiacritics.js' import {s, sprintf} from './utils/sprintf.js' class MultipleSelect { constructor ($el, options) { this.$el = $el - this.options = $.extend({}, defaults, options) + this.options = $.extend({}, Constants.DEFAULTS, options) } init () { + this.initLocale() this.initContainer() this.initData() this.initDrop() } + initLocale () { + if (this.options.locale) { + const {locales} = $.fn.multipleSelect + const parts = this.options.locale.split(/-|_/) + + parts[0] = parts[0].toLowerCase() + if (parts[1]) { + parts[1] = parts[1].toUpperCase() + } + + if (locales[this.options.locale]) { + $.extend(this.options, locales[this.options.locale]) + } else if (locales[parts.join('-')]) { + $.extend(this.options, locales[parts.join('-')]) + } else if (locales[parts[0]]) { + $.extend(this.options, locales[parts[0]]) + } + } + } + initContainer () { const el = this.$el[0] const name = el.getAttribute('name') || this.options.name || '' @@ -251,7 +273,7 @@ class MultipleSelect { const customStyle = this.options.styler(row.value) const style = customStyle ? sprintf`style="${s}"`(customStyle) : '' - let classes = row.classes + let {classes} = row if (this.options.single && !this.options.singleRadio) { classes += ' hide-radio' @@ -691,98 +713,12 @@ class MultipleSelect { } destroy () { + if (!this.$parent) { + return + } this.$el.before(this.$parent).show() this.$parent.remove() } } -const defaults = { - name: '', - placeholder: '', - data: undefined, - - selectAll: true, - single: false, - singleRadio: false, - multiple: false, - hideOptgroupCheckboxes: false, - multipleWidth: 80, - width: undefined, - dropWidth: undefined, - maxHeight: 250, - position: 'bottom', - - displayValues: false, - displayTitle: false, - displayDelimiter: ', ', - minimumCountSelected: 3, - ellipsis: false, - - isOpen: false, - keepOpen: false, - openOnHover: false, - container: null, - - filter: false, - filterGroup: false, - filterPlaceholder: '', - filterAcceptOnEnter: false, - - animate: undefined, - - styler () { - return false - }, - textTemplate ($elm) { - return $elm[0].innerHTML - }, - labelTemplate ($elm) { - return $elm[0].getAttribute('label') - }, - - formatSelectAll () { - return '[Select all]' - }, - formatAllSelected () { - return 'All selected' - }, - formatCountSelected (count, total) { - return count + ' of ' + total + ' selected' - }, - formatNoMatchesFound () { - return 'No matches found' - }, - - onOpen () { - return false - }, - onClose () { - return false - }, - onCheckAll () { - return false - }, - onUncheckAll () { - return false - }, - onFocus () { - return false - }, - onBlur () { - return false - }, - onOptgroupClick () { - return false - }, - onClick () { - return false - }, - onFilter () { - return false - }, - onAfterCreate () { - return false - } -} - export default MultipleSelect diff --git a/src/constants/index.js b/src/constants/index.js new file mode 100644 index 00000000..3e05ef27 --- /dev/null +++ b/src/constants/index.js @@ -0,0 +1,121 @@ +const VERSION = '1.3.1' + +const DEFAULTS = { + name: '', + placeholder: '', + data: undefined, + locale: undefined, + + selectAll: true, + single: false, + singleRadio: false, + multiple: false, + hideOptgroupCheckboxes: false, + multipleWidth: 80, + width: undefined, + dropWidth: undefined, + maxHeight: 250, + position: 'bottom', + + displayValues: false, + displayTitle: false, + displayDelimiter: ', ', + minimumCountSelected: 3, + ellipsis: false, + + isOpen: false, + keepOpen: false, + openOnHover: false, + container: null, + + filter: false, + filterGroup: false, + filterPlaceholder: '', + filterAcceptOnEnter: false, + + animate: undefined, + + styler () { + return false + }, + textTemplate ($elm) { + return $elm[0].innerHTML + }, + labelTemplate ($elm) { + return $elm[0].getAttribute('label') + }, + + onOpen () { + return false + }, + onClose () { + return false + }, + onCheckAll () { + return false + }, + onUncheckAll () { + return false + }, + onFocus () { + return false + }, + onBlur () { + return false + }, + onOptgroupClick () { + return false + }, + onClick () { + return false + }, + onFilter () { + return false + }, + onAfterCreate () { + return false + } +} + +const EN = { + formatSelectAll () { + return '[Select all]' + }, + formatAllSelected () { + return 'All selected' + }, + formatCountSelected (count, total) { + return count + ' of ' + total + ' selected' + }, + formatNoMatchesFound () { + return 'No matches found' + } +} + +const METHODS = [ + 'getOptions', + 'getSelects', 'setSelects', + 'enable', 'disable', + 'open', 'close', + 'checkAll', 'uncheckAll', + 'focus', 'blur', + 'refresh', 'destroy' +] + +// eslint-disable-next-line compat/compat +Object.assign(DEFAULTS, EN) + +const Constants = { + VERSION, + + DEFAULTS, + + METHODS, + + LOCALES: { + en: EN, + 'en-US': EN + } +} + +export default Constants diff --git a/src/locale/multiple-select-en-US.js b/src/locale/multiple-select-en-US.js new file mode 100644 index 00000000..66e351af --- /dev/null +++ b/src/locale/multiple-select-en-US.js @@ -0,0 +1,21 @@ +/** + * Multiple Select English translation + * Author: Zhixin Wen + */ + +$.fn.multipleSelect.locales['en-US'] = { + formatSelectAll () { + return '[Select all]' + }, + formatAllSelected () { + return 'All selected' + }, + formatCountSelected (count, total) { + return count + ' of ' + total + ' selected' + }, + formatNoMatchesFound () { + return 'No matches found' + } +} + +$.extend($.fn.multipleSelect.defaults, $.fn.multipleSelect.locales['en-US']) diff --git a/src/locale/multiple-select-zh-CN.js b/src/locale/multiple-select-zh-CN.js new file mode 100644 index 00000000..013ea3c0 --- /dev/null +++ b/src/locale/multiple-select-zh-CN.js @@ -0,0 +1,21 @@ +/** + * Multiple Select English translation + * Author: Zhixin Wen + */ + +$.fn.multipleSelect.locales['zh-CN'] = { + formatSelectAll () { + return '[全选]' + }, + formatAllSelected () { + return '已选择所有记录' + }, + formatCountSelected (count, total) { + return '已从' + total + '条记录中选择' + count + '条' + }, + formatNoMatchesFound () { + return '没有找到记录' + } +} + +$.extend($.fn.multipleSelect.defaults, $.fn.multipleSelect.locales['zh-CN']) diff --git a/src/locale/multiple-select-zh-TW.js b/src/locale/multiple-select-zh-TW.js new file mode 100644 index 00000000..b5df7fd2 --- /dev/null +++ b/src/locale/multiple-select-zh-TW.js @@ -0,0 +1,21 @@ +/** + * Multiple Select English translation + * Author: Zhixin Wen + */ + +$.fn.multipleSelect.locales['zh-TW'] = { + formatSelectAll () { + return '[全選]' + }, + formatAllSelected () { + return '已選擇所有記錄' + }, + formatCountSelected (count, total) { + return '已從' + total + '條記錄中選擇' + count + '條' + }, + formatNoMatchesFound () { + return '沒有找到記錄' + } +} + +$.extend($.fn.multipleSelect.defaults, $.fn.multipleSelect.locales['zh-TW']) diff --git a/src/multiple-select.js b/src/multiple-select.js index 31a9f10c..76708e52 100644 --- a/src/multiple-select.js +++ b/src/multiple-select.js @@ -5,21 +5,12 @@ * http://wenzhixin.net.cn/p/multiple-select/ */ +import Constants from './constants/index.js' import MultipleSelect from './MultipleSelect.js' $.fn.multipleSelect = function (option, ...args) { let value - const allowedMethods = [ - 'getOptions', - 'getSelects', 'setSelects', - 'enable', 'disable', - 'open', 'close', - 'checkAll', 'uncheckAll', - 'focus', 'blur', - 'refresh', 'destroy' - ] - this.each((i, el) => { const $this = $(el) let data = $this.data('multipleSelect') @@ -36,7 +27,7 @@ $.fn.multipleSelect = function (option, ...args) { } if (typeof option === 'string') { - if ($.inArray(option, allowedMethods) < 0) { + if ($.inArray(option, Constants.METHODS) < 0) { throw new Error(`Unknown method: ${option}`) } value = data[option](...args) @@ -51,3 +42,7 @@ $.fn.multipleSelect = function (option, ...args) { return typeof value !== 'undefined' ? value : this } + +$.fn.multipleSelect.defaults = Constants.DEFAULTS +$.fn.multipleSelect.locales = Constants.LOCALES +$.fn.multipleSelect.methods = Constants.METHODS diff --git a/src/utils/removeDiacritics.js b/src/utils/removeDiacritics.js index 93672237..678221d4 100644 --- a/src/utils/removeDiacritics.js +++ b/src/utils/removeDiacritics.js @@ -1,7 +1,7 @@ /** * Strips diacritics. - * @param {string} str - * @returns {string} + * @param {string} str diacritics string + * @returns {string} removed diacritics string */ function removeDiacritics (str) { // A polyfill for `normalize` will be even larger than our code below, so diff --git a/tools/template.js b/tools/template.js index 4fb4aa1a..407df71e 100644 --- a/tools/template.js +++ b/tools/template.js @@ -31,7 +31,7 @@ function showHelp () { /** * Perform document file writing. - * @returns {Promise} + * @returns {Promise} process */ async function run () { if (options.help || Object.keys(options).length === 1) {