From 20a2241feb41f467040c7d1eaa70f926256ea6aa Mon Sep 17 00:00:00 2001 From: Zachary Dovel Date: Mon, 17 Sep 2018 07:46:03 -0700 Subject: [PATCH] Initial commit --- src/isLocalizationFunctionStart.js | 4 ++ src/parseLocalizationFunction.js | 49 ++++++++++++++----- src/readCharacter.test.js | 78 ++++++++++++++++++++++++++++-- src/readString.test.js | 18 +++++++ 4 files changed, 131 insertions(+), 18 deletions(-) diff --git a/src/isLocalizationFunctionStart.js b/src/isLocalizationFunctionStart.js index 2e2140f..3ef2e73 100644 --- a/src/isLocalizationFunctionStart.js +++ b/src/isLocalizationFunctionStart.js @@ -3,6 +3,10 @@ const translationStartPatterns = [ '__`', `__n(`, '__n`', + '__p(', + '__p`', + `__np(`, + '__np`', ]; const length = translationStartPatterns diff --git a/src/parseLocalizationFunction.js b/src/parseLocalizationFunction.js index 9c6e5c8..6324e6d 100644 --- a/src/parseLocalizationFunction.js +++ b/src/parseLocalizationFunction.js @@ -37,9 +37,21 @@ function continueUntilStackLengthIs(text, state, length) { return state; } +function readStringArgument(text, {index, stack, lineNumber}, name) { + const start = continueToQuoteStart(text, {index, stack, lineNumber}); + const end = continueUntilStackLengthIs(text, {...start}, start.stack.length - 1); + const stringArgument = text.substring(start.index, end.index - 1); + + if (start.index === end.index - 1) { + throw new SyntaxError(`${name} string argument is empty`); + } + + return [end, stringArgument]; +} + /** * Parses the information from a localization function, include the function string, - * the key, the line number. + * the key, the line number. Parses __, __n, __p, __np. * @param {String} text - The text blob * @param {Number} index - The offset on the text * @param {Array} stack The current code stack @@ -50,31 +62,42 @@ function continueUntilStackLengthIs(text, state, length) { */ module.exports = function parseLocalizationFunction(text, {index, stack, lineNumber}) { const functionStart = {index, stack, lineNumber}; + let plural = false; + let particular = false; index += 1; if (text.charAt(index + 1) === 'n') { + plural = true; index += 1; } - if (text.charAt(index + 1) === '(') { + if (text.charAt(index + 1) === 'p') { + particular = true; index += 1; + } + if (text.charAt(index + 1) === '(') { + index += 1; } - const keyStart = continueToQuoteStart(text, {index, stack, lineNumber}); - const keyEnd = continueUntilStackLengthIs(text, {...keyStart}, keyStart.stack.length - 1); + const metadata = {plural, particular}; + let state = {index, stack, lineNumber}; - if (keyStart.index === keyEnd.index - 1) { - throw new SyntaxError('empty localization key'); + if (particular) { + let context; + [state, context] = readStringArgument(text, state, 'context'); + metadata.context = context; } - const functionEnd = (keyEnd.stack[0] === '(') ? - continueUntilStackLengthIs(text, {...keyEnd}, keyEnd.stack.length - 1) : keyEnd; + let key; + [state, key] = readStringArgument(text, state, 'key'); + metadata.key = key; + + const functionEnd = (state.stack[0] === '(') ? + continueUntilStackLengthIs(text, {...state}, state.stack.length - 1) : state; + const fn = text.substring(functionStart.index, functionEnd.index); + metadata.fn = fn; - return { - ...functionEnd, - key: text.substring(keyStart.index, keyEnd.index - 1), - fn: text.substring(functionStart.index, functionEnd.index), - }; + return Object.assign({}, functionEnd, metadata); } diff --git a/src/readCharacter.test.js b/src/readCharacter.test.js index ac43499..dc9dd0d 100644 --- a/src/readCharacter.test.js +++ b/src/readCharacter.test.js @@ -221,6 +221,8 @@ describe('plugins/readCharacter', () => { localization: { key: 'a', fn: '__("a")', + plural: false, + particular: false, } }, { index: 8, @@ -229,6 +231,33 @@ describe('plugins/readCharacter', () => { }]); }); + it('parses basic translation function with context', () => { + let state = {index: 0, stack: [], lineNumber: 0} + const text = '__p("a", "b")c'; + const actual = []; + + while ((state = readCharacter(text, state)) !== null) { + actual.push(state); + } + + expect(actual).toEqual([{ + index: 13, + stack: [], + lineNumber: 0, + localization: { + context: 'a', + key: 'b', + fn: '__p("a", "b")', + plural: false, + particular: true, + } + }, { + index: 14, + stack: [], + lineNumber: 0, + }]); + }); + it('parses basic plural translation function', () => { let state = {index: 0, stack: [], lineNumber: 0} const text = '__n("%d cat", "%d cats", 1)b'; @@ -245,6 +274,8 @@ describe('plugins/readCharacter', () => { localization: { key: '%d cat', fn: '__n("%d cat", "%d cats", 1)', + plural: true, + particular: false, } }, { index: 28, @@ -253,6 +284,33 @@ describe('plugins/readCharacter', () => { }]); }); + it('parses basic plural translation function with context', () => { + let state = {index: 0, stack: [], lineNumber: 0} + const text = '__np("a", "%d cat", "%d cats", 1)b'; + const actual = []; + + while ((state = readCharacter(text, state)) !== null) { + actual.push(state); + } + + expect(actual).toEqual([{ + index: 33, + stack: [], + lineNumber: 0, + localization: { + key: '%d cat', + context: 'a', + fn: '__np("a", "%d cat", "%d cats", 1)', + plural: true, + particular: true, + } + }, { + index: 34, + stack: [], + lineNumber: 0, + }]); + }); + describe('polymer-style template strings', () => { it('parses basic translation function in [[]] interpolation string', () => { let state = {index: 0, stack: [], lineNumber: 0} @@ -285,7 +343,9 @@ describe('plugins/readCharacter', () => { lineNumber: 0, localization: { "key": "a", - "fn": "__(\"a\")" + "fn": "__(\"a\")", + particular: false, + plural: false, } }, { index: 12, @@ -390,7 +450,9 @@ describe('plugins/readCharacter', () => { lineNumber: 0, localization: { "key": "a", - "fn": "__(\"a\")" + "fn": "__(\"a\")", + particular: false, + plural: false, } }, { index: 12, @@ -495,7 +557,9 @@ describe('plugins/readCharacter', () => { lineNumber: 0, localization: { "key": "a", - "fn": "__(\"a\")" + "fn": "__(\"a\")", + particular: false, + plural: false } }, { index: 11, @@ -579,7 +643,9 @@ describe('plugins/readCharacter', () => { lineNumber: 0, localization: { "key": "a", - "fn": "__(\"a\")" + "fn": "__(\"a\")", + plural: false, + particular: false, } }, { index: 13, @@ -664,6 +730,8 @@ describe('plugins/readCharacter', () => { localization: { key: 'a', fn: '__`a`', + particular: false, + plural: false, } }, { index: 6, @@ -764,7 +832,7 @@ describe('plugins/readCharacter', () => { while ((state = readCharacter(text, state)) !== null) { actual.push(state); } - }).toThrow(new SyntaxError('empty localization key')); + }).toThrow(new SyntaxError('key string argument is empty')); expect(actual).toEqual([]); diff --git a/src/readString.test.js b/src/readString.test.js index bf98530..4b5cf26 100644 --- a/src/readString.test.js +++ b/src/readString.test.js @@ -19,6 +19,16 @@ describe('readString', () => { }); }); + describe('singular with context', () => { + it('create key and value when contains translation', () => { + expect(readString(`a __p('b', 'c') d`)).toEqual({c: {fn: `__p('b', 'c')`, lineNumber: 0, index: 15}}); + }); + + it('adds nothing when no translation', () => { + expect(readString(`a c`)).toEqual({}); + }); + }); + describe('plural', () => { it('create key and value when contains plural translation', () => { expect(readString("a __n('%d cat', '%d cats', 1) c")).toEqual({ @@ -26,4 +36,12 @@ describe('readString', () => { }); }); }); + + describe('plural with context', () => { + it('create key and value when contains plural translation', () => { + expect(readString("a __np('a', '%d cat', '%d cats', 1) c")).toEqual({ + '%d cat': {fn: "__np('a', '%d cat', '%d cats', 1)", lineNumber: 0, index: 35} + }); + }); + }); });