diff --git a/README.md b/README.md index 3f79929..8eb489f 100644 --- a/README.md +++ b/README.md @@ -311,9 +311,11 @@ i18n.configure({ mustacheConfig: { tags: ['{{', '}}'], disable: false - } -}) + }, + // Parser can be any object that responds to .parse & .stringify + parser: JSON +}) ``` The locale itself is gathered directly from the browser by header, cookie or query parameter depending on your setup. @@ -376,6 +378,18 @@ i18n.configure({ **NOTE:** Enabling `staticCatalog` disables all other fs realated options such as `updateFiles`, `autoReload` and `syncFiles` +#### Some words on `parser` option + +Instead of parsing all file contents as JSON, you can parse them as YAML or any other format you like +```js +const YAML = require('yaml') + +i18n.configure({ + extension: '.yml', + parser: YAML +}) +``` + ### i18n.init() When used as middleware in frameworks like express to setup the current environment for each loop. In contrast to configure the `i18n.init()` should be called within each request-response-cycle. diff --git a/i18n.js b/i18n.js index 16986ff..fea61a4 100644 --- a/i18n.js +++ b/i18n.js @@ -47,6 +47,7 @@ const i18n = function I18n(_OPTS = false) { tags: ['{{', '}}'], disable: false } + let mustacheRegex const pathsep = path.sep // ---> means win support will be available in node 0.8.x and above let autoReload @@ -70,6 +71,7 @@ const i18n = function I18n(_OPTS = false) { let updateFiles let syncFiles let missingKeyFn + let parser // public exports const i18n = {} @@ -178,6 +180,13 @@ const i18n = function I18n(_OPTS = false) { missingKeyFn = typeof opt.missingKeyFn === 'function' ? opt.missingKeyFn : missingKey + parser = + typeof opt.parser === 'object' && + typeof opt.parser.parse === 'function' && + typeof opt.parser.stringify === 'function' + ? opt.parser + : JSON + // when missing locales we try to guess that from directory opt.locales = opt.staticCatalog ? Object.keys(opt.staticCatalog) @@ -1228,10 +1237,10 @@ const i18n = function I18n(_OPTS = false) { const file = getStorageFilePath(locale) try { logDebug('read ' + file + ' for locale: ' + locale) - localeFile = fs.readFileSync(file) + localeFile = fs.readFileSync(file, 'utf-8') try { // parsing filecontents to locales[locale] - locales[locale] = JSON.parse(localeFile) + locales[locale] = parser.parse(localeFile) } catch (parseError) { logError( 'unable to parse locales from file (maybe ' + @@ -1292,7 +1301,7 @@ const i18n = function I18n(_OPTS = false) { tmp = target + '.tmp' fs.writeFileSync( tmp, - JSON.stringify(locales[locale], null, indent), + parser.stringify(locales[locale], null, indent), 'utf8' ) stats = fs.statSync(tmp) diff --git a/locales/en.yml b/locales/en.yml new file mode 100644 index 0000000..bd1327a --- /dev/null +++ b/locales/en.yml @@ -0,0 +1,106 @@ +Empty: "" +Hello: Hello +"Hello %s, how are you today?": Hello %s, how are you today? +weekend: weekend +"Hello %s, how are you today? How was your %s.": Hello %s, how are you today? How was your %s. +Hi: Hi +Howdy: Howdy +"%s cat": + one: "%s cat" + other: "%s cats" +"%f star": + one: "%f star" + other: "%f stars" +"%d star": + one: "%d star" + other: "%d stars" +"%s star": + one: "%s star" + other: "%s stars" +cat: + one: "%s cat" + other: "%s cats" +cats: + n: + one: "%s cat" + other: "%s cats" +nested: + deep: + plural: + one: plural + other: plurals + path: + sub: nested.path.sub +There is one monkey in the %%s: + one: There is one monkey in the %%s + other: There are %d monkeys in the %%s +tree: tree +There is one monkey in the %s: + one: There is one monkey in the %s + other: There are %d monkeys in the %s +There is one monkey in the tree: + one: There is one monkey in the tree + other: There are %d monkeys in the tree +plurals with intervals in string (no object): "[0] a zero rule|[2,5] two to five (included)|and a catchall rule" +plurals with intervals in _other_ missing _one_: + other: "[0] a zero rule|[2,5] two to five (included)|and a catchall rule" +plurals with intervals as string: + one: The default 'one' rule + other: "[0] a zero rule|[2,5] two to five (included)|and a catchall rule" +plurals with intervals as string (excluded): + one: The default 'one' rule + other: "[0] a zero rule|]2,5[ two to five (excluded)|and a catchall rule" +plurals in any order: + one: The default 'one' rule + other: "[0] a zero rule|and a catchall rule|[2,5] two to five (included)" +plurals to eternity: "[0,] this will last forever|but only gt 0" +plurals from eternity: "[,0] this was born long before|but only lt 0" +Hello %s: Hello %s +"Hello {{name}}": Hello {{name}} +"Hello {{name}}, how was your %s?": Hello {{name}}, how was your %s? +format: + date: MM/DD/YYYY + time: h:mm:ss a +greeting: + formal: Hello + informal: Hi + placeholder: + formal: Hello %s + informal: Hi %s + loud: greeting.placeholder.loud + plurals: + one: The default 'one' rule + other: "[0] a zero rule|[2,5] two to five (included)|and a catchall rule" +another: + nested: + extra: + deep: + example: + one: The default 'one' rule + other: "[0] a zero rule|[2,5] two to five (included)|and a catchall rule" + lazy: + example: + other: "[0] a zero rule|[2,5] two to five (included)|and a catchall rule" + mustache: + example: "[0] a zero rule for {{me}}|[2,5] two to five (included) for {{me}}|and + a catchall rule for {{me}}" + mustacheprintf: + example: "[0] %s is zero rule for {{me}}|[2,5] %s is between two and five + (included) for {{me}}|and a catchall rule for {{me}} to get my number + %s" +nested.deep.plural: + one: nested.deep.plural + other: 1 +ordered arguments: "%2$s then %1$s" +ordered arguments with numbers: "%2$d then %1$s then %3$.2f" +repeated argument: "%1$s, %1$s, %1$s" +. is first character: Dot is first character +last character is .: last character is Dot +few sentences. with .: few sentences with Dot +"Hello {{{name}}}": Hello {{{name}}} +Standalone | 42 symbol somewhere | in the text | 1| 0: Standalone | 42 symbol somewhere | in the text | 1| 0 +"should ignore\ \n standalone | mixed with\ \n new lines 42 | value - 42": |- + should ignore + standalone | mixed with + new lines 42 | value - 42 +mftest: "In {lang} there {NUM, plural,one{is one for #}other{others for #}}" diff --git a/package-lock.json b/package-lock.json index 94efb41..66b06f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "i18n", - "version": "0.13.4", + "version": "0.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "i18n", - "version": "0.13.4", + "version": "0.14.0", "license": "MIT", "dependencies": { "@messageformat/core": "^3.0.0", @@ -35,6 +35,7 @@ "prettier": "^2.5.1", "should": "^13.2.3", "sinon": "^12.0.1", + "yaml": "^1.10.2", "zombie": "^6.1.4" }, "engines": { diff --git a/package.json b/package.json index 1cbfa01..4295344 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "prettier": "^2.5.1", "should": "^13.2.3", "sinon": "^12.0.1", + "yaml": "^1.10.2", "zombie": "^6.1.4" }, "engines": { diff --git a/test/i18n.configureParser.js b/test/i18n.configureParser.js new file mode 100644 index 0000000..ce98f00 --- /dev/null +++ b/test/i18n.configureParser.js @@ -0,0 +1,25 @@ +const { I18n } = require('..') +const YAML = require('yaml') +require('should') + +describe('configure parser', function () { + context('with YAML parser', function () { + const i18n = new I18n({ + locales: ['en'], + extension: '.yml', + parser: YAML + }) + + it('should parse the locale files with the YAML parser', function () { + i18n.__('Hello').should.equal('Hello') + }) + + it('should write unknown keys to the catalog', function () { + i18n.__('does.not.exist') + + const catalog = i18n.getCatalog() + catalog.should.have.property('en') + catalog.en.should.have.property('does.not.exist', 'does.not.exist') + }) + }) +})