diff --git a/.eslintignore b/.eslintignore index ce77b6f906..9e14bd7877 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,7 @@ /dist /node_modules /build +/i18n /extension-esm /extension /lib @@ -12,4 +13,4 @@ /echarts.all.js /echarts.blank.js /echarts.common.js -/echarts.simple.js \ No newline at end of file +/echarts.simple.js diff --git a/build/build-i18n.js b/build/build-i18n.js new file mode 100644 index 0000000000..57c2f56215 --- /dev/null +++ b/build/build-i18n.js @@ -0,0 +1,91 @@ +const fs = require('fs'); +const preamble = require('./preamble'); +const ts = require('typescript'); +const path = require('path'); + +const umdWrapperHead = ` +${preamble.js} +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports); + } else { + // Browser globals + factory({}); + } +})(this, function(exports) { +`; + +const umdWrapperHeadWithEcharts = ` +${preamble.js} +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports', 'echarts'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports, require('echarts')); + } else { + // Browser globals + factory({}, root.echarts); + } +})(this, function(exports, echarts) { +`; + +const umdWrapperTail = ` +});`; + +async function buildI18nWrap() { + const targetDir = path.join(__dirname, '../i18n'); + const sourceDir = path.join(__dirname, '../src/i18n'); + const files = fs.readdirSync(sourceDir); + files.forEach(t => { + if(!t.startsWith('lang')) { + return; + } + const fileName = t.replace(/\.ts$/, ''); + const type = fileName.replace(/^lang/, ''); + const echartsRegister = ` + echarts.registerLocale('${type}', localeObj); + `; + const pureExports = ` + for (var key in localeObj) { + if (localeObj.hasOwnProperty(key)) { + exports[key] = localeObj[key]; + } + } + `; + const code = fs.readFileSync(path.join(sourceDir, t), 'utf-8'); + // const outputText = ts.transpileModule(code, { + // module: ts.ModuleKind.CommonJS, + // }).outputText; + // Simple regexp replace is enough + const outputCode = code.replace(/export\s+?default/, 'var localeObj =') + .replace(/\/\*([\w\W]*?)\*\//, ''); + + fs.writeFileSync(path.join(targetDir, fileName + '.js'), umdWrapperHeadWithEcharts + outputCode + echartsRegister + umdWrapperTail, 'utf-8'); + fs.writeFileSync(path.join(targetDir, fileName + '-obj.js'), umdWrapperHead + outputCode + pureExports + umdWrapperTail, 'utf-8'); + }) + console.log('i18n build completed'); +} + +buildI18nWrap(); + +module.exports = { + buildI18n: buildI18nWrap +}; diff --git a/build/build.js b/build/build.js index 2e9f61bf47..1fd2a1af11 100755 --- a/build/build.js +++ b/build/build.js @@ -24,11 +24,11 @@ const config = require('./config.js'); const commander = require('commander'); const chalk = require('chalk'); const rollup = require('rollup'); -const ecLangPlugin = require('./ec-lang-rollup-plugin'); const prePublish = require('./pre-publish'); const transformDEV = require('./transform-dev'); const UglifyJS = require("uglify-js"); const preamble = require('./preamble'); +const {buildI18n} = require('./build-i18n') async function run() { @@ -60,24 +60,16 @@ async function run() { + '\n' + descIndent + '# Only generate `dist/echarts.js`.', egIndent + 'node build/build.js --type common --min' + '\n' + descIndent + '# Only generate `dist/echarts.common.min.js`.', - egIndent + 'node build/build.js --type simple --min --lang en' + egIndent + 'node build/build.js --type simple --min' + '\n' + descIndent + '# Only generate `dist/echarts-en.simple.min.js`.', - egIndent + 'node build/build.js --lang "my/lang.js" -i "my/index.js" -o "my/bundle.js"' + egIndent + 'node build/build.js -i "my/index.js" -o "my/bundle.js"' + '\n' + descIndent + '# Take `/my/index.js` as input and generate `/my/bundle.js`,' - + '\n' + descIndent + 'where `/my/lang.js` is used as language file.', ].join('\n')) .option( '-w, --watch', [ 'Watch modifications of files and auto-compile to dist file. For example,', descIndent + '`echarts/dist/echarts.js`.' ].join('\n')) - .option( - '--lang ', [ - 'Use the specified file instead of `echarts/src/lang.js`. For example:', - descIndent + '`--lang en` will use `echarts/src/langEN.js`.', - descIndent + '`--lang my/langDE.js` will use `/my/langDE.js`. -o must be specified in this case.', - descIndent + '`--lang /my/indexSW.js` will use `/my/indexSW.js`. -o must be specified in this case.' - ].join('\n')) .option( '--prepublish', 'Build all for release' @@ -118,7 +110,6 @@ async function run() { let isPrePublish = !!commander.prepublish; let opt = { - lang: commander.lang, min: commander.min, type: commander.type || '', input: commander.input, @@ -132,7 +123,6 @@ async function run() { }; validateIO(opt.input, opt.output); - validateLang(opt.lang, opt.output); if (isWatch) { watch(config.createECharts(opt)); @@ -172,21 +162,6 @@ function validateIO(input, output) { } } -function validateLang(lang, output) { - if (!lang) { - return; - } - - let langInfo = ecLangPlugin.getLangFileInfo(lang); - - if (langInfo.isOuter && !output) { - throw new Error('`-o` or `--output` must be specified if using a file path in `--lang`.'); - } - if (!langInfo.absolutePath || !fs.statSync(langInfo.absolutePath).isFile()) { - throw new Error(`File ${langInfo.absolutePath} does not exist yet. Contribution is welcome!`); - } -} - /** * @param {Array.} configs A list of rollup configs: * See: @@ -201,6 +176,8 @@ function validateLang(lang, output) { * ] */ async function build(configs, min, sourcemap) { + // buildI18n JSON before build when build + buildI18n(); // ensureZRenderCode.prepare(); diff --git a/build/config.js b/build/config.js index 5ec1858080..3d39d0ec88 100644 --- a/build/config.js +++ b/build/config.js @@ -19,7 +19,6 @@ const assert = require('assert'); const nodeResolvePlugin = require('rollup-plugin-node-resolve'); -const ecLangPlugin = require('./ec-lang-rollup-plugin'); const nodePath = require('path'); const ecDir = nodePath.resolve(__dirname, '..'); const typescriptPlugin = require('rollup-plugin-typescript2'); @@ -27,7 +26,7 @@ const fs = require('fs'); const progress = require('./progress'); function preparePlugins( - {lang, sourcemap, addBundleVersion, totalFiles, clean}, + {sourcemap, addBundleVersion, totalFiles, clean}, {include, exclude} ) { assert(include); @@ -71,10 +70,6 @@ function preparePlugins( }) ]; - lang && plugins.push( - ecLangPlugin({lang}) - ); - addBundleVersion && plugins.push({ outro: function () { return 'exports.bundleVersion = \'' + (+new Date()) + '\';'; diff --git a/build/ec-lang-rollup-plugin.js b/build/ec-lang-rollup-plugin.js deleted file mode 100644 index e05e6b0b8f..0000000000 --- a/build/ec-lang-rollup-plugin.js +++ /dev/null @@ -1,80 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - - -/** - * Find language definations. - * - * Usage: - * - * import ecLangPlugin from 'echarts/build/rollup-plugin-ec-lang'; - * let rollupConfig = { - * plugins: [ - * ecLangPlugin({lang: 'en'}), - * ... - * ] - * }; - */ - -const {resolve} = require('path'); -const {readFileSync} = require('fs'); - -/** - * @param {Object} [opt] - * @param {string} [opt.lang=null] null/undefined/'' or 'en' or 'fi' or a file path. - */ -function getPlugin(opt) { - let lang = opt && opt.lang || ''; - - return { - load: function (absolutePath) { - if (/\/src\/lang\.js$/.test(absolutePath)) { - let langPath = getLangFileInfo(lang).absolutePath; - if (langPath) { - absolutePath = langPath; - } - } - return readFileSync(absolutePath, 'utf-8'); - } - }; -} - -/** - * @param {string} lang null/undefined/'' or 'en' or 'fi' or a file path. - * @return {Object} {isOuter, absolutePath} - */ -let getLangFileInfo = getPlugin.getLangFileInfo = function (lang) { - let absolutePath; - let isOuter = false; - - if (lang) { - if (/^[a-zA-Z]{2}$/.test(lang)) { - absolutePath = resolve(__dirname, '../', 'src/lang' + lang.toUpperCase() + '.ts'); - } - else { - isOuter = true; - // `lang` is an absolute path or a relative path based on process.cwd(). - absolutePath = resolve(lang); - } - } - - return {isOuter, absolutePath}; -}; - -module.exports = getPlugin; \ No newline at end of file diff --git a/build/release.js b/build/release.js index defeeafdeb..667e6caf1f 100644 --- a/build/release.js +++ b/build/release.js @@ -41,40 +41,31 @@ function release() { } } } - [ - '', 'en' - ].forEach(function (lang) { - ['', 'simple', 'common', 'extension'].forEach(function (type) { - const args = [ - `--lang`, - lang, - `--type`, - type, - `--clean`, - `--sourcemap`, - `--min` - ]; + ['', 'simple', 'common', 'extension'].forEach(function (type) { - if (lang === 'en' && type === 'extension') { - return; - } + const args = [ + `--type`, + type, + `--clean`, + `--sourcemap`, + `--min` + ]; - const p = spawn(path.join(__dirname, 'build.js'), args); + const p = spawn(path.join(__dirname, 'build.js'), args); - const scope = `[${lang || 'zh'}] [${type || 'all'}]`; + const scope = `[${type || 'all'}]`; - function createOnDataHandler(idx) { - return function (data) { - logs[idx] = `${chalk.gray(scope)}: ${data}`; - updateLog(); - } + function createOnDataHandler(idx) { + return function (data) { + logs[idx] = `${chalk.gray(scope)}: ${data}`; + updateLog(); } + } - p.stdout.on('data', createOnDataHandler(idx)); + p.stdout.on('data', createOnDataHandler(idx)); - idx++; - }); + idx++; }); } diff --git a/i18n/langEN-obj.js b/i18n/langEN-obj.js new file mode 100644 index 0000000000..fe6824e5e0 --- /dev/null +++ b/i18n/langEN-obj.js @@ -0,0 +1,172 @@ + + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports); + } else { + // Browser globals + factory({}); + } +})(this, function(exports) { + + +/** + * Language: English. + */ + +var localeObj = { + time: { + month: [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ], + monthAbbr: [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' + ], + dayOfWeek: [ + 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' + ], + dayOfWeekAbbr: [ + 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' + ] + }, + legend: { + selector: { + all: 'All', + inverse: 'Inv' + } + }, + toolbox: { + brush: { + title: { + rect: 'Box Select', + polygon: 'Lasso Select', + lineX: 'Horizontally Select', + lineY: 'Vertically Select', + keep: 'Keep Selections', + clear: 'Clear Selections' + } + }, + dataView: { + title: 'Data View', + lang: ['Data View', 'Close', 'Refresh'] + }, + dataZoom: { + title: { + zoom: 'Zoom', + back: 'Zoom Reset' + } + }, + magicType: { + title: { + line: 'Switch to Line Chart', + bar: 'Switch to Bar Chart', + stack: 'Stack', + tiled: 'Tile' + } + }, + restore: { + title: 'Restore' + }, + saveAsImage: { + title: 'Save as Image', + lang: ['Right Click to Save Image'] + } + }, + series: { + typeNames: { + pie: 'Pie chart', + bar: 'Bar chart', + line: 'Line chart', + scatter: 'Scatter plot', + effectScatter: 'Ripple scatter plot', + radar: 'Radar chart', + tree: 'Tree', + treemap: 'Treemap', + boxplot: 'Boxplot', + candlestick: 'Candlestick', + k: 'K line chart', + heatmap: 'Heat map', + map: 'Map', + parallel: 'Parallel coordinate map', + lines: 'Line graph', + graph: 'Relationship graph', + sankey: 'Sankey diagram', + funnel: 'Funnel chart', + gauge: 'Guage', + pictorialBar: 'Pictorial bar', + themeRiver: 'Theme River Map', + sunburst: 'Sunburst' + } + }, + aria: { + general: { + withTitle: 'This is a chart about "{title}"', + withoutTitle: 'This is a chart' + }, + series: { + single: { + prefix: '', + withName: ' with type {seriesType} named {seriesName}.', + withoutName: ' with type {seriesType}.' + }, + multiple: { + prefix: '. It consists of {seriesCount} series count.', + withName: ' The {seriesId} series is a {seriesType} representing {seriesName}.', + withoutName: ' The {seriesId} series is a {seriesType}.', + separator: { + middle: '', + end: '' + } + } + }, + data: { + allData: 'The data is as follows: ', + partialData: 'The first {displayCnt} items are: ', + withName: 'the data for {name} is {value}', + withoutName: '{value}', + separator: { + middle: ', ', + end: '. ' + } + } + } +}; + for (var key in localeObj) { + if (localeObj.hasOwnProperty(key)) { + exports[key] = localeObj[key]; + } + } + +}); \ No newline at end of file diff --git a/i18n/langEN.js b/i18n/langEN.js new file mode 100644 index 0000000000..37e347263a --- /dev/null +++ b/i18n/langEN.js @@ -0,0 +1,168 @@ + + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports', 'echarts'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports, require('echarts')); + } else { + // Browser globals + factory({}, root.echarts); + } +})(this, function(exports, echarts) { + + +/** + * Language: English. + */ + +var localeObj = { + time: { + month: [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ], + monthAbbr: [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' + ], + dayOfWeek: [ + 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' + ], + dayOfWeekAbbr: [ + 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' + ] + }, + legend: { + selector: { + all: 'All', + inverse: 'Inv' + } + }, + toolbox: { + brush: { + title: { + rect: 'Box Select', + polygon: 'Lasso Select', + lineX: 'Horizontally Select', + lineY: 'Vertically Select', + keep: 'Keep Selections', + clear: 'Clear Selections' + } + }, + dataView: { + title: 'Data View', + lang: ['Data View', 'Close', 'Refresh'] + }, + dataZoom: { + title: { + zoom: 'Zoom', + back: 'Zoom Reset' + } + }, + magicType: { + title: { + line: 'Switch to Line Chart', + bar: 'Switch to Bar Chart', + stack: 'Stack', + tiled: 'Tile' + } + }, + restore: { + title: 'Restore' + }, + saveAsImage: { + title: 'Save as Image', + lang: ['Right Click to Save Image'] + } + }, + series: { + typeNames: { + pie: 'Pie chart', + bar: 'Bar chart', + line: 'Line chart', + scatter: 'Scatter plot', + effectScatter: 'Ripple scatter plot', + radar: 'Radar chart', + tree: 'Tree', + treemap: 'Treemap', + boxplot: 'Boxplot', + candlestick: 'Candlestick', + k: 'K line chart', + heatmap: 'Heat map', + map: 'Map', + parallel: 'Parallel coordinate map', + lines: 'Line graph', + graph: 'Relationship graph', + sankey: 'Sankey diagram', + funnel: 'Funnel chart', + gauge: 'Guage', + pictorialBar: 'Pictorial bar', + themeRiver: 'Theme River Map', + sunburst: 'Sunburst' + } + }, + aria: { + general: { + withTitle: 'This is a chart about "{title}"', + withoutTitle: 'This is a chart' + }, + series: { + single: { + prefix: '', + withName: ' with type {seriesType} named {seriesName}.', + withoutName: ' with type {seriesType}.' + }, + multiple: { + prefix: '. It consists of {seriesCount} series count.', + withName: ' The {seriesId} series is a {seriesType} representing {seriesName}.', + withoutName: ' The {seriesId} series is a {seriesType}.', + separator: { + middle: '', + end: '' + } + } + }, + data: { + allData: 'The data is as follows: ', + partialData: 'The first {displayCnt} items are: ', + withName: 'the data for {name} is {value}', + withoutName: '{value}', + separator: { + middle: ', ', + end: '. ' + } + } + } +}; + echarts.registerLocale('EN', localeObj); + +}); \ No newline at end of file diff --git a/i18n/langES-obj.js b/i18n/langES-obj.js new file mode 100644 index 0000000000..1744746fc2 --- /dev/null +++ b/i18n/langES-obj.js @@ -0,0 +1,95 @@ + + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports); + } else { + // Browser globals + factory({}); + } +})(this, function(exports) { + + +var localeObj = { + legend: { + selector: { + all: "Todas", + inverse: "Inversa" + } + }, + toolbox: { + brush: { + title: { + rect: "Selección de cuadro", + polygon: "Selección de lazo", + lineX: "Seleccionar horizontalmente", + lineY: "Seleccionar verticalmente", + keep: "Mantener selección", + clear: "Despejar selecciones" + } + }, + dataView: { + title: "Ver datos", + lang: ["Ver datos", "Cerrar", "Actualizar"] + }, + dataZoom: { + title: { + zoom: "Zoom", + back: "Restablecer zoom" + } + }, + magicType: { + title: { + line: "Cambiar a gráfico de líneas", + bar: "Cambiar a gráfico de barras", + stack: "Pila", + tiled: "Teja" + } + }, + restore: { + title: "Restaurar" + }, + saveAsImage: { + title: "Guardar como imagen", + lang: ["Clic derecho para guardar imagen"] + } + } +}; + + for (var key in localeObj) { + if (localeObj.hasOwnProperty(key)) { + exports[key] = localeObj[key]; + } + } + +}); \ No newline at end of file diff --git a/i18n/langES.js b/i18n/langES.js new file mode 100644 index 0000000000..6edce52078 --- /dev/null +++ b/i18n/langES.js @@ -0,0 +1,91 @@ + + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports', 'echarts'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports, require('echarts')); + } else { + // Browser globals + factory({}, root.echarts); + } +})(this, function(exports, echarts) { + + +var localeObj = { + legend: { + selector: { + all: "Todas", + inverse: "Inversa" + } + }, + toolbox: { + brush: { + title: { + rect: "Selección de cuadro", + polygon: "Selección de lazo", + lineX: "Seleccionar horizontalmente", + lineY: "Seleccionar verticalmente", + keep: "Mantener selección", + clear: "Despejar selecciones" + } + }, + dataView: { + title: "Ver datos", + lang: ["Ver datos", "Cerrar", "Actualizar"] + }, + dataZoom: { + title: { + zoom: "Zoom", + back: "Restablecer zoom" + } + }, + magicType: { + title: { + line: "Cambiar a gráfico de líneas", + bar: "Cambiar a gráfico de barras", + stack: "Pila", + tiled: "Teja" + } + }, + restore: { + title: "Restaurar" + }, + saveAsImage: { + title: "Guardar como imagen", + lang: ["Clic derecho para guardar imagen"] + } + } +}; + + echarts.registerLocale('ES', localeObj); + +}); \ No newline at end of file diff --git a/i18n/langFI-obj.js b/i18n/langFI-obj.js new file mode 100644 index 0000000000..42e53b5a4e --- /dev/null +++ b/i18n/langFI-obj.js @@ -0,0 +1,95 @@ + + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports); + } else { + // Browser globals + factory({}); + } +})(this, function(exports) { + + +var localeObj = { + legend: { + selector: { + all: "Kaikki", + inverse: "Käänteinen" + } + }, + toolbox: { + brush: { + title: { + rect: "Laatikko valinta", + polygon: "Lasso valinta", + lineX: "Vaakataso valinta", + lineY: "Pysty valinta", + keep: "Pidä valinta", + clear: "Poista valinta" + } + }, + dataView: { + title: "Data näkymä", + lang: ["Data näkymä", "Sulje", "Päivitä"] + }, + dataZoom: { + title: { + zoom: "Zoomaa", + back: "Zoomin nollaus" + } + }, + magicType: { + title: { + line: "Vaihda Viivakaavioon", + bar: "Vaihda palkkikaavioon", + stack: "Pinoa", + tiled: "Erottele" + } + }, + restore: { + title: "Palauta" + }, + saveAsImage: { + title: "Tallenna kuvana", + lang: ["Paina oikeaa hiirennappia tallentaaksesi kuva"] + } + } +}; + + for (var key in localeObj) { + if (localeObj.hasOwnProperty(key)) { + exports[key] = localeObj[key]; + } + } + +}); \ No newline at end of file diff --git a/i18n/langFI.js b/i18n/langFI.js new file mode 100644 index 0000000000..435be693be --- /dev/null +++ b/i18n/langFI.js @@ -0,0 +1,91 @@ + + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports', 'echarts'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports, require('echarts')); + } else { + // Browser globals + factory({}, root.echarts); + } +})(this, function(exports, echarts) { + + +var localeObj = { + legend: { + selector: { + all: "Kaikki", + inverse: "Käänteinen" + } + }, + toolbox: { + brush: { + title: { + rect: "Laatikko valinta", + polygon: "Lasso valinta", + lineX: "Vaakataso valinta", + lineY: "Pysty valinta", + keep: "Pidä valinta", + clear: "Poista valinta" + } + }, + dataView: { + title: "Data näkymä", + lang: ["Data näkymä", "Sulje", "Päivitä"] + }, + dataZoom: { + title: { + zoom: "Zoomaa", + back: "Zoomin nollaus" + } + }, + magicType: { + title: { + line: "Vaihda Viivakaavioon", + bar: "Vaihda palkkikaavioon", + stack: "Pinoa", + tiled: "Erottele" + } + }, + restore: { + title: "Palauta" + }, + saveAsImage: { + title: "Tallenna kuvana", + lang: ["Paina oikeaa hiirennappia tallentaaksesi kuva"] + } + } +}; + + echarts.registerLocale('FI', localeObj); + +}); \ No newline at end of file diff --git a/i18n/langTH-obj.js b/i18n/langTH-obj.js new file mode 100644 index 0000000000..46f4ee9385 --- /dev/null +++ b/i18n/langTH-obj.js @@ -0,0 +1,95 @@ + + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports); + } else { + // Browser globals + factory({}); + } +})(this, function(exports) { + + +var localeObj = { + legend: { + selector: { + all: "ทั้งหมด", + inverse: "ผกผัน" + } + }, + toolbox: { + brush: { + title: { + rect: "ตัวเลือกแบบกล่อง", + polygon: "ตัวเลือกแบบบ่วงบาศ", + lineX: "ตัวเลือกแบบแนวนอน", + lineY: "ตัวเลือกแบบแนวตั้ง", + keep: "บันทึกตัวเลือก", + clear: "ล้างตัวเลือก" + } + }, + dataView: { + title: "มุมมองข้อมูล", + lang: ["มุมมองข้อมูล", "ปิด", "รีเฟรช"] + }, + dataZoom: { + title: { + zoom: "ซูม", + back: "ตั้งซูมใหม่" + } + }, + magicType: { + title: { + line: "สวิตซ์แบบแผนภาพเส้น", + bar: "สวิตซ์แบบแผนภาพแท่ง", + stack: "กองไว้", + tiled: "แยกไว้" + } + }, + restore: { + title: "ตั้งค่าใหม่" + }, + saveAsImage: { + title: "บันทึกไปยังรูปภาพ", + lang: ["คลิกขวาเพื่อบันทึกรูปภาพ"] + } + } +}; + + for (var key in localeObj) { + if (localeObj.hasOwnProperty(key)) { + exports[key] = localeObj[key]; + } + } + +}); \ No newline at end of file diff --git a/i18n/langTH.js b/i18n/langTH.js new file mode 100644 index 0000000000..db7ecce3b4 --- /dev/null +++ b/i18n/langTH.js @@ -0,0 +1,91 @@ + + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports', 'echarts'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports, require('echarts')); + } else { + // Browser globals + factory({}, root.echarts); + } +})(this, function(exports, echarts) { + + +var localeObj = { + legend: { + selector: { + all: "ทั้งหมด", + inverse: "ผกผัน" + } + }, + toolbox: { + brush: { + title: { + rect: "ตัวเลือกแบบกล่อง", + polygon: "ตัวเลือกแบบบ่วงบาศ", + lineX: "ตัวเลือกแบบแนวนอน", + lineY: "ตัวเลือกแบบแนวตั้ง", + keep: "บันทึกตัวเลือก", + clear: "ล้างตัวเลือก" + } + }, + dataView: { + title: "มุมมองข้อมูล", + lang: ["มุมมองข้อมูล", "ปิด", "รีเฟรช"] + }, + dataZoom: { + title: { + zoom: "ซูม", + back: "ตั้งซูมใหม่" + } + }, + magicType: { + title: { + line: "สวิตซ์แบบแผนภาพเส้น", + bar: "สวิตซ์แบบแผนภาพแท่ง", + stack: "กองไว้", + tiled: "แยกไว้" + } + }, + restore: { + title: "ตั้งค่าใหม่" + }, + saveAsImage: { + title: "บันทึกไปยังรูปภาพ", + lang: ["คลิกขวาเพื่อบันทึกรูปภาพ"] + } + } +}; + + echarts.registerLocale('TH', localeObj); + +}); \ No newline at end of file diff --git a/i18n/langZH-obj.js b/i18n/langZH-obj.js new file mode 100644 index 0000000000..8807ba483b --- /dev/null +++ b/i18n/langZH-obj.js @@ -0,0 +1,168 @@ + + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports); + } else { + // Browser globals + factory({}); + } +})(this, function(exports) { + + +var localeObj = { + time: { + month: [ + '一月', '二月', '三月', '四月', '五月', '六月', + '七月', '八月', '九月', '十月', '十一月', '十二月' + ], + monthAbbr: [ + '1月', '2月', '3月', '4月', '5月', '6月', + '7月', '8月', '9月', '10月', '11月', '12月' + ], + dayOfWeek: [ + '星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六' + ], + dayOfWeekAbbr: [ + '日', '一', '二', '三', '四', '五', '六' + ] + }, + legend: { + selector: { + all: '全选', + inverse: '反选' + } + }, + toolbox: { + brush: { + title: { + rect: '矩形选择', + polygon: '圈选', + lineX: '横向选择', + lineY: '纵向选择', + keep: '保持选择', + clear: '清除选择' + } + }, + dataView: { + title: '数据视图', + lang: ['数据视图', '关闭', '刷新'] + }, + dataZoom: { + title: { + zoom: '区域缩放', + back: '区域缩放还原' + } + }, + magicType: { + title: { + line: '切换为折线图', + bar: '切换为柱状图', + stack: '切换为堆叠', + tiled: '切换为平铺' + } + }, + restore: { + title: '还原' + }, + saveAsImage: { + title: '保存为图片', + lang: ['右键另存为图片'] + } + }, + series: { + typeNames: { + pie: '饼图', + bar: '柱状图', + line: '折线图', + scatter: '散点图', + effectScatter: '涟漪散点图', + radar: '雷达图', + tree: '树图', + treemap: '矩形树图', + boxplot: '箱型图', + candlestick: 'K线图', + k: 'K线图', + heatmap: '热力图', + map: '地图', + parallel: '平行坐标图', + lines: '线图', + graph: '关系图', + sankey: '桑基图', + funnel: '漏斗图', + gauge: '仪表盘图', + pictorialBar: '象形柱图', + themeRiver: '主题河流图', + sunburst: '旭日图' + } + }, + aria: { + general: { + withTitle: '这是一个关于“{title}”的图表。', + withoutTitle: '这是一个图表,' + }, + series: { + single: { + prefix: '', + withName: '图表类型是{seriesType},表示{seriesName}。', + withoutName: '图表类型是{seriesType}。' + }, + multiple: { + prefix: '它由{seriesCount}个图表系列组成。', + withName: '第{seriesId}个系列是一个表示{seriesName}的{seriesType},', + withoutName: '第{seriesId}个系列是一个{seriesType},', + separator: { + middle: ';', + end: '。' + } + } + }, + data: { + allData: '其数据是——', + partialData: '其中,前{displayCnt}项是——', + withName: '{name}的数据是{value}', + withoutName: '{value}', + separator: { + middle: ',', + end: '' + } + } + } +}; + for (var key in localeObj) { + if (localeObj.hasOwnProperty(key)) { + exports[key] = localeObj[key]; + } + } + +}); \ No newline at end of file diff --git a/src/lang.ts b/i18n/langZH.js similarity index 88% rename from src/lang.ts rename to i18n/langZH.js index 42dc196ec5..022d01fa35 100644 --- a/src/lang.ts +++ b/i18n/langZH.js @@ -1,3 +1,5 @@ + + /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -17,11 +19,28 @@ * under the License. */ + /** - * Language: (Simplified) Chinese. + * AUTO-GENERATED FILE. DO NOT MODIFY. */ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports', 'echarts'], factory); + } else if ( + typeof exports === 'object' && + typeof exports.nodeName !== 'string' + ) { + // CommonJS + factory(exports, require('echarts')); + } else { + // Browser globals + factory({}, root.echarts); + } +})(this, function(exports, echarts) { + -export default { +var localeObj = { time: { month: [ '一月', '二月', '三月', '四月', '五月', '六月', @@ -140,3 +159,6 @@ export default { } } }; + echarts.registerLocale('ZH', localeObj); + +}); \ No newline at end of file diff --git a/package.json b/package.json index c533be1785..623753e7e5 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,10 @@ "scripts": { "prepublish": "node build/build.js --prepublish", "build": "node build/build.js", + "build:i18n": "node build/build-i18n.js", "build:full": "node build/build.js --clean", "watch": "node build/build.js --watch", - "dev:fast": "node build/dev-fast.js", + "dev:fast": "node build/build-i18n.js && node build/dev-fast.js", "release": "node build/release.js", "help": "node build/build.js --help", "test:visual": "node test/runTest/server.js", diff --git a/src/component/legend/LegendModel.ts b/src/component/legend/LegendModel.ts index 290b7b348d..4ee162bf45 100644 --- a/src/component/legend/LegendModel.ts +++ b/src/component/legend/LegendModel.ts @@ -20,7 +20,6 @@ import * as zrUtil from 'zrender/src/core/util'; import Model from '../../model/Model'; import {isNameSpecified} from '../../util/model'; -import lang from '../../lang'; import ComponentModel from '../../model/Component'; import { ComponentOption, @@ -35,16 +34,22 @@ import { import { Dictionary } from 'zrender/src/core/types'; import GlobalModel from '../../model/Global'; -const langSelector = lang.legend.selector; - -const defaultSelectorOption = { - all: { - type: 'all', - title: zrUtil.clone(langSelector.all) - }, - inverse: { - type: 'inverse', - title: zrUtil.clone(langSelector.inverse) +type LegendDefaultSelectorOptionsProps = { + type: string; + title: string; +}; +const getDefaultSelectorOptions = function (ecModel: GlobalModel, type: string): LegendDefaultSelectorOptionsProps { + if (type === 'all') { + return { + type: 'all', + title: ecModel.getLocale(['legend', 'selector', 'all']) + }; + } + else if (type === 'inverse') { + return { + type: 'inverse', + title: ecModel.getLocale(['legend', 'selector', 'inverse']) + }; } }; @@ -201,6 +206,7 @@ class LegendModel extends ComponentMode _updateSelector(option: Ops) { let selector = option.selector; + const {ecModel} = this; if (selector === true) { selector = option.selector = ['all', 'inverse']; } @@ -208,7 +214,7 @@ class LegendModel extends ComponentMode zrUtil.each(selector, function (item, index) { zrUtil.isString(item) && (item = {type: item}); (selector as LegendSelectorButtonOption[])[index] = zrUtil.merge( - item, defaultSelectorOption[item.type] + item, getDefaultSelectorOptions(ecModel, item.type) ); }); } @@ -434,4 +440,4 @@ class LegendModel extends ComponentMode ComponentModel.registerClass(LegendModel); -export default LegendModel; \ No newline at end of file +export default LegendModel; diff --git a/src/component/timeline/SliderTimelineView.ts b/src/component/timeline/SliderTimelineView.ts index 8ebe1c1442..a088e7f70e 100644 --- a/src/component/timeline/SliderTimelineView.ts +++ b/src/component/timeline/SliderTimelineView.ts @@ -736,6 +736,7 @@ function createScaleByModel(model: SliderTimelineModel, axisType?: string): Scal }); case 'time': return new TimeScale({ + lang: model.ecModel.getLocaleModel(), useUTC: model.ecModel.get('useUTC') }); default: diff --git a/src/component/toolbox/ToolboxModel.ts b/src/component/toolbox/ToolboxModel.ts index 4c25d8fc75..10712e5ffb 100644 --- a/src/component/toolbox/ToolboxModel.ts +++ b/src/component/toolbox/ToolboxModel.ts @@ -86,9 +86,13 @@ class ToolboxModel extends ComponentModel { optionUpdated() { super.optionUpdated.apply(this, arguments as any); + const {ecModel} = this; zrUtil.each(this.option.feature, function (featureOpt, featureName) { const Feature = featureManager.getFeature(featureName); + if (Feature.getDefaultOption) { + Feature.defaultOption = Feature.getDefaultOption(ecModel); + } Feature && zrUtil.merge(featureOpt, Feature.defaultOption); }); } @@ -147,4 +151,4 @@ class ToolboxModel extends ComponentModel { ComponentModel.registerClass(ToolboxModel); -export default ToolboxModel; \ No newline at end of file +export default ToolboxModel; diff --git a/src/component/toolbox/feature/Brush.ts b/src/component/toolbox/feature/Brush.ts index 984fa33772..c935d69f4f 100644 --- a/src/component/toolbox/feature/Brush.ts +++ b/src/component/toolbox/feature/Brush.ts @@ -18,7 +18,6 @@ */ import * as zrUtil from 'zrender/src/core/util'; -import lang from '../../../lang'; import { ToolboxFeatureModel, ToolboxFeatureOption, @@ -30,8 +29,6 @@ import ExtensionAPI from '../../../ExtensionAPI'; import BrushModel from '../../brush/BrushModel'; import { BrushTypeUncertain } from '../../helper/BrushController'; -const brushLang = lang.toolbox.brush; - const ICON_TYPES = ['rect', 'polygon', 'lineX', 'lineY', 'keep', 'clear'] as const; type IconType = typeof ICON_TYPES[number]; @@ -132,24 +129,28 @@ class BrushFeature extends ToolboxFeature { } }; - static defaultOption: ToolboxBrushFeatureOption = { - show: true, - type: ICON_TYPES.slice(), - icon: { - /* eslint-disable */ - rect: 'M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13', // jshint ignore:line - polygon: 'M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2', // jshint ignore:line - lineX: 'M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9 M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4', // jshint ignore:line - lineY: 'M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4', // jshint ignore:line - keep: 'M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z', // jshint ignore:line - clear: 'M22,14.7l30.9,31 M52.9,14.7L22,45.7 M4.7,16.8V4.2h13.1 M26,4.2h7.8 M41.6,4.2h7.8 M70.3,16.8V4.2H57.2 M4.7,25.9v8.6 M70.3,25.9v8.6 M4.7,43.2v12.6h13.1 M26,55.8h7.8 M41.6,55.8h7.8 M70.3,43.2v12.6H57.2' // jshint ignore:line - /* eslint-enable */ - }, - // `rect`, `polygon`, `lineX`, `lineY`, `keep`, `clear` - title: zrUtil.clone(brushLang.title) - }; + static getDefaultOption(ecModel: GlobalModel) { + const defaultOption: ToolboxBrushFeatureOption = { + show: true, + type: ICON_TYPES.slice(), + icon: { + /* eslint-disable */ + rect: 'M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13', // jshint ignore:line + polygon: 'M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2', // jshint ignore:line + lineX: 'M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9 M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4', // jshint ignore:line + lineY: 'M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4', // jshint ignore:line + keep: 'M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z', // jshint ignore:line + clear: 'M22,14.7l30.9,31 M52.9,14.7L22,45.7 M4.7,16.8V4.2h13.1 M26,4.2h7.8 M41.6,4.2h7.8 M70.3,16.8V4.2H57.2 M4.7,25.9v8.6 M70.3,25.9v8.6 M4.7,43.2v12.6h13.1 M26,55.8h7.8 M41.6,55.8h7.8 M70.3,43.2v12.6H57.2' // jshint ignore:line + /* eslint-enable */ + }, + // `rect`, `polygon`, `lineX`, `lineY`, `keep`, `clear` + title: ecModel.getLocale(['toolbox', 'brush', 'title']) + }; + + return defaultOption; + } } registerFeature('brush', BrushFeature); -export default BrushFeature; \ No newline at end of file +export default BrushFeature; diff --git a/src/component/toolbox/feature/DataView.ts b/src/component/toolbox/feature/DataView.ts index 690f7d4ada..90fcd8ff9e 100644 --- a/src/component/toolbox/feature/DataView.ts +++ b/src/component/toolbox/feature/DataView.ts @@ -19,7 +19,6 @@ import * as echarts from '../../../echarts'; import * as zrUtil from 'zrender/src/core/util'; -import lang from '../../../lang'; import GlobalModel from '../../../model/Global'; import SeriesModel from '../../../model/Series'; import { ToolboxFeature, registerFeature, ToolboxFeatureOption } from '../featureManager'; @@ -29,8 +28,6 @@ import { addEventListener } from 'zrender/src/core/event'; import Axis from '../../../coord/Axis'; import Cartesian2D from '../../../coord/cartesian/Cartesian2D'; -const dataViewLang = lang.toolbox.dataView; - const BLOCK_SPLITER = new Array(60).join('-'); const ITEM_SPLITER = '\t'; @@ -423,23 +420,27 @@ class DataView extends ToolboxFeature { this.remove(ecModel, api); } - static defaultOption: ToolboxDataViewFeatureOption = { - show: true, - readOnly: false, - optionToContent: null, - contentToOption: null, - - // eslint-disable-next-line - icon: 'M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28', - title: zrUtil.clone(dataViewLang.title), - lang: zrUtil.clone(dataViewLang.lang), - backgroundColor: '#fff', - textColor: '#000', - textareaColor: '#fff', - textareaBorderColor: '#333', - buttonColor: '#c23531', - buttonTextColor: '#fff' - }; + static getDefaultOption(ecModel: GlobalModel) { + const defaultOption: ToolboxDataViewFeatureOption = { + show: true, + readOnly: false, + optionToContent: null, + contentToOption: null, + + // eslint-disable-next-line + icon: 'M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28', + title: ecModel.getLocale(['toolbox', 'dataView', 'title']), + lang: ecModel.getLocale(['toolbox', 'dataView', 'lang']), + backgroundColor: '#fff', + textColor: '#000', + textareaColor: '#fff', + textareaBorderColor: '#333', + buttonColor: '#c23531', + buttonTextColor: '#fff' + }; + + return defaultOption; + } } /** @@ -495,4 +496,4 @@ echarts.registerAction({ }, payload.newOption)); }); -export default DataView; \ No newline at end of file +export default DataView; diff --git a/src/component/toolbox/feature/DataZoom.ts b/src/component/toolbox/feature/DataZoom.ts index 688a14ca2a..c4330ac8e4 100644 --- a/src/component/toolbox/feature/DataZoom.ts +++ b/src/component/toolbox/feature/DataZoom.ts @@ -24,7 +24,6 @@ import BrushController, { BrushControllerEvents, BrushDimensionMinMax } from '.. import BrushTargetManager, { BrushTargetInfoCartesian2D } from '../../helper/BrushTargetManager'; import * as history from '../../dataZoom/history'; import sliderMove from '../../helper/sliderMove'; -import lang from '../../../lang'; // Use dataZoomSelect import '../../dataZoomSelect'; import { @@ -51,7 +50,6 @@ import { registerInternalOptionCreator } from '../../../model/internalComponentC import ComponentModel from '../../../model/Component'; -const dataZoomLang = lang.toolbox.dataZoom; const each = zrUtil.each; const DATA_ZOOM_ID_BASE = makeInternalComponentId('toolbox-dataZoom_'); @@ -206,17 +204,21 @@ class DataZoomFeature extends ToolboxFeature { }); } - static defaultOption: ToolboxDataZoomFeatureOption = { - show: true, - filterMode: 'filter', - // Icon group - icon: { - zoom: 'M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1', - back: 'M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26' - }, - // `zoom`, `back` - title: zrUtil.clone(dataZoomLang.title) - }; + static getDefaultOption(ecModel: GlobalModel) { + const defaultOption: ToolboxDataZoomFeatureOption = { + show: true, + filterMode: 'filter', + // Icon group + icon: { + zoom: 'M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1', + back: 'M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26' + }, + // `zoom`, `back` + title: ecModel.getLocale(['toolbox', 'dataZoom', 'title']) + }; + + return defaultOption; + } } const handlers: { [key in IconType]: (this: DataZoomFeature) => void } = { diff --git a/src/component/toolbox/feature/MagicType.ts b/src/component/toolbox/feature/MagicType.ts index b5b3e0ff00..e0aae5965b 100644 --- a/src/component/toolbox/feature/MagicType.ts +++ b/src/component/toolbox/feature/MagicType.ts @@ -19,7 +19,6 @@ import * as echarts from '../../../echarts'; import * as zrUtil from 'zrender/src/core/util'; -import lang from '../../../lang'; import {ToolboxFeature, ToolboxFeatureOption, ToolboxFeatureModel, registerFeature} from '../featureManager'; import { SeriesOption, ECUnitOption } from '../../../util/types'; import GlobalModel from '../../../model/Global'; @@ -27,7 +26,6 @@ import ExtensionAPI from '../../../ExtensionAPI'; import SeriesModel from '../../../model/Series'; import { SINGLE_REFERRING } from '../../../util/model'; -const magicTypeLang = lang.toolbox.magicType; const INNER_STACK_KEYWORD = '__ec_magicType_stack__' as const; const ICON_TYPES = ['line', 'bar', 'stack'] as const; @@ -74,21 +72,25 @@ class MagicType extends ToolboxFeature { return icons; } - static defaultOption: ToolboxMagicTypeFeatureOption = { - show: true, - type: [], - // Icon group - icon: { - line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4', - bar: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7', - // eslint-disable-next-line - stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z' // jshint ignore:line - }, - // `line`, `bar`, `stack`, `tiled` - title: zrUtil.clone(magicTypeLang.title), - option: {}, - seriesIndex: {} - }; + static getDefaultOption(ecModel: GlobalModel) { + const defaultOption: ToolboxMagicTypeFeatureOption = { + show: true, + type: [], + // Icon group + icon: { + line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4', + bar: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7', + // eslint-disable-next-line + stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z' // jshint ignore:line + }, + // `line`, `bar`, `stack`, `tiled` + title: ecModel.getLocale(['toolbox', 'magicType', 'title']), + option: {}, + seriesIndex: {} + }; + + return defaultOption; + } onclick(ecModel: GlobalModel, api: ExtensionAPI, type: IconType) { const model = this.model; @@ -153,11 +155,13 @@ class MagicType extends ToolboxFeature { // Change title of stack if (type === 'stack') { const seriesOptions = newOption.series as (SeriesOption & { stack: string })[]; + const tiledLang = ecModel.getLocale(['toolbox', 'magicType', 'title', 'tiled'] as any); + const titleLang = ecModel.getLocale(['toolbox', 'magicType', 'title']); const isStack = seriesOptions && seriesOptions[0] && seriesOptions[0].stack === INNER_STACK_KEYWORD; newTitle = isStack - ? zrUtil.merge({ stack: magicTypeLang.title.tiled }, magicTypeLang.title) - : zrUtil.clone(magicTypeLang.title); + ? zrUtil.merge({ stack: tiledLang }, titleLang) + : zrUtil.clone(titleLang); } api.dispatchAction({ @@ -234,4 +238,4 @@ echarts.registerAction({ registerFeature('magicType', MagicType); -export default MagicType; \ No newline at end of file +export default MagicType; diff --git a/src/component/toolbox/feature/Restore.ts b/src/component/toolbox/feature/Restore.ts index 9e650bddb8..debea2e2cc 100644 --- a/src/component/toolbox/feature/Restore.ts +++ b/src/component/toolbox/feature/Restore.ts @@ -19,13 +19,10 @@ import * as echarts from '../../../echarts'; import * as history from '../../dataZoom/history'; -import lang from '../../../lang'; import { ToolboxFeatureOption, ToolboxFeature, registerFeature } from '../featureManager'; import ExtensionAPI from '../../../ExtensionAPI'; import GlobalModel from '../../../model/Global'; -const restoreLang = lang.toolbox.restore; - export interface ToolboxRestoreFeatureOption extends ToolboxFeatureOption { icon?: string title?: string @@ -42,12 +39,16 @@ class RestoreOption extends ToolboxFeature { }); } - static defaultOption: ToolboxRestoreFeatureOption = { - show: true, - // eslint-disable-next-line - icon: 'M3.8,33.4 M47,18.9h9.8V8.7 M56.3,20.1 C52.1,9,40.5,0.6,26.8,2.1C12.6,3.7,1.6,16.2,2.1,30.6 M13,41.1H3.1v10.2 M3.7,39.9c4.2,11.1,15.8,19.5,29.5,18 c14.2-1.6,25.2-14.1,24.7-28.5', - title: restoreLang.title - }; + static getDefaultOption(ecModel: GlobalModel) { + const defaultOption: ToolboxRestoreFeatureOption = { + show: true, + // eslint-disable-next-line + icon: 'M3.8,33.4 M47,18.9h9.8V8.7 M56.3,20.1 C52.1,9,40.5,0.6,26.8,2.1C12.6,3.7,1.6,16.2,2.1,30.6 M13,41.1H3.1v10.2 M3.7,39.9c4.2,11.1,15.8,19.5,29.5,18 c14.2-1.6,25.2-14.1,24.7-28.5', + title: ecModel.getLocale(['toolbox', 'restore', 'title']) + }; + + return defaultOption; + } } registerFeature('restore', RestoreOption); @@ -57,4 +58,4 @@ echarts.registerAction( function (payload, ecModel) { ecModel.resetOption('recreate'); } -); \ No newline at end of file +); diff --git a/src/component/toolbox/feature/SaveAsImage.ts b/src/component/toolbox/feature/SaveAsImage.ts index f90d7185ab..5480b2a320 100644 --- a/src/component/toolbox/feature/SaveAsImage.ts +++ b/src/component/toolbox/feature/SaveAsImage.ts @@ -20,14 +20,11 @@ /* global Uint8Array */ import env from 'zrender/src/core/env'; -import lang from '../../../lang'; import { ToolboxFeature, ToolboxFeatureOption, registerFeature } from '../featureManager'; import { ZRColor } from '../../../util/types'; import GlobalModel from '../../../model/Global'; import ExtensionAPI from '../../../ExtensionAPI'; -const saveAsImageLang = lang.toolbox.saveAsImage; - export interface ToolboxSaveAsImageFeatureOption extends ToolboxFeatureOption { icon?: string title?: string @@ -97,20 +94,25 @@ class SaveAsImage extends ToolboxFeature { } } - static defaultOption: ToolboxSaveAsImageFeatureOption = { - show: true, - icon: 'M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0', - title: saveAsImageLang.title, - type: 'png', - // Default use option.backgroundColor - // backgroundColor: '#fff', - connectedBackgroundColor: '#fff', - name: '', - excludeComponents: ['toolbox'], - pixelRatio: 1, - lang: saveAsImageLang.lang.slice() - }; + static getDefaultOption(ecModel: GlobalModel) { + const defaultOption: ToolboxSaveAsImageFeatureOption = { + show: true, + icon: 'M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0', + title: ecModel.getLocale(['toolbox', 'saveAsImage', 'title']), + type: 'png', + // Default use option.backgroundColor + // backgroundColor: '#fff', + connectedBackgroundColor: '#fff', + name: '', + excludeComponents: ['toolbox'], + pixelRatio: 1, + lang: ecModel.getLocale(['toolbox', 'saveAsImage', 'lang']) + }; + + return defaultOption; + } } + SaveAsImage.prototype.unusable = !env.canvasSupported; registerFeature('saveAsImage', SaveAsImage); diff --git a/src/component/toolbox/featureManager.ts b/src/component/toolbox/featureManager.ts index 52367cd240..756e013617 100644 --- a/src/component/toolbox/featureManager.ts +++ b/src/component/toolbox/featureManager.ts @@ -106,7 +106,8 @@ type ToolboxFeatureCtor = { /** * Static defaultOption property */ - defaultOption: ToolboxFeatureOption + defaultOption?: ToolboxFeatureOption + getDefaultOption?: (ecModel: GlobalModel) => ToolboxFeatureOption }; const features: Dictionary = {}; diff --git a/src/coord/axisHelper.ts b/src/coord/axisHelper.ts index 932e2a35da..d4624312ec 100644 --- a/src/coord/axisHelper.ts +++ b/src/coord/axisHelper.ts @@ -192,6 +192,7 @@ export function createScaleByModel(model: AxisBaseModel, axisType?: string): Sca }); case 'time': return new TimeScale({ + locale: model.ecModel.getLocaleModel(), useUTC: model.ecModel.get('useUTC') }); default: diff --git a/src/echarts.ts b/src/echarts.ts index 76334a7b02..b69a4783c2 100644 --- a/src/echarts.ts +++ b/src/echarts.ts @@ -100,6 +100,7 @@ import { handleLegacySelectEvents } from './legacy/dataSelectAction'; // At least canvas renderer. import 'zrender/src/canvas/canvas'; +import { createLocaleObject, SYSTEM_LANG, LocaleOption } from './locale'; declare let global: any; type ModelFinder = modelUtil.ModelFinder; @@ -277,6 +278,8 @@ class ECharts extends Eventful { private _theme: ThemeOption; + private _locale: LocaleOption; + private _chartsViews: ChartView[] = []; private _chartsMap: {[viewId: string]: ChartView} = {}; @@ -314,6 +317,7 @@ class ECharts extends Eventful { // Theme name or themeOption. theme?: string | ThemeOption, opts?: { + locale?: string | LocaleOption, renderer?: RendererType, devicePixelRatio?: number, width?: number, @@ -353,6 +357,8 @@ class ECharts extends Eventful { this._theme = theme; + this._locale = createLocaleObject(opts.locale || SYSTEM_LANG); + this._coordSysMgr = new CoordinateSystemManager(); const api = this._api = createExtensionAPI(this); @@ -507,7 +513,7 @@ class ECharts extends Eventful { const theme = this._theme; const ecModel = this._model = new GlobalModel(); ecModel.scheduler = this._scheduler; - ecModel.init(null, null, null, theme, optionManager); + ecModel.init(null, null, null, theme, this._locale, optionManager); } this._model.setOption(option, {replaceMerge: replaceMerge}, optionPreprocessorFuncs); @@ -2350,7 +2356,8 @@ export function init( renderer?: RendererType, devicePixelRatio?: number, width?: number, - height?: number + height?: number, + locale?: string | LocaleOption } ): ECharts { if (__DEV__) { @@ -2575,6 +2582,8 @@ export function getCoordinateSystemDimensions(type: string): DimensionDefinition } } +export {registerLocale} from './locale'; + /** * Layout is a special stage of visual encoding * Most visual encoding like color are common for different chart diff --git a/src/langEN.ts b/src/i18n/langEN.ts similarity index 99% rename from src/langEN.ts rename to src/i18n/langEN.ts index 3dc01b6518..35c2a1c432 100644 --- a/src/langEN.ts +++ b/src/i18n/langEN.ts @@ -139,4 +139,4 @@ export default { } } } -}; +}; \ No newline at end of file diff --git a/src/i18n/langES.ts b/src/i18n/langES.ts new file mode 100644 index 0000000000..ecd752a21b --- /dev/null +++ b/src/i18n/langES.ts @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default { + legend: { + selector: { + all: "Todas", + inverse: "Inversa" + } + }, + toolbox: { + brush: { + title: { + rect: "Selección de cuadro", + polygon: "Selección de lazo", + lineX: "Seleccionar horizontalmente", + lineY: "Seleccionar verticalmente", + keep: "Mantener selección", + clear: "Despejar selecciones" + } + }, + dataView: { + title: "Ver datos", + lang: ["Ver datos", "Cerrar", "Actualizar"] + }, + dataZoom: { + title: { + zoom: "Zoom", + back: "Restablecer zoom" + } + }, + magicType: { + title: { + line: "Cambiar a gráfico de líneas", + bar: "Cambiar a gráfico de barras", + stack: "Pila", + tiled: "Teja" + } + }, + restore: { + title: "Restaurar" + }, + saveAsImage: { + title: "Guardar como imagen", + lang: ["Clic derecho para guardar imagen"] + } + } +}; diff --git a/src/i18n/langFI.ts b/src/i18n/langFI.ts new file mode 100644 index 0000000000..55ec25f7b1 --- /dev/null +++ b/src/i18n/langFI.ts @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default { + legend: { + selector: { + all: "Kaikki", + inverse: "Käänteinen" + } + }, + toolbox: { + brush: { + title: { + rect: "Laatikko valinta", + polygon: "Lasso valinta", + lineX: "Vaakataso valinta", + lineY: "Pysty valinta", + keep: "Pidä valinta", + clear: "Poista valinta" + } + }, + dataView: { + title: "Data näkymä", + lang: ["Data näkymä", "Sulje", "Päivitä"] + }, + dataZoom: { + title: { + zoom: "Zoomaa", + back: "Zoomin nollaus" + } + }, + magicType: { + title: { + line: "Vaihda Viivakaavioon", + bar: "Vaihda palkkikaavioon", + stack: "Pinoa", + tiled: "Erottele" + } + }, + restore: { + title: "Palauta" + }, + saveAsImage: { + title: "Tallenna kuvana", + lang: ["Paina oikeaa hiirennappia tallentaaksesi kuva"] + } + } +}; diff --git a/src/i18n/langTH.ts b/src/i18n/langTH.ts new file mode 100644 index 0000000000..5d275afee8 --- /dev/null +++ b/src/i18n/langTH.ts @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default { + legend: { + selector: { + all: "ทั้งหมด", + inverse: "ผกผัน" + } + }, + toolbox: { + brush: { + title: { + rect: "ตัวเลือกแบบกล่อง", + polygon: "ตัวเลือกแบบบ่วงบาศ", + lineX: "ตัวเลือกแบบแนวนอน", + lineY: "ตัวเลือกแบบแนวตั้ง", + keep: "บันทึกตัวเลือก", + clear: "ล้างตัวเลือก" + } + }, + dataView: { + title: "มุมมองข้อมูล", + lang: ["มุมมองข้อมูล", "ปิด", "รีเฟรช"] + }, + dataZoom: { + title: { + zoom: "ซูม", + back: "ตั้งซูมใหม่" + } + }, + magicType: { + title: { + line: "สวิตซ์แบบแผนภาพเส้น", + bar: "สวิตซ์แบบแผนภาพแท่ง", + stack: "กองไว้", + tiled: "แยกไว้" + } + }, + restore: { + title: "ตั้งค่าใหม่" + }, + saveAsImage: { + title: "บันทึกไปยังรูปภาพ", + lang: ["คลิกขวาเพื่อบันทึกรูปภาพ"] + } + } +}; diff --git a/src/i18n/langZH.ts b/src/i18n/langZH.ts new file mode 100644 index 0000000000..4f1bd661a4 --- /dev/null +++ b/src/i18n/langZH.ts @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default { + time: { + month: [ + '一月', '二月', '三月', '四月', '五月', '六月', + '七月', '八月', '九月', '十月', '十一月', '十二月' + ], + monthAbbr: [ + '1月', '2月', '3月', '4月', '5月', '6月', + '7月', '8月', '9月', '10月', '11月', '12月' + ], + dayOfWeek: [ + '星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六' + ], + dayOfWeekAbbr: [ + '日', '一', '二', '三', '四', '五', '六' + ] + }, + legend: { + selector: { + all: '全选', + inverse: '反选' + } + }, + toolbox: { + brush: { + title: { + rect: '矩形选择', + polygon: '圈选', + lineX: '横向选择', + lineY: '纵向选择', + keep: '保持选择', + clear: '清除选择' + } + }, + dataView: { + title: '数据视图', + lang: ['数据视图', '关闭', '刷新'] + }, + dataZoom: { + title: { + zoom: '区域缩放', + back: '区域缩放还原' + } + }, + magicType: { + title: { + line: '切换为折线图', + bar: '切换为柱状图', + stack: '切换为堆叠', + tiled: '切换为平铺' + } + }, + restore: { + title: '还原' + }, + saveAsImage: { + title: '保存为图片', + lang: ['右键另存为图片'] + } + }, + series: { + typeNames: { + pie: '饼图', + bar: '柱状图', + line: '折线图', + scatter: '散点图', + effectScatter: '涟漪散点图', + radar: '雷达图', + tree: '树图', + treemap: '矩形树图', + boxplot: '箱型图', + candlestick: 'K线图', + k: 'K线图', + heatmap: '热力图', + map: '地图', + parallel: '平行坐标图', + lines: '线图', + graph: '关系图', + sankey: '桑基图', + funnel: '漏斗图', + gauge: '仪表盘图', + pictorialBar: '象形柱图', + themeRiver: '主题河流图', + sunburst: '旭日图' + } + }, + aria: { + general: { + withTitle: '这是一个关于“{title}”的图表。', + withoutTitle: '这是一个图表,' + }, + series: { + single: { + prefix: '', + withName: '图表类型是{seriesType},表示{seriesName}。', + withoutName: '图表类型是{seriesType}。' + }, + multiple: { + prefix: '它由{seriesCount}个图表系列组成。', + withName: '第{seriesId}个系列是一个表示{seriesName}的{seriesType},', + withoutName: '第{seriesId}个系列是一个{seriesType},', + separator: { + middle: ';', + end: '。' + } + } + }, + data: { + allData: '其数据是——', + partialData: '其中,前{displayCnt}项是——', + withName: '{name}的数据是{value}', + withoutName: '{value}', + separator: { + middle: ',', + end: '' + } + } + } +}; \ No newline at end of file diff --git a/src/langES.ts b/src/langES.ts deleted file mode 100644 index 2fba545c8a..0000000000 --- a/src/langES.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -/** - * Language: Spanish - */ - -export default { - legend: { - selector: { - all: 'Todas', - inverse: 'Inversa' - } - }, - toolbox: { - brush: { - title: { - rect: 'Selección de cuadro', - polygon: 'Selección de lazo', - lineX: 'Seleccionar horizontalmente', - lineY: 'Seleccionar verticalmente', - keep: 'Mantener selección', - clear: 'Despejar selecciones' - } - }, - dataView: { - title: 'Ver datos', - lang: ['Ver datos', 'Cerrar', 'Actualizar'] - }, - dataZoom: { - title: { - zoom: 'Zoom', - back: 'Restablecer zoom' - } - }, - magicType: { - title: { - line: 'Cambiar a gráfico de líneas', - bar: 'Cambiar a gráfico de barras', - stack: 'Pila', - tiled: 'Teja' - } - }, - restore: { - title: 'Restaurar' - }, - saveAsImage: { - title: 'Guardar como imagen', - lang: ['Clic derecho para guardar imagen'] - } - } -}; diff --git a/src/langFI.ts b/src/langFI.ts deleted file mode 100644 index bdeffe48ec..0000000000 --- a/src/langFI.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -/** - * Language: Finnish - */ - -export default { - legend: { - selector: { - all: 'Kaikki', - inverse: 'Käänteinen' - } - }, - toolbox: { - brush: { - title: { - rect: 'Laatikko valinta', - polygon: 'Lasso valinta', - lineX: 'Vaakataso valinta', - lineY: 'Pysty valinta', - keep: 'Pidä valinta', - clear: 'Poista valinta' - } - }, - dataView: { - title: 'Data näkymä', - lang: ['Data näkymä', 'Sulje', 'Päivitä'] - }, - dataZoom: { - title: { - zoom: 'Zoomaa', - back: 'Zoomin nollaus' - } - }, - magicType: { - title: { - line: 'Vaihda Viivakaavioon', - bar: 'Vaihda palkkikaavioon', - stack: 'Pinoa', - tiled: 'Erottele' - } - }, - restore: { - title: 'Palauta' - }, - saveAsImage: { - title: 'Tallenna kuvana', - lang: ['Paina oikeaa hiirennappia tallentaaksesi kuva'] - } - } -}; diff --git a/src/langTH.ts b/src/langTH.ts deleted file mode 100644 index 0ae102d15f..0000000000 --- a/src/langTH.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -/** - * Language: Thai - */ - -export default { - legend: { - selector: { - all: 'ทั้งหมด', - inverse: 'ผกผัน' - } - }, - toolbox: { - brush: { - title: { - rect: 'ตัวเลือกแบบกล่อง', - polygon: 'ตัวเลือกแบบบ่วงบาศ', - lineX: 'ตัวเลือกแบบแนวนอน', - lineY: 'ตัวเลือกแบบแนวตั้ง', - keep: 'บันทึกตัวเลือก', - clear: 'ล้างตัวเลือก' - } - }, - dataView: { - title: 'มุมมองข้อมูล', - lang: ['มุมมองข้อมูล', 'ปิด', 'รีเฟรช'] - }, - dataZoom: { - title: { - zoom: 'ซูม', - back: 'ตั้งซูมใหม่' - } - }, - magicType: { - title: { - line: 'สวิตซ์แบบแผนภาพเส้น', - bar: 'สวิตซ์แบบแผนภาพแท่ง', - stack: 'กองไว้', - tiled: 'แยกไว้' - } - }, - restore: { - title: 'ตั้งค่าใหม่' - }, - saveAsImage: { - title: 'บันทึกไปยังรูปภาพ', - lang: ['คลิกขวาเพื่อบันทึกรูปภาพ'] - } - } -}; diff --git a/src/locale.ts b/src/locale.ts new file mode 100644 index 0000000000..becfc45a49 --- /dev/null +++ b/src/locale.ts @@ -0,0 +1,79 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { Dictionary } from './util/types'; +import Model from './model/Model'; +import env from 'zrender/src/core/env'; +// default import ZH and EN lang +import langEN from './i18n/langEN'; +import langZH from './i18n/langZH'; +import { isString, clone, merge } from 'zrender/src/core/util'; + +export type LocaleOption = typeof langEN; + +const LOCALE_ZH = 'ZH'; +const LOCALE_EN = 'EN'; +const DEFAULT_LOCALE = LOCALE_EN; + +const localeStorage: Dictionary = {}; +const localeModels: Dictionary = {}; + +export const SYSTEM_LANG = !env.domSupported ? DEFAULT_LOCALE : (function () { + const langStr = ( + document.documentElement.lang || navigator.language || (navigator as any).browserLanguage + ).toUpperCase(); + return langStr.indexOf(LOCALE_ZH) > -1 ? LOCALE_ZH : DEFAULT_LOCALE; +})(); + +export function registerLocale(locale: string, localeObj: LocaleOption) { + locale = locale.toUpperCase(); + localeModels[locale] = new Model(localeObj); + localeStorage[locale] = localeObj; +} + +// export function getLocale(locale: string) { +// return localeStorage[locale]; +// } + +export function createLocaleObject(locale: string | LocaleOption): LocaleOption { + if (isString(locale)) { + const localeObj = localeStorage[locale.toUpperCase()] || {} as LocaleOption; + if (locale === LOCALE_ZH || locale === LOCALE_EN) { + return clone(localeObj); + } + else { + return merge(clone(localeObj), clone(localeStorage[DEFAULT_LOCALE]), false); + } + } + else { + return merge(clone(locale), clone(localeStorage[DEFAULT_LOCALE]), false); + } +} + +export function getLocaleModel(lang: string): Model { + return localeModels[lang]; +} + +export function getDefaultLocaleModel(): Model { + return localeModels[DEFAULT_LOCALE]; +} + +// Default locale +registerLocale(LOCALE_EN, langEN); +registerLocale(LOCALE_ZH, langZH); diff --git a/src/model/Global.ts b/src/model/Global.ts index 252148b236..18d1a2d0ef 100644 --- a/src/model/Global.ts +++ b/src/model/Global.ts @@ -57,6 +57,7 @@ import { import OptionManager from './OptionManager'; import Scheduler from '../stream/Scheduler'; import { concatInternalOptions } from './internalComponentCreator'; +import { LocaleOption } from '../locale'; export interface GlobalModelSetOptionOpts { replaceMerge: ComponentMainType | ComponentMainType[]; @@ -79,6 +80,8 @@ class GlobalModel extends Model { private _theme: Model; + private _locale: Model; + private _optionManager: OptionManager; private _componentsMap: HashMap; @@ -118,11 +121,13 @@ class GlobalModel extends Model { parentModel: Model, ecModel: GlobalModel, theme: object, + locale: object, optionManager: OptionManager ): void { theme = theme || {}; this.option = null; // Mark as not initialized. this._theme = new Model(theme); + this._locale = new Model(locale); this._optionManager = optionManager; } @@ -416,6 +421,15 @@ class GlobalModel extends Model { return this._theme; } + getLocaleModel(): Model { + return this._locale; + } + + getLocale(localePosition: Parameters['get']>[0]): any { + const locale = this.getLocaleModel(); + return locale.get(localePosition as any); + } + setUpdatePayload(payload: Payload) { this._payload = payload; } diff --git a/src/scale/Time.ts b/src/scale/Time.ts index 03b62b26ba..ade85c521e 100644 --- a/src/scale/Time.ts +++ b/src/scale/Time.ts @@ -61,9 +61,9 @@ import IntervalScale from './Interval'; import Scale from './Scale'; import {TimeScaleTick} from '../util/types'; import {TimeAxisLabelFormatterOption} from '../coord/axisCommonTypes'; -import { log, warn } from '../util/log'; -import { isForInStatement } from '@babel/types'; -import { getDataItemValue } from '../util/model'; +import { warn } from '../util/log'; +import { LocaleOption } from '../locale'; +import Model from '../model/Model'; // FIXME 公用? const bisect = function ( @@ -84,6 +84,12 @@ const bisect = function ( return lo; }; +interface TimeScale { + constructor(settings?: { + locale: Model, + useUTC: boolean + }): void +} class TimeScale extends IntervalScale { @@ -114,7 +120,8 @@ class TimeScale extends IntervalScale { labelFormatter: TimeAxisLabelFormatterOption ): string { const isUTC = this.getSetting('useUTC'); - return leveledFormat(tick, idx, labelFormatter, isUTC); + const lang = this.getSetting('locale'); + return leveledFormat(tick, idx, labelFormatter, lang, isUTC); } /** diff --git a/src/util/format.ts b/src/util/format.ts index d8eb86da92..ffdb30ba40 100644 --- a/src/util/format.ts +++ b/src/util/format.ts @@ -19,9 +19,9 @@ import * as zrUtil from 'zrender/src/core/util'; import * as numberUtil from './number'; -import * as timeUtil from './time'; import {TooltipRenderMode, ColorString} from './types'; import { Dictionary } from 'zrender/src/core/types'; +import { getDateFromStr, format, pad } from './time'; /** * Add a comma each three digit. @@ -104,10 +104,10 @@ export function formatTpl( const isTimeAxis = paramsList[0].axisType && paramsList[0].axisType.indexOf('time') >= 0; if (isTimeAxis) { const axisValue = paramsList[0].data[paramsList[0].axisIndex]; - const date = timeUtil.getDateFromStr( + const date = getDateFromStr( typeof axisValue === 'number' ? new Date(axisValue) : axisValue ); - return timeUtil.format(date, tpl); + return format(date, tpl); } else { const $vars = paramsList[0].$vars || []; @@ -200,11 +200,6 @@ export function getTooltipMarker(inOpt: ColorString | GetTooltipMarkerOpt, extra } } -export function pad(str: string, len: number): string { - str += ''; - return '0000'.substr(0, len - str.length) + str; -} - /** * ISO Date format diff --git a/src/util/time.ts b/src/util/time.ts index 577d7f2c73..ee2c29efd0 100644 --- a/src/util/time.ts +++ b/src/util/time.ts @@ -1,9 +1,28 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + import * as zrUtil from 'zrender/src/core/util'; import {TimeAxisLabelFormatterOption} from './../coord/axisCommonTypes'; import * as numberUtil from './number'; -import {pad} from './format'; -import lang from '../lang'; import {TimeScaleTick} from './types'; +import { getDefaultLocaleModel, getLocaleModel, SYSTEM_LANG, LocaleOption } from '../locale'; +import Model from '../model/Model'; export const ONE_SECOND = 1000; export const ONE_MINUTE = ONE_SECOND * 60; @@ -47,6 +66,11 @@ export const timeUnits: TimeUnit[] = [ 'half-day', 'quarter-day', 'hour', 'minute', 'second', 'millisecond' ]; +export function pad(str: string, len: number): string { + str += ''; + return '0000'.substr(0, len - str.length) + str; +} + export function getPrimaryTimeUnit(timeUnit: TimeUnit): PrimaryTimeUnit { switch (timeUnit) { case 'half-year': @@ -80,7 +104,9 @@ export function getDefaultFormatPrecisionOfInterval(timeUnit: PrimaryTimeUnit): } } -export function format(time: Date | number, template: string, isUTC?: boolean): string { +export function format( + time: Date | number, template: string, lang?: string | Model, isUTC?: boolean +): string { const date = numberUtil.parseDate(time); const utc = isUTC ? 'UTC' : ''; const y = (date as any)['get' + utc + 'FullYear'](); @@ -94,18 +120,27 @@ export function format(time: Date | number, template: string, isUTC?: boolean): const s = (date as any)['get' + utc + 'Seconds'](); const S = (date as any)['get' + utc + 'Milliseconds'](); + + const localeModel = lang instanceof Model ? lang + : getLocaleModel(lang || SYSTEM_LANG) || getDefaultLocaleModel(); + const timeModel = localeModel.getModel('time'); + const month = timeModel.get('month'); + const monthAbbr = timeModel.get('monthAbbr'); + const dayOfWeek = timeModel.get('dayOfWeek'); + const dayOfWeekAbbr = timeModel.get('dayOfWeekAbbr'); + return (template || '') .replace(/{yyyy}/g, y) .replace(/{yy}/g, y % 100 + '') .replace(/{Q}/g, q + '') - .replace(/{MMMM}/g, lang.time.month[M - 1]) - .replace(/{MMM}/g, lang.time.monthAbbr[M - 1]) + .replace(/{MMMM}/g, month[M - 1]) + .replace(/{MMM}/g, monthAbbr[M - 1]) .replace(/{MM}/g, pad(M, 2)) .replace(/{M}/g, M) .replace(/{dd}/g, pad(d, 2)) .replace(/{d}/g, d) - .replace(/{eeee}/g, lang.time.dayOfWeek[e]) - .replace(/{ee}/g, lang.time.dayOfWeekAbbr[e]) + .replace(/{eeee}/g, dayOfWeek[e]) + .replace(/{ee}/g, dayOfWeekAbbr[e]) .replace(/{e}/g, e) .replace(/{HH}/g, pad(H, 2)) .replace(/{H}/g, H) @@ -123,6 +158,7 @@ export function leveledFormat( tick: TimeScaleTick, idx: number, formatter: TimeAxisLabelFormatterOption, + lang: string | Model, isUTC?: boolean ) { let template = null; @@ -176,7 +212,7 @@ export function leveledFormat( } } - return format(new Date(tick.value), template, isUTC); + return format(new Date(tick.value), template, lang, isUTC); } export function getUnitFromValue( diff --git a/src/visual/aria.ts b/src/visual/aria.ts index 7e9b00acbb..49a985aab2 100644 --- a/src/visual/aria.ts +++ b/src/visual/aria.ts @@ -20,8 +20,9 @@ // @ts-nocheck import * as zrUtil from 'zrender/src/core/util'; -import lang from '../lang'; -import { retrieveRawValue } from '../data/helper/dataProvider'; +import { retrieveRawValue } + +from '../data/helper/dataProvider'; export default function (dom, ecModel) { const ariaModel = ecModel.getModel('aria'); @@ -145,7 +146,7 @@ export default function (dom, ecModel) { const userConfig = ariaModel.get(path); if (userConfig == null) { const pathArr = path.split('.'); - let result = lang.aria; + let result = ecModel.getLocale('aria'); for (let i = 0; i < pathArr.length; ++i) { result = result[pathArr[i]]; } @@ -165,6 +166,6 @@ export default function (dom, ecModel) { } function getSeriesTypeName(type) { - return lang.series.typeNames[type] || '自定义图'; + return ecModel.getLocale(['series', 'typeNames'])[type] || '自定义图'; } } diff --git a/test/lang.html b/test/lang.html new file mode 100644 index 0000000000..e8f516928a --- /dev/null +++ b/test/lang.html @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + +