diff --git a/built/src/Observer.d.ts b/built/src/Observer.d.ts new file mode 100644 index 0000000..fa29bc9 --- /dev/null +++ b/built/src/Observer.d.ts @@ -0,0 +1,2 @@ +declare const Observer: (obj: any, defaultKey?: string) => any; +export default Observer; diff --git a/built/src/Observer.js b/built/src/Observer.js new file mode 100644 index 0000000..d7ef1b1 --- /dev/null +++ b/built/src/Observer.js @@ -0,0 +1,36 @@ +const Observer = (obj, defaultKey = 'zh-CN') => { + Object.keys(obj.__data__ || obj).forEach(key => { + defineReactive(obj, key, defaultKey); + }); + return obj; +}; +const observe = value => { + if (!value || typeof value !== 'object') { + return; + } + Observer(value); +}; +var defineReactive = (obj, key, defaultKey) => { + var childObj = observe(obj[key]); + Object.defineProperty(obj, key, { + get() { + if (obj.__data__[key]) { + return obj.__data__[key]; + } + else if (obj.__metas__[defaultKey][key]) { + return obj.__metas__[defaultKey][key]; + } + }, + set(newVal) { + if (obj[key] === newVal) { + return; + } + obj[key] = newVal; + const cb = obj.callback[key]; + cb.call(obj); + childObj = observe(newVal); + } + }); +}; +export default Observer; +//# sourceMappingURL=Observer.js.map \ No newline at end of file diff --git a/built/src/Observer.js.map b/built/src/Observer.js.map new file mode 100644 index 0000000..86e3b7c --- /dev/null +++ b/built/src/Observer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Observer.js","sourceRoot":"","sources":["../../src/Observer.ts"],"names":[],"mappings":"AAKA,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,EAAE,EAAE;IAC7C,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QAC7C,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AACF,MAAM,OAAO,GAAG,KAAK,CAAC,EAAE;IAEtB,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QACvC,OAAO;KACR;IACD,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClB,CAAC,CAAC;AAEF,IAAI,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE;IAC5C,IAAI,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE;QAC9B,GAAG;YACD,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;gBACrB,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;aAC1B;iBAAM,IAAI,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,EAAE;gBACzC,OAAO,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC;aACvC;QACH,CAAC;QACD,GAAG,CAAC,MAAM;YACR,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,MAAM,EAAE;gBACvB,OAAO;aACR;YAED,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;YAElB,MAAM,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC7B,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAEb,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC;KACF,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,eAAe,QAAQ,CAAC"} \ No newline at end of file diff --git a/built/src/demo.d.ts b/built/src/demo.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/built/src/demo.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/built/src/demo.js b/built/src/demo.js new file mode 100644 index 0000000..36a57a9 --- /dev/null +++ b/built/src/demo.js @@ -0,0 +1,22 @@ +import IntlFormat from '../src/index'; +const intlFormat = IntlFormat.init('zh-CN', { + 'zh-CN': { + value: '值', + test: '测试', + testTemplate: '你有{value}条未读通知', + foo: { + bar: 'foobar' + }, + photo: '我{num, plural, =0 {没有照片} =1 {有1张照片} other {有#张照片}}' + }, + 'en-US': { + value: 'value' + } +}); +console.log(intlFormat.test, 'intlFormat.test'); +console.log(intlFormat.template(intlFormat.testTemplate, { + value: '22' +}), 'intlFormat.testTemplate'); +intlFormat.setLang('en-US'); +console.log(intlFormat.get('foo.bar'), 'get foo bar'); +//# sourceMappingURL=demo.js.map \ No newline at end of file diff --git a/built/src/demo.js.map b/built/src/demo.js.map new file mode 100644 index 0000000..24ed96c --- /dev/null +++ b/built/src/demo.js.map @@ -0,0 +1 @@ +{"version":3,"file":"demo.js","sourceRoot":"","sources":["../../src/demo.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,cAAc,CAAC;AAEtC,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE;IAC1C,OAAO,EAAE;QACP,KAAK,EAAE,GAAG;QACV,IAAI,EAAE,IAAI;QACV,YAAY,EAAE,gBAAgB;QAC9B,GAAG,EAAE;YACH,GAAG,EAAE,QAAQ;SACd;QACD,KAAK,EAAE,oDAAoD;KAC5D;IACD,OAAO,EAAE;QACP,KAAK,EAAE,OAAO;KACf;CACF,CAAC,CAAC;AAEH,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;AAEhD,OAAO,CAAC,GAAG,CACT,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,YAAY,EAAE;IAC3C,KAAK,EAAE,IAAI;CACZ,CAAC,EACF,yBAAyB,CAC1B,CAAC;AAEF,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAC5B,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,CAAC"} \ No newline at end of file diff --git a/built/src/index.d.ts b/built/src/index.d.ts new file mode 100644 index 0000000..59c7260 --- /dev/null +++ b/built/src/index.d.ts @@ -0,0 +1,17 @@ +export interface I18NAPI { + init?(lang: string, metas: object, defaultKey?: 'zh-CN'): I18NAPI; + setLang?(lang: string): void; + template?(str: string, args: object): string; + get(name: string, args?: object): string; +} +declare const IntlFormat: { + init: ( + lang: string, + metas: { + [key: string]: T; + }, + defaultKey?: string + ) => I18NAPI & T; +}; +export { IntlFormat }; +export default IntlFormat; diff --git a/built/src/index.js b/built/src/index.js new file mode 100644 index 0000000..be24a70 --- /dev/null +++ b/built/src/index.js @@ -0,0 +1,70 @@ +import IntlMessageFormat from 'intl-messageformat'; +import * as lodashGet from 'lodash.get'; +import Observer from './Observer'; +class I18N { + constructor(lang, metas, defaultKey) { + this.__lang__ = lang; + this.__metas__ = metas; + this.__data__ = metas[lang]; + this.__defaultKey__ = defaultKey; + } + setLang(lang) { + this.__lang__ = lang; + this.__data__ = this.__metas__[lang]; + } + getProp(obj, is, value) { + if (typeof is === 'string') { + is = is.split('.'); + } + if (is.length === 1 && value !== undefined) { + return (obj[is[0]] = value); + } + else if (is.length === 0) { + return obj; + } + else { + const prop = is.shift(); + if (value !== undefined && obj[prop] === undefined) { + obj[prop] = {}; + } + return this.getProp(obj[prop], is, value); + } + } + template(str, args) { + if (!str) { + return ''; + } + return str.replace(/\{(.+?)\}/g, (match, p1) => { + return this.getProp(Object.assign({}, this.__data__, args), p1); + }); + } + get(str, args) { + let msg = lodashGet(this.__data__, str); + if (!msg) { + msg = lodashGet(this.__metas__[this.__defaultKey__ || 'zh-CN'], str, str); + } + if (args) { + try { + msg = new IntlMessageFormat(msg, this.__lang__); + msg = msg.format(args); + return msg; + } + catch (err) { + console.warn(`canary-intl format message failed for key='${str}'`, err); + return ''; + } + } + else { + return msg; + } + } +} +const IntlFormat = { + init: (lang, metas, defaultKey) => { + const i18n = new I18N(lang, metas, defaultKey); + return Observer(i18n, defaultKey); + } +}; +export { IntlFormat }; +export default IntlFormat; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/built/src/index.js.map b/built/src/index.js.map new file mode 100644 index 0000000..ac541d9 --- /dev/null +++ b/built/src/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/built/test/test.d.ts b/built/test/test.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/built/test/test.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/built/test/test.js b/built/test/test.js new file mode 100644 index 0000000..f4f0351 --- /dev/null +++ b/built/test/test.js @@ -0,0 +1,70 @@ +import * as assert from 'assert'; +import IntlFormat from '../src/index'; +describe('IntlFormat', function () { + let intlFormat; + before(function () { + intlFormat = IntlFormat.init('zh-CN', { + 'zh-CN': { + value: '值', + test: '测试', + testTemplate: '你有{value}条未读通知', + foo: { + bar: 'foobar' + }, + photo: '我{num, plural, =0 {没有照片} =1 {有1张照片} other {有#张照片}}' + }, + 'en-US': { + value: 'value' + } + }); + }); + describe('get current value from certain language', function () { + it('get key test value for current lanuage', function () { + assert.equal(intlFormat.test, '测试'); + }); + it('Get method: get key test value for current lanuage', function () { + assert.equal(intlFormat.get('test'), '测试'); + assert.equal(intlFormat.get('photo', { + num: 0 + }), '我没有照片'); + assert.equal(intlFormat.get('photo', { + num: 1 + }), '我有1张照片'); + assert.equal(intlFormat.get('photo', { + num: 1000 + }), '我有1,000张照片'); + }); + it('Template method: get template values for current lanuage', function () { + assert.equal(intlFormat.template(intlFormat.testTemplate, { + value: 3 + }), '你有3条未读通知'); + assert.equal(intlFormat.get('testTemplate', { + value: 3 + }), '你有3条未读通知'); + }); + it('Different instance values', function () { + const intlFormat1 = IntlFormat.init('zh-CN', { + 'zh-CN': { + test: 'firstvalue' + } + }); + const intlFormat2 = IntlFormat.init('zh-CN', { + 'zh-CN': { + test: 'secondvalue' + } + }); + assert.equal(intlFormat1.test, 'firstvalue'); + assert.equal(intlFormat2.test, 'secondvalue'); + }); + it('Get deep value', function () { + assert.equal(intlFormat.foo.bar, 'foobar'); + assert.equal(intlFormat.get('foo.bar'), 'foobar'); + }); + it('获取默认中文值', function () { + intlFormat.setLang('en-US'); + assert.equal(intlFormat.foo.bar, 'foobar'); + assert.equal(intlFormat.get('foo.bar'), 'foobar'); + }); + }); +}); +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/built/test/test.js.map b/built/test/test.js.map new file mode 100644 index 0000000..a408c2d --- /dev/null +++ b/built/test/test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test.js","sourceRoot":"","sources":["../../test/test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..f75a29f --- /dev/null +++ b/demo/index.html @@ -0,0 +1,17 @@ + + + + + + + intl-format/Demo + + + + + + + + + \ No newline at end of file diff --git a/demo/webpack.config.js b/demo/webpack.config.js new file mode 100644 index 0000000..e011ba8 --- /dev/null +++ b/demo/webpack.config.js @@ -0,0 +1,46 @@ +const path = require('path'); // 导入路径包 +const webpack = require('webpack'); +const env = process.env.NODE_ENV; + +let config = { + entry: { + app: ['./src/demo'] + }, + cache: false, + watch: true, + output: { + path: path.resolve(__dirname, '../built'), + filename: 'app.js' + }, + devtool: 'inline-source-map', + resolve: { + // Add '.ts' and '.tsx' as resolvable extensions. + extensions: ['.ts', '.tsx', '.js', '.json'] + }, + // 使用loader模块 + module: { + loaders: [{ test: /\.tsx?$/, loader: 'ts-loader' }] + }, + + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(env) + }) + ], + devServer: { + contentBase: path.join(__dirname, '../'), + headers: { + 'Access-Control-Allow-Origin': '*' + }, + historyApiFallback: { + index: '/index.html' + }, + stats: 'minimal', + host: '0.0.0.0', + port: 8000, + inline: true, + compress: true + } +}; + +module.exports = config; diff --git a/package.json b/package.json new file mode 100644 index 0000000..c46c61a --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "@mi/canary-intl", + "version": "1.0.0", + "description": "I18N tools for universal javascript apps, easy use & better api;", + "main": "built/src/index.js", + "types": "built/src/index.d.ts", + "author": "zhaoyingbo", + "keywords": [ + "I18N", + "intl", + "react", + "G11N", + "L10N" + ], + "scripts": { + "lint": "tslint src", + "autofix": "tslint src --fix", + "build": "tsc", + "demo": "webpack-dev-server --config demo/webpack.config.js", + "demo-build": "webpack --config demo/webpack.config.js", + "test": "mocha --compilers ts:ts-node/register test/**/*.ts -R spec" + }, + "dependencies": { + "intl-messageformat": "^2.2.0", + "lodash.get": "^4.4.2" + }, + "devDependencies": { + "@types/mocha": "^2.2.42", + "@types/node": "^7.0.31", + "babel-cli": "^6.18.0", + "mocha": "^3.5.0", + "ts-loader": "^2.3.0", + "ts-node": "^3.3.0", + "tslint": "^5.5.0", + "typescript": "^2.4.2", + "webpack": "^3.3.0", + "webpack-cli": "^3.3.0", + "webpack-dev-server": "2.9.1" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/src/Observer.ts b/src/Observer.ts new file mode 100644 index 0000000..99c07ac --- /dev/null +++ b/src/Observer.ts @@ -0,0 +1,45 @@ +/** + * @file 值处理 + * @author linhuiw + */ + +const Observer = (obj, defaultKey = 'zh-CN') => { + Object.keys(obj.__data__ || obj).forEach(key => { + defineReactive(obj, key, defaultKey); + }); + return obj; +}; +const observe = value => { + // 判断是否为object类型,是就继续执行Observer + if (!value || typeof value !== 'object') { + return; + } + Observer(value); +}; + +var defineReactive = (obj, key, defaultKey) => { + var childObj = observe(obj[key]); + Object.defineProperty(obj, key, { + get() { + if (obj.__data__[key]) { + return obj.__data__[key]; + } else if (obj.__metas__[defaultKey][key]) { + return obj.__metas__[defaultKey][key]; + } + }, + set(newVal) { + if (obj[key] === newVal) { + return; + } + // 如果值有变化的话,做一些操作 + obj[key] = newVal; + // 执行回调 + const cb = obj.callback[key]; + cb.call(obj); + // 如果set进来的值为复杂类型,再递归它,加上set/get + childObj = observe(newVal); + } + }); +}; + +export default Observer; diff --git a/src/demo.ts b/src/demo.ts new file mode 100644 index 0000000..195527a --- /dev/null +++ b/src/demo.ts @@ -0,0 +1,28 @@ +import IntlFormat from '../src/index'; + +const intlFormat = IntlFormat.init('zh-CN', { + 'zh-CN': { + value: '值', + test: '测试', + testTemplate: '你有{value}条未读通知', + foo: { + bar: 'foobar' + }, + photo: '我{num, plural, =0 {没有照片} =1 {有1张照片} other {有#张照片}}' + }, + 'en-US': { + value: 'value' + } +}); + +console.log(intlFormat.test, 'intlFormat.test'); + +console.log( + intlFormat.template(intlFormat.testTemplate, { + value: '22' + }), + 'intlFormat.testTemplate' +); + +intlFormat.setLang('en-US'); +console.log(intlFormat.get('foo.bar'), 'get foo bar'); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4220cbb --- /dev/null +++ b/src/index.ts @@ -0,0 +1,116 @@ +/** + * @file I18N Tools + * @author linhuiw + */ + +import IntlMessageFormat from 'intl-messageformat'; +import lodashGet from 'lodash.get'; +import Observer from './Observer'; + +export interface I18NAPI { + /** + * 初始化对应语言 + * @param lang: 对应语言 + * @param metas: 所有语言的语言文件 + * @param defaultKey: 默认支持的文件枚举值 + */ + init?(lang: string, metas: object, defaultKey?: 'zh-CN'): I18NAPI; + /** + * 设置对应语言 + * @param lang: 切换的对应语言 + */ + setLang?(lang: string): void; + /** + * 获取对应语言的模板值 + * @param template: 对应语言的模板 + * @param args: 模板的参数 + */ + template?(str: string, args: object): string; + /** + * 获取对应语言的值 + * @param name: 对应语言的模板的 Key + * @param options: 模板的参数 + */ + get(name: string, args?: object): string; +} + +class I18N { + __lang__: string; + __metas__: any; + __data__: any; + __defaultKey__: string; + constructor(lang: string, metas: object, defaultKey?: string) { + this.__lang__ = lang; + this.__metas__ = metas; + this.__data__ = metas[lang]; + this.__defaultKey__ = defaultKey; + } + setLang(lang: string) { + this.__lang__ = lang; + this.__data__ = this.__metas__[lang]; + } + getProp(obj, is, value?) { + if (typeof is === 'string') { + is = is.split('.'); + } + if (is.length === 1 && value !== undefined) { + return (obj[is[0]] = value); + } else if (is.length === 0) { + return obj; + } else { + const prop = is.shift(); + if (value !== undefined && obj[prop] === undefined) { + obj[prop] = {}; + } + return this.getProp(obj[prop], is, value); + } + } + template(str, args) { + if (!str) { + return ''; + } + return str.replace(/\{(.+?)\}/g, (match, p1) => { + return this.getProp( + { + ...this.__data__, + ...args + }, + p1 + ); + }); + } + get(str, args?) { + let msg = lodashGet(this.__data__, str); + if (!msg) { + msg = lodashGet(this.__metas__[this.__defaultKey__ || 'zh-CN'], str, str); + } + if (args) { + try { + msg = new IntlMessageFormat(msg, this.__lang__); + msg = msg.format(args); + return msg; + } catch (err) { + console.warn(`canary-intl format message failed for key='${str}'`, err); + return ''; + } + } else { + return msg; + } + } +} + +const IntlFormat = { + init: ( + lang: string, + metas: { + [key: string]: T; + }, + defaultKey?: string + ): I18NAPI & T => { + const i18n = new I18N(lang, metas, defaultKey); + return Observer(i18n, defaultKey); + } +}; + +export { IntlFormat }; +export default IntlFormat; diff --git a/test/test.ts b/test/test.ts new file mode 100644 index 0000000..8ab7772 --- /dev/null +++ b/test/test.ts @@ -0,0 +1,84 @@ +import * as assert from 'assert'; +import IntlFormat from '../src/index'; +describe('IntlFormat', function() { + let intlFormat; + before(function() { + intlFormat = IntlFormat.init('zh-CN', { + 'zh-CN': { + value: '值', + test: '测试', + testTemplate: '你有{value}条未读通知', + foo: { + bar: 'foobar' + }, + photo: '我{num, plural, =0 {没有照片} =1 {有1张照片} other {有#张照片}}' + }, + 'en-US': { + value: 'value' + } + }); + }); + describe('get current value from certain language', function() { + it('get key test value for current lanuage', function() { + assert.equal(intlFormat.test, '测试'); + }); + it('Get method: get key test value for current lanuage', function() { + assert.equal(intlFormat.get('test'), '测试'); + assert.equal( + intlFormat.get('photo', { + num: 0 + }), + '我没有照片' + ); + assert.equal( + intlFormat.get('photo', { + num: 1 + }), + '我有1张照片' + ); + assert.equal( + intlFormat.get('photo', { + num: 1000 + }), + '我有1,000张照片' + ); + }); + it('Template method: get template values for current lanuage', function() { + assert.equal( + intlFormat.template(intlFormat.testTemplate, { + value: 3 + }), + '你有3条未读通知' + ); + assert.equal( + intlFormat.get('testTemplate', { + value: 3 + }), + '你有3条未读通知' + ); + }); + it('Different instance values', function() { + const intlFormat1: any = IntlFormat.init('zh-CN', { + 'zh-CN': { + test: 'firstvalue' + } + }); + const intlFormat2: any = IntlFormat.init('zh-CN', { + 'zh-CN': { + test: 'secondvalue' + } + }); + assert.equal(intlFormat1.test, 'firstvalue'); + assert.equal(intlFormat2.test, 'secondvalue'); + }); + it('Get deep value', function() { + assert.equal(intlFormat.foo.bar, 'foobar'); + assert.equal(intlFormat.get('foo.bar'), 'foobar'); + }); + it('获取默认中文值', function() { + intlFormat.setLang('en-US'); + assert.equal(intlFormat.foo.bar, 'foobar'); + assert.equal(intlFormat.get('foo.bar'), 'foobar'); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8a46582 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "noImplicitAny": false, + "module": "esnext", + "target": "es6", + "removeComments": true, + "preserveConstEnums": true, + "jsx": "react", + "sourceMap": true, + "declaration": true, + "outDir": "built", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "baseUrl": "./src/", + "allowSyntheticDefaultImports": true, + "lib": ["dom", "es2015", "es2016", "es2017"], + "types": ["node", "mocha"] + }, + "exclude": ["node_modules", "packages", "built"] +} \ No newline at end of file