From a3a35d76fe7b2e99570695914835e8c5a3abce36 Mon Sep 17 00:00:00 2001 From: Logan Rasmussen Date: Thu, 10 Sep 2026 18:02:21 -0600 Subject: [PATCH 1/4] fix: remove string eval from gettext --- src/utilities/Intl.jsx | 2 +- src/utilities/__tests__/Intl-test.tsx | 50 ++- src/utilities/__tests__/gettext-test.js | 166 +++++++++ src/utilities/gettext.js | 471 ++++++++++++++++++++++++ vite.config.ts | 1 + 5 files changed, 688 insertions(+), 2 deletions(-) create mode 100644 src/utilities/__tests__/gettext-test.js create mode 100644 src/utilities/gettext.js diff --git a/src/utilities/Intl.jsx b/src/utilities/Intl.jsx index f7d095720c..214bb5d48b 100644 --- a/src/utilities/Intl.jsx +++ b/src/utilities/Intl.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types' import trimChars from 'lodash/fp/trimChars' -import makei18n from 'gettext.js' +import makei18n from 'src/utilities/gettext' export const i18n = new makei18n() diff --git a/src/utilities/__tests__/Intl-test.tsx b/src/utilities/__tests__/Intl-test.tsx index c512730e4b..6b9c5a98e2 100644 --- a/src/utilities/__tests__/Intl-test.tsx +++ b/src/utilities/__tests__/Intl-test.tsx @@ -1,7 +1,9 @@ import React from 'react' import { render, screen } from 'src/utilities/testingLibrary' -import { __, _np, B } from 'src/utilities/Intl' +import { __, _n, _np, B, loadJSON, setLocale } from 'src/utilities/Intl' +import es from 'src/const/language/es.json' +import frCa from 'src/const/language/frCa.json' describe('Intl', () => { it('__ should produce text', () => { @@ -41,4 +43,50 @@ describe('Intl', () => { expect(boldComponents).toHaveLength(2) }) + + describe('Plural translations with Content Security Policy (no unsafe-eval)', () => { + let originalFunction: typeof globalThis.Function + + beforeEach(() => { + originalFunction = globalThis.Function + }) + + afterEach(() => { + globalThis.Function = originalFunction + }) + + it('translates Spanish plurals without violating CSP unsafe-eval', () => { + loadJSON(structuredClone(es)) + setLocale('es') + + // Disallow any dynamic code evaluation / new Function to simulate strict CSP + globalThis.Function = function () { + throw new EvalError( + "Evaluating a string as JavaScript violates the following Content Security Policy directive because 'unsafe-eval' is not an allowed source of script", + ) + } as unknown as typeof Function + + // Singular (1) + expect(_n('%1 search result', '%1 search results', 1, 1)).toBe('1 resultado de búsqueda') + // Plural (2) + expect(_n('%1 search result', '%1 search results', 2, 2)).toBe('2 Resultados de la búsqueda') + }) + + it('translates French Canadian plurals without violating CSP unsafe-eval', () => { + loadJSON(structuredClone(frCa)) + setLocale('fr-ca') + + // Disallow any dynamic code evaluation / new Function to simulate strict CSP + globalThis.Function = function () { + throw new EvalError( + "Evaluating a string as JavaScript violates the following Content Security Policy directive because 'unsafe-eval' is not an allowed source of script", + ) + } as unknown as typeof Function + + // Singular (1) + expect(_n('%1 search result', '%1 search results', 1, 1)).toBe('1 résultat de recherche') + // Plural (2) + expect(_n('%1 search result', '%1 search results', 2, 2)).toBe('2 résultats de recherche') + }) + }) }) diff --git a/src/utilities/__tests__/gettext-test.js b/src/utilities/__tests__/gettext-test.js new file mode 100644 index 0000000000..6d443b3b3d --- /dev/null +++ b/src/utilities/__tests__/gettext-test.js @@ -0,0 +1,166 @@ +import i18n from 'src/utilities/gettext' + +describe('gettext safe implementation', () => { + let originalFunction + + beforeEach(() => { + originalFunction = globalThis.Function + }) + + afterEach(() => { + globalThis.Function = originalFunction + }) + + describe('CSP compliance', () => { + it('evaluates plural forms without invoking new Function or eval', () => { + globalThis.Function = function () { + throw new EvalError('CSP unsafe-eval violation') + } + + const instance = i18n() + instance.loadJSON({ + '': { + language: 'es', + 'plural-forms': 'nplurals=2; plural=(n != 1);', + }, + '%1 apple': ['%1 manzana', '%1 manzanas'], + }) + instance.setLocale('es') + + expect(instance.ngettext('%1 apple', '%1 apples', 1, 1)).toBe('1 manzana') + expect(instance.ngettext('%1 apple', '%1 apples', 5, 5)).toBe('5 manzanas') + }) + }) + + describe('Plural expression evaluator rules', () => { + const simulateCsp = () => { + globalThis.Function = function () { + throw new EvalError('CSP unsafe-eval violation') + } + } + + it('handles nplurals=1; plural=0; (e.g., Japanese, Chinese)', () => { + simulateCsp() + const instance = i18n() + instance.loadJSON({ + '': { + language: 'ja', + 'plural-forms': 'nplurals=1; plural=0;', + }, + '%1 item': ['%1 個のアイテム'], + }) + instance.setLocale('ja') + + expect(instance.ngettext('%1 item', '%1 items', 1, 1)).toBe('1 個のアイテム') + expect(instance.ngettext('%1 item', '%1 items', 0, 0)).toBe('0 個のアイテム') + expect(instance.ngettext('%1 item', '%1 items', 10, 10)).toBe('10 個のアイテム') + }) + + it('handles nplurals=2; plural=(n > 1); (French standard)', () => { + simulateCsp() + const instance = i18n() + instance.loadJSON({ + '': { + language: 'fr', + 'plural-forms': 'nplurals=2; plural=(n > 1);', + }, + '%1 file': ['%1 fichier', '%1 fichiers'], + }) + instance.setLocale('fr') + + // 0 is singular in French + expect(instance.ngettext('%1 file', '%1 files', 0, 0)).toBe('0 fichier') + // 1 is singular + expect(instance.ngettext('%1 file', '%1 files', 1, 1)).toBe('1 fichier') + // 2 is plural + expect(instance.ngettext('%1 file', '%1 files', 2, 2)).toBe('2 fichiers') + }) + + it('handles complex ternary and modulo (Slavic/Russian plural rule)', () => { + simulateCsp() + const instance = i18n() + instance.loadJSON({ + '': { + language: 'ru', + 'plural-forms': + 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);', + }, + '%1 book': ['%1 книга', '%1 книги', '%1 книг'], + }) + instance.setLocale('ru') + + expect(instance.ngettext('%1 book', '%1 books', 1, 1)).toBe('1 книга') + expect(instance.ngettext('%1 book', '%1 books', 21, 21)).toBe('21 книга') + expect(instance.ngettext('%1 book', '%1 books', 2, 2)).toBe('2 книги') + expect(instance.ngettext('%1 book', '%1 books', 4, 4)).toBe('4 книги') + expect(instance.ngettext('%1 book', '%1 books', 5, 5)).toBe('5 книг') + expect(instance.ngettext('%1 book', '%1 books', 11, 11)).toBe('11 книг') + }) + + it('handles complex ternary with multiple branches (Arabic plural rule)', () => { + simulateCsp() + const instance = i18n() + instance.loadJSON({ + '': { + language: 'ar', + 'plural-forms': + 'nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5);', + }, + '%1 book': ['0', '1', '2', '3-10', '11+', 'other'], + }) + instance.setLocale('ar') + + expect(instance.ngettext('%1 book', '%1 books', 0)).toBe('0') + expect(instance.ngettext('%1 book', '%1 books', 1)).toBe('1') + expect(instance.ngettext('%1 book', '%1 books', 2)).toBe('2') + expect(instance.ngettext('%1 book', '%1 books', 5)).toBe('3-10') + expect(instance.ngettext('%1 book', '%1 books', 15)).toBe('11+') + expect(instance.ngettext('%1 book', '%1 books', 102)).toBe('other') + }) + }) + + describe('General gettext features', () => { + it('translates singular strings with __ and gettext', () => { + const instance = i18n() + instance.loadJSON({ + '': { + language: 'es', + 'plural-forms': 'nplurals=2; plural=(n != 1);', + }, + Hello: 'Hola', + }) + instance.setLocale('es') + + expect(instance.gettext('Hello')).toBe('Hola') + expect(instance.__('Hello')).toBe('Hola') + }) + + it('translates contextual strings with _p and pgettext', () => { + const instance = i18n() + instance.loadJSON({ + '': { + language: 'fr', + 'plural-forms': 'nplurals=2; plural=(n!=1);', + }, + 'menu\u0004File': 'Fichier', + }) + instance.setLocale('fr') + + expect(instance.pgettext('menu', 'File')).toBe('Fichier') + expect(instance._p('menu', 'File')).toBe('Fichier') + }) + + it('interpolates placeholders with %1, %2', () => { + const instance = i18n() + expect(instance.strfmt('%1 has %2 items', 'Logan', 3)).toBe('Logan has 3 items') + }) + + it('falls back to msgid when translation is missing', () => { + const instance = i18n() + instance.setLocale('fr') + expect(instance.gettext('Unknown')).toBe('Unknown') + expect(instance.ngettext('%1 result', '%1 results', 1, 1)).toBe('1 result') + expect(instance.ngettext('%1 result', '%1 results', 5, 5)).toBe('5 results') + }) + }) +}) diff --git a/src/utilities/gettext.js b/src/utilities/gettext.js new file mode 100644 index 0000000000..acca20eeff --- /dev/null +++ b/src/utilities/gettext.js @@ -0,0 +1,471 @@ +/*! + * gettext.js - CSP-compliant implementation + * Evaluates GNU gettext plural forms safely without eval() or new Function(). + */ + +/** + * Parses and safely evaluates GNU gettext plural form expressions without dynamic code execution. + * Grammar supports: + * - Variables: 'n' + * - Integer numbers + * - Arithmetic operators: +, -, *, /, % (with integer division) + * - Comparison operators: ==, !=, <, <=, >, >= + * - Logical operators: &&, ||, ! + * - Conditional (ternary): ? : + * - Grouping: ( ) + */ +function evaluatePluralExpression(expr, n) { + let pos = 0 + const str = expr.replace(/\s+/g, '') + + function parseTernary() { + const condition = parseOr() + if (str[pos] === '?') { + pos++ // skip '?' + const trueBranch = parseTernary() + if (str[pos] !== ':') { + throw new Error(`Expected ':' at position ${pos} in plural expression: ${expr}`) + } + pos++ // skip ':' + const falseBranch = parseTernary() + return condition ? trueBranch : falseBranch + } + return condition + } + + function parseOr() { + let val = parseAnd() + while (str.slice(pos, pos + 2) === '||') { + pos += 2 + const right = parseAnd() + val = val || right + } + return val + } + + function parseAnd() { + let val = parseEquality() + while (str.slice(pos, pos + 2) === '&&') { + pos += 2 + const right = parseEquality() + val = val && right + } + return val + } + + function parseEquality() { + let val = parseRelational() + while (str.slice(pos, pos + 2) === '==' || str.slice(pos, pos + 2) === '!=') { + const op = str.slice(pos, pos + 2) + pos += 2 + const right = parseRelational() + val = op === '==' ? Number(val) === Number(right) : Number(val) !== Number(right) + } + return val + } + + function parseRelational() { + let val = parseAddSub() + while ( + str.slice(pos, pos + 2) === '<=' || + str.slice(pos, pos + 2) === '>=' || + str[pos] === '<' || + str[pos] === '>' + ) { + if (str.slice(pos, pos + 2) === '<=') { + pos += 2 + val = Number(val) <= Number(parseAddSub()) + } else if (str.slice(pos, pos + 2) === '>=') { + pos += 2 + val = Number(val) >= Number(parseAddSub()) + } else if (str[pos] === '<') { + pos++ + val = Number(val) < Number(parseAddSub()) + } else if (str[pos] === '>') { + pos++ + val = Number(val) > Number(parseAddSub()) + } + } + return val + } + + function parseAddSub() { + let val = parseMulDivMod() + while (str[pos] === '+' || str[pos] === '-') { + const op = str[pos] + pos++ + const right = parseMulDivMod() + val = op === '+' ? Number(val) + Number(right) : Number(val) - Number(right) + } + return val + } + + function parseMulDivMod() { + let val = parseUnary() + while (str[pos] === '*' || str[pos] === '/' || str[pos] === '%') { + const op = str[pos] + pos++ + const right = parseUnary() + if (op === '*') { + val = Number(val) * Number(right) + } else if (op === '/') { + val = Number(right) !== 0 ? Math.floor(Number(val) / Number(right)) : 0 + } else if (op === '%') { + val = Number(right) !== 0 ? Number(val) % Number(right) : 0 + } + } + return val + } + + function parseUnary() { + if (str[pos] === '!') { + pos++ + return !parseUnary() + } + if (str[pos] === '+') { + pos++ + return +parseUnary() + } + if (str[pos] === '-') { + pos++ + return -parseUnary() + } + return parsePrimary() + } + + function parsePrimary() { + if (str[pos] === '(') { + pos++ + const val = parseTernary() + if (str[pos] !== ')') { + throw new Error(`Expected ')' at position ${pos} in plural expression: ${expr}`) + } + pos++ + return val + } + if (str[pos] === 'n') { + pos++ + return n + } + const numMatch = str.slice(pos).match(/^[0-9]+/) + if (numMatch) { + pos += numMatch[0].length + return parseInt(numMatch[0], 10) + } + throw new Error(`Unexpected token at position ${pos} in plural expression: ${expr}`) + } + + return parseTernary() +} + +const compilePluralForm = function (pluralForm) { + const npluralsMatch = pluralForm.match(/nplurals\s*=\s*([0-9]+)/) + const pluralMatch = pluralForm.match(/plural\s*=\s*([^;]+)/) + + if (!npluralsMatch || !pluralMatch) { + throw new Error(`The plural form "${pluralForm}" is not valid`) + } + + const nplurals = parseInt(npluralsMatch[1], 10) + const expr = pluralMatch[1].trim() + + return function (n) { + const rawResult = evaluatePluralExpression(expr, typeof n === 'number' ? n : Number(n) || 0) + let plural = 0 + if (rawResult === true) { + plural = 1 + } else if (rawResult) { + plural = Number(rawResult) + } + + return { + nplurals, + plural, + } + } +} + +const i18n = function (options) { + const opts = options || {} + if (this) { + this.__version = '2.0.0' + } + + const defaults = { + domain: 'messages', + locale: + (typeof document !== 'undefined' ? document.documentElement.getAttribute('lang') : false) || + 'en', + plural_func: function (n) { + return { nplurals: 2, plural: n !== 1 ? 1 : 0 } + }, + ctxt_delimiter: String.fromCharCode(4), // \u0004 + } + + const _ = { + isObject: function (obj) { + const type = typeof obj + return type === 'function' || (type === 'object' && !!obj) + }, + } + + const _plural_funcs = {} + let _locale = opts.locale || defaults.locale + let _domain = opts.domain || defaults.domain + const _dictionary = {} + const _plural_forms = {} + const _ctxt_delimiter = opts.ctxt_delimiter || defaults.ctxt_delimiter + + if (opts.messages) { + _dictionary[_domain] = {} + _dictionary[_domain][_locale] = opts.messages + } + + if (opts.plural_forms) { + _plural_forms[_locale] = opts.plural_forms + } + + const strfmt = function (fmt) { + const args = arguments + return fmt + .replace(/%%/g, '%% ') + .replace(/%(\d+)/g, function (_str, p1) { + return args[p1] + }) + .replace(/%% /g, '%') + } + + const removeContext = function (str) { + if (str.indexOf(_ctxt_delimiter) !== -1) { + const parts = str.split(_ctxt_delimiter) + return parts[1] + } + return str + } + + const expand_locale = function (locale) { + const locales = [locale] + let curLocale = locale + let i = curLocale.lastIndexOf('-') + while (i > 0) { + curLocale = curLocale.slice(0, i) + locales.push(curLocale) + i = curLocale.lastIndexOf('-') + } + return locales + } + + const normalizeLocale = function (locale) { + let normalized = locale.replace('_', '-') + const i = normalized.search(/[.@]/) + if (i !== -1) { + normalized = normalized.slice(0, i) + } + return normalized + } + + const getPluralFunc = function (plural_form) { + const pf_re = new RegExp( + '^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_\\(\\)])+', + ) + const match = plural_form.match(pf_re) + + if (!match || match[0] !== plural_form) { + throw new Error(strfmt('The plural form "%1" is not valid', plural_form)) + } + + return compilePluralForm(plural_form) + } + + const t = function (messages, n, tOptions /* , extra */) { + if (!tOptions.plural_form) { + return strfmt.apply( + this, + [removeContext(messages[0])].concat(Array.prototype.slice.call(arguments, 3)), + ) + } + + let plural + if (tOptions.plural_func) { + plural = tOptions.plural_func(n) + } else if (!_plural_funcs[_locale]) { + _plural_funcs[_locale] = getPluralFunc(_plural_forms[_locale]) + plural = _plural_funcs[_locale](n) + } else { + plural = _plural_funcs[_locale](n) + } + + if ( + typeof plural.plural === 'undefined' || + plural.plural > plural.nplurals || + messages.length <= plural.plural + ) { + plural.plural = 0 + } + + return strfmt.apply( + this, + [removeContext(messages[plural.plural])].concat(Array.prototype.slice.call(arguments, 3)), + ) + } + + return { + strfmt, + expand_locale, + + __: function () { + return this.gettext.apply(this, arguments) + }, + _n: function () { + return this.ngettext.apply(this, arguments) + }, + _p: function () { + return this.pgettext.apply(this, arguments) + }, + + setMessages: function (domain, locale, messages, plural_forms) { + if (!domain || !locale || !messages) { + throw new Error('You must provide a domain, a locale and messages') + } + + if (typeof domain !== 'string' || typeof locale !== 'string' || !_.isObject(messages)) { + throw new Error('Invalid arguments') + } + + const normalizedLocale = normalizeLocale(locale) + + if (plural_forms) { + _plural_forms[normalizedLocale] = plural_forms + } + + if (!_dictionary[domain]) { + _dictionary[domain] = {} + } + + _dictionary[domain][normalizedLocale] = messages + + return this + }, + + loadJSON: function (jsonData, domain) { + const data = + typeof jsonData === 'object' && jsonData !== null ? jsonData : JSON.parse(jsonData) + + if (!data[''] || !data['']['language'] || !data['']['plural-forms']) { + throw new Error( + 'Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information', + ) + } + + const headers = data[''] + delete data[''] + + return this.setMessages( + domain || defaults.domain, + headers['language'], + data, + headers['plural-forms'], + ) + }, + + setLocale: function (locale) { + _locale = normalizeLocale(locale) + return this + }, + + getLocale: function () { + return _locale + }, + + textdomain: function (domain) { + if (!domain) { + return _domain + } + _domain = domain + return this + }, + + gettext: function (msgid /* , extra */) { + return this.dcnpgettext.apply( + this, + [undefined, undefined, msgid, undefined, undefined].concat( + Array.prototype.slice.call(arguments, 1), + ), + ) + }, + + ngettext: function (msgid, msgid_plural, n /* , extra */) { + return this.dcnpgettext.apply( + this, + [undefined, undefined, msgid, msgid_plural, n].concat( + Array.prototype.slice.call(arguments, 3), + ), + ) + }, + + pgettext: function (msgctxt, msgid /* , extra */) { + return this.dcnpgettext.apply( + this, + [undefined, msgctxt, msgid, undefined, undefined].concat( + Array.prototype.slice.call(arguments, 2), + ), + ) + }, + + dcnpgettext: function (domain, msgctxt, msgid, msgid_plural, n /* , extra */) { + const currentDomain = domain || _domain + + if (typeof msgid !== 'string') { + throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string', msgid)) + } + + let translation + const options = { plural_form: false } + const key = msgctxt ? msgctxt + _ctxt_delimiter + msgid : msgid + let exist + let foundLocale + const locales = expand_locale(_locale) + + for (const localeCandidate of locales) { + exist = + _dictionary[currentDomain] && + _dictionary[currentDomain][localeCandidate] && + _dictionary[currentDomain][localeCandidate][key] + + if (msgid_plural) { + exist = exist && typeof _dictionary[currentDomain][localeCandidate][key] !== 'string' + } else { + exist = exist && typeof _dictionary[currentDomain][localeCandidate][key] === 'string' + } + if (exist) { + foundLocale = localeCandidate + break + } + } + + if (!exist) { + translation = msgid + options.plural_func = defaults.plural_func + } else { + translation = _dictionary[currentDomain][foundLocale][key] + } + + if (!msgid_plural) { + return t.apply( + this, + [[translation], n, options].concat(Array.prototype.slice.call(arguments, 5)), + ) + } + + options.plural_form = true + return t.apply( + this, + [exist ? translation : [msgid, msgid_plural], n, options].concat( + Array.prototype.slice.call(arguments, 5), + ), + ) + }, + } +} + +export default i18n diff --git a/vite.config.ts b/vite.config.ts index f5dc929983..083cbb02d8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -72,6 +72,7 @@ export default defineConfig({ }, resolve: { alias: { + 'gettext.js': path.resolve(__dirname, './src/utilities/gettext.js'), src: path.resolve(__dirname, './src'), utils: path.join(__dirname, 'src/utils'), }, From e9e1faf6eaf77a31f8fac1f26cd6211cae13d35e Mon Sep 17 00:00:00 2001 From: Logan Rasmussen Date: Fri, 11 Sep 2026 10:47:18 -0600 Subject: [PATCH 2/4] fix(i18n): patch gettext.js with CSP-compliant plural evaluator --- package-lock.json | 287 ++++++++++++- package.json | 2 + patches/gettext.js+2.0.3.patch | 516 ++++++++++++++++++++++++ src/utilities/Intl.jsx | 2 +- src/utilities/__tests__/gettext-test.js | 2 +- src/utilities/gettext.js | 471 --------------------- vite.config.ts | 1 - 7 files changed, 794 insertions(+), 487 deletions(-) create mode 100644 patches/gettext.js+2.0.3.patch delete mode 100644 src/utilities/gettext.js diff --git a/package-lock.json b/package-lock.json index 039ef60ae8..bca7c5f3d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,6 +64,7 @@ "jsdom": "^24.1.1", "lint-staged": "^15.2.8", "markdown-eslint-parser": "^1.2.1", + "patch-package": "^8.0.1", "prettier": "3.7.4", "rollup": "^4.59.0", "semantic-release": "^25.0.3", @@ -1631,19 +1632,6 @@ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@kyper/text": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@kyper/text/-/text-3.0.0.tgz", - "integrity": "sha512-Z1arAeYl+8deTQyFguImJEq5jUGvW6dkyCP52LHn76uz3PTaZTAn3dfXw7W1Zx5O55V4BYPdL+YOO2IHIajZLQ==", - "peer": true, - "peerDependencies": { - "@kyper/tokenprovider": "^4.0.0", - "@mxenabled/cssinjs": "^0.6.0", - "prop-types": "^15.7.2", - "react": "^16.14.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/@kyper/tokenprovider": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@kyper/tokenprovider/-/tokenprovider-4.0.1.tgz", @@ -5237,6 +5225,13 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==" }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -5980,6 +5975,22 @@ "node": ">= 16" } }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/clean-stack": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", @@ -8141,6 +8152,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/findup-sync": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", @@ -9409,6 +9430,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -9745,6 +9782,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -10002,6 +10052,26 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -10037,6 +10107,16 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -10061,6 +10141,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kolorist": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", @@ -13808,6 +13898,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -14153,6 +14260,150 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, + "node_modules/patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/patch-package/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/patch-package/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/patch-package/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/patch-package/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/patch-package/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/patch-package/node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -15825,6 +16076,16 @@ "node": ">=8" } }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", diff --git a/package.json b/package.json index b88e7d1c28..eaf6e16aef 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "lint": "eslint . --ext ts,tsx,js,jsx,md --report-unused-disable-directives --max-warnings 14", "preview": "vite preview", "prepare": "husky", + "postinstall": "patch-package", "test": "vitest run", "test:coverage": "vitest run --coverage", "test:watch": "vitest", @@ -97,6 +98,7 @@ "jsdom": "^24.1.1", "lint-staged": "^15.2.8", "markdown-eslint-parser": "^1.2.1", + "patch-package": "^8.0.1", "prettier": "3.7.4", "rollup": "^4.59.0", "semantic-release": "^25.0.3", diff --git a/patches/gettext.js+2.0.3.patch b/patches/gettext.js+2.0.3.patch new file mode 100644 index 0000000000..373872eaa4 --- /dev/null +++ b/patches/gettext.js+2.0.3.patch @@ -0,0 +1,516 @@ +diff --git a/node_modules/gettext.js/dist/gettext.cjs.js b/node_modules/gettext.js/dist/gettext.cjs.js +index 85c83de..ff22c27 100644 +--- a/node_modules/gettext.js/dist/gettext.cjs.js ++++ b/node_modules/gettext.js/dist/gettext.cjs.js +@@ -88,6 +88,138 @@ var i18n = function (options) { + return locale; + }; + ++ var parsePlural = function (expr) { ++ var pos = 0; ++ var str = expr.replace(/\s+/g, ''); ++ ++ function parseTernary() { ++ var cond = parseOr(); ++ if (str[pos] === '?') { ++ pos++; ++ var then = parseTernary(); ++ if (str[pos] === ':') pos++; ++ var el = parseTernary(); ++ return { type: 'ter', cond: cond, then: then, else: el }; ++ } ++ return cond; ++ } ++ ++ function parseOr() { ++ var node = parseAnd(); ++ while (str.slice(pos, pos + 2) === '||') { ++ pos += 2; ++ node = { type: 'bin', op: '||', left: node, right: parseAnd() }; ++ } ++ return node; ++ } ++ ++ function parseAnd() { ++ var node = parseEquality(); ++ while (str.slice(pos, pos + 2) === '&&') { ++ pos += 2; ++ node = { type: 'bin', op: '&&', left: node, right: parseEquality() }; ++ } ++ return node; ++ } ++ ++ function parseEquality() { ++ var node = parseRelational(); ++ while (str.slice(pos, pos + 2) === '==' || str.slice(pos, pos + 2) === '!=') { ++ var op = str.slice(pos, pos + 2); ++ pos += 2; ++ node = { type: 'bin', op: op, left: node, right: parseRelational() }; ++ } ++ return node; ++ } ++ ++ function parseRelational() { ++ var node = parseAddSub(); ++ while (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=' || str[pos] === '<' || str[pos] === '>') { ++ var op = (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=') ? str.slice(pos, pos + 2) : str[pos]; ++ pos += op.length; ++ node = { type: 'bin', op: op, left: node, right: parseAddSub() }; ++ } ++ return node; ++ } ++ ++ function parseAddSub() { ++ var node = parseMulDivMod(); ++ while (str[pos] === '+' || str[pos] === '-') { ++ var op = str[pos]; ++ pos++; ++ node = { type: 'bin', op: op, left: node, right: parseMulDivMod() }; ++ } ++ return node; ++ } ++ ++ function parseMulDivMod() { ++ var node = parseUnary(); ++ while (str[pos] === '*' || str[pos] === '/' || str[pos] === '%') { ++ var op = str[pos]; ++ pos++; ++ node = { type: 'bin', op: op, left: node, right: parseUnary() }; ++ } ++ return node; ++ } ++ ++ function parseUnary() { ++ if (str[pos] === '!') { ++ pos++; ++ return { type: 'un', op: op, arg: parseUnary() }; ++ } ++ return parsePrimary(); ++ } ++ ++ function parsePrimary() { ++ if (str[pos] === '(') { ++ pos++; ++ var node = parseTernary(); ++ if (str[pos] === ')') pos++; ++ return node; ++ } ++ if (str[pos] === 'n') { ++ pos++; ++ return { type: 'n' }; ++ } ++ var m = str.slice(pos).match(/^[0-9]+/); ++ if (m) { ++ pos += m[0].length; ++ return { type: 'num', value: parseInt(m[0], 10) }; ++ } ++ return { type: 'num', value: 0 }; ++ } ++ ++ return parseTernary(); ++ }; ++ ++ var evalPluralNode = function (node, n) { ++ switch (node.type) { ++ case 'num': return node.value; ++ case 'n': return n; ++ case 'un': return node.op === '!' ? !evalPluralNode(node.arg, n) : -evalPluralNode(node.arg, n); ++ case 'bin': { ++ var l = evalPluralNode(node.left, n); ++ if (node.op === '&&') return l && evalPluralNode(node.right, n); ++ if (node.op === '||') return l || evalPluralNode(node.right, n); ++ var r = evalPluralNode(node.right, n); ++ if (node.op === '==') return Number(l) === Number(r); ++ if (node.op === '!=') return Number(l) !== Number(r); ++ if (node.op === '<=') return Number(l) <= Number(r); ++ if (node.op === '>=') return Number(l) >= Number(r); ++ if (node.op === '<') return Number(l) < Number(r); ++ if (node.op === '>') return Number(l) > Number(r); ++ if (node.op === '+') return Number(l) + Number(r); ++ if (node.op === '-') return Number(l) - Number(r); ++ if (node.op === '*') return Number(l) * Number(r); ++ if (node.op === '/') return Number(r) !== 0 ? Math.floor(Number(l) / Number(r)) : 0; ++ if (node.op === '%') return Number(r) !== 0 ? Number(l) % Number(r) : 0; ++ return 0; ++ } ++ case 'ter': return evalPluralNode(node.cond, n) ? evalPluralNode(node.then, n) : evalPluralNode(node.else, n); ++ } ++ return 0; ++ }; ++ + var getPluralFunc = function (plural_form) { + // Plural form string regexp + // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js +@@ -99,13 +231,16 @@ var i18n = function (options) { + if (!match || match[0] !== plural_form) + throw new Error(strfmt('The plural form "%1" is not valid', plural_form)); + +- console.log('>>> Plural form:', plural_form); ++ var npluralsMatch = plural_form.match(/nplurals\s*=\s*([0-9]+)/); ++ var pluralMatch = plural_form.match(/plural\s*=\s*([^;]+)/); ++ var nplurals = npluralsMatch ? parseInt(npluralsMatch[1], 10) : 2; ++ var expr = pluralMatch ? pluralMatch[1] : 'n != 1'; ++ var ast = parsePlural(expr); + +- // Careful here, this is a hidden eval() equivalent.. +- // Risk should be reasonable though since we test the plural_form through regex before +- // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js +- // TODO: should test if https://github.com/soney/jsep present and use it if so +- return new Function("n", 'var plural, nplurals; '+ plural_form +' return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };'); ++ return function (n) { ++ var res = evalPluralNode(ast, typeof n === 'number' ? n : Number(n) || 0); ++ return { nplurals: nplurals, plural: (res === true ? 1 : (res ? Number(res) : 0)) }; ++ }; + }; + + // Proper translation function that handle plurals and directives +diff --git a/node_modules/gettext.js/dist/gettext.cjs.min.js b/node_modules/gettext.js/dist/gettext.cjs.min.js +index 9f5f2e6..63534e1 100644 +--- a/node_modules/gettext.js/dist/gettext.cjs.min.js ++++ b/node_modules/gettext.js/dist/gettext.cjs.min.js +@@ -1,2 +1 @@ +-"use strict"; +-/*! gettext.js - Guillaume Potier - MIT Licensed */var i18n=function(t){t=t||{},this&&(this.__version="2.0.0");var r={domain:"messages",locale:"undefined"!=typeof document&&document.documentElement.getAttribute("lang")||"en",plural_func:function(t){return{nplurals:2,plural:1!=t?1:0}},ctxt_delimiter:String.fromCharCode(4)},e=function(t){var r=typeof t;return"function"===r||"object"===r&&!!t},n={},l=t.locale||r.locale,a=t.domain||r.domain,o={},i={},u=t.ctxt_delimiter||r.ctxt_delimiter;t.messages&&(o[a]={},o[a][l]=t.messages),t.plural_forms&&(i[l]=t.plural_forms);var s=function(t){var r=arguments;return t.replace(/%%/g,"%% ").replace(/%(\d+)/g,(function(t,e){return r[e]})).replace(/%% /g,"%")},p=function(t){return-1!==t.indexOf(u)?t.split(u)[1]:t},c=function(t){for(var r=[t],e=t.lastIndexOf("-");e>0;)t=t.slice(0,e),r.push(t),e=t.lastIndexOf("-");return r},f=function(t){var r=(t=t.replace("_","-")).search(/[.@]/);return-1!=r&&(t=t.slice(0,r)),t},d=function(t){var r=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),e=t.match(r);if(!e||e[0]!==t)throw new Error(s('The plural form "%1" is not valid',t));return console.log(">>> Plural form:",t),new Function("n","var plural, nplurals; "+t+" return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };")},g=function(t,r,e){return e.plural_form?(e.plural_func?a=e.plural_func(r):(n[l]||(n[l]=d(i[l])),a=n[l](r)),(void 0===a.plural||a.plural>a.nplurals||t.length<=a.plural)&&(a.plural=0),s.apply(this,[p(t[a.plural])].concat(Array.prototype.slice.call(arguments,3)))):s.apply(this,[p(t[0])].concat(Array.prototype.slice.call(arguments,3)));var a};return{strfmt:s,expand_locale:c,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(t,r,n,l){if(!t||!r||!n)throw new Error("You must provide a domain, a locale and messages");if("string"!=typeof t||"string"!=typeof r||!e(n))throw new Error("Invalid arguments");return r=f(r),l&&(i[r]=l),o[t]||(o[t]={}),o[t][r]=n,this},loadJSON:function(t,n){if(e(t)||(t=JSON.parse(t)),!t[""]||!t[""].language||!t[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var l=t[""];return delete t[""],this.setMessages(n||r.domain,l.language,t,l["plural-forms"])},setLocale:function(t){return l=f(t),this},getLocale:function(){return l},textdomain:function(t){return t?(a=t,this):a},gettext:function(t){return this.dcnpgettext.apply(this,[void 0,void 0,t,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(t,r,e){return this.dcnpgettext.apply(this,[void 0,void 0,t,r,e].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(t,r){return this.dcnpgettext.apply(this,[void 0,t,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(t,e,n,i,s){if(t=t||a,"string"!=typeof n)throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',n));var p,f,d,m={plural_form:!1},h=e?e+u+n:n,y=c(l);for(var v in y)if(d=y[v],f=o[t]&&o[t][d]&&o[t][d][h],f=i?f&&"string"!=typeof o[t][d][h]:f&&"string"==typeof o[t][d][h])break;return f?p=o[t][d][h]:(p=n,m.plural_func=r.plural_func),i?(m.plural_form=!0,g.apply(this,[f?p:[n,i],s,m].concat(Array.prototype.slice.call(arguments,5)))):g.apply(this,[[p],s,m].concat(Array.prototype.slice.call(arguments,5)))}}};module.exports=i18n; +\ No newline at end of file ++"use strict";/*! gettext.js - Guillaume Potier - MIT Licensed */var i18n=function(c){c=c||{},this&&(this.__version="2.0.0");var y={domain:"messages",locale:(typeof document<"u"?document.documentElement.getAttribute("lang"):!1)||"en",plural_func:function(r){return{nplurals:2,plural:r!=1?1:0}},ctxt_delimiter:""},A={isObject:function(r){var e=typeof r;return e==="function"||e==="object"&&!!r},isArray:function(r){return toString.call(r)==="[object Array]"}},d={},s=c.locale||y.locale,b=c.domain||y.domain,p={},_={},x=c.ctxt_delimiter||y.ctxt_delimiter;c.messages&&(p[b]={},p[b][s]=c.messages),c.plural_forms&&(_[s]=c.plural_forms);var N=function(r){var e=arguments;return r.replace(/%%/g,"%% ").replace(/%(\d+)/g,function(t,n){return e[n]}).replace(/%% /g,"%")},O=function(r){if(r.indexOf(x)!==-1){var e=r.split(x);return e[1]}return r},E=function(r){for(var e=[r],t=r.lastIndexOf("-");t>0;)r=r.slice(0,t),e.push(r),t=r.lastIndexOf("-");return e},M=function(r){r=r.replace("_","-");var e=r.search(/[.@]/);return e!=-1&&(r=r.slice(0,e)),r},I=function(r){var e=0,t=r.replace(/\s+/g,"");function n(){var u=v();if(t[e]==="?"){e++;var i=n();t[e]===":"&&e++;var k=n();return{type:"ter",cond:u,then:i,else:k}}return u}function v(){for(var u=o();t.slice(e,e+2)==="||";)e+=2,u={type:"bin",op:"||",left:u,right:o()};return u}function o(){for(var u=h();t.slice(e,e+2)==="&&";)e+=2,u={type:"bin",op:"&&",left:u,right:h()};return u}function h(){for(var u=m();t.slice(e,e+2)==="=="||t.slice(e,e+2)==="!=";){var i=t.slice(e,e+2);e+=2,u={type:"bin",op:i,left:u,right:m()}}return u}function m(){for(var u=a();t.slice(e,e+2)==="<="||t.slice(e,e+2)===">="||t[e]==="<"||t[e]===">";){var i=t.slice(e,e+2)==="<="||t.slice(e,e+2)===">="?t.slice(e,e+2):t[e];e+=i.length,u={type:"bin",op:i,left:u,right:a()}}return u}function a(){for(var u=l();t[e]==="+"||t[e]==="-";){var i=t[e];e++,u={type:"bin",op:i,left:u,right:l()}}return u}function l(){for(var u=g();t[e]==="*"||t[e]==="/"||t[e]==="%";){var i=t[e];e++,u={type:"bin",op:i,left:u,right:g()}}return u}function g(){return t[e]==="!"?(e++,{type:"un",op,arg:g()}):w()}function w(){if(t[e]==="("){e++;var u=n();return t[e]===")"&&e++,u}if(t[e]==="n")return e++,{type:"n"};var i=t.slice(e).match(/^[0-9]+/);return i?(e+=i[0].length,{type:"num",value:parseInt(i[0],10)}):{type:"num",value:0}}return n()},f=function(r,e){switch(r.type){case"num":return r.value;case"n":return e;case"un":return r.op==="!"?!f(r.arg,e):-f(r.arg,e);case"bin":{var t=f(r.left,e);if(r.op==="&&")return t&&f(r.right,e);if(r.op==="||")return t||f(r.right,e);var n=f(r.right,e);return r.op==="=="?Number(t)===Number(n):r.op==="!="?Number(t)!==Number(n):r.op==="<="?Number(t)<=Number(n):r.op===">="?Number(t)>=Number(n):r.op==="<"?Number(t)"?Number(t)>Number(n):r.op==="+"?Number(t)+Number(n):r.op==="-"?Number(t)-Number(n):r.op==="*"?Number(t)*Number(n):r.op==="/"?Number(n)!==0?Math.floor(Number(t)/Number(n)):0:r.op==="%"&&Number(n)!==0?Number(t)%Number(n):0}case"ter":return f(r.cond,e)?f(r.then,e):f(r.else,e)}return 0},P=function(r){var e=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),t=r.match(e);if(!t||t[0]!==r)throw new Error(N('The plural form "%1" is not valid',r));var n=r.match(/nplurals\s*=\s*([0-9]+)/),v=r.match(/plural\s*=\s*([^;]+)/),o=n?parseInt(n[1],10):2,h=v?v[1]:"n != 1",m=I(h);return function(a){var l=f(m,typeof a=="number"?a:Number(a)||0);return{nplurals:o,plural:l===!0?1:l?Number(l):0}}},S=function(r,e,t){if(!t.plural_form)return N.apply(this,[O(r[0])].concat(Array.prototype.slice.call(arguments,3)));var n;return t.plural_func?n=t.plural_func(e):(d[s]||(d[s]=P(_[s])),n=d[s](e)),(typeof n.plural>"u"||n.plural>n.nplurals||r.length<=n.plural)&&(n.plural=0),N.apply(this,[O(r[n.plural])].concat(Array.prototype.slice.call(arguments,3)))};return{strfmt:N,expand_locale:E,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(r,e,t,n){if(!r||!e||!t)throw new Error("You must provide a domain, a locale and messages");if(typeof r!="string"||typeof e!="string"||!A.isObject(t))throw new Error("Invalid arguments");return e=M(e),n&&(_[e]=n),p[r]||(p[r]={}),p[r][e]=t,this},loadJSON:function(r,e){if(A.isObject(r)||(r=JSON.parse(r)),!r[""]||!r[""].language||!r[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var t=r[""];return delete r[""],this.setMessages(e||y.domain,t.language,r,t["plural-forms"])},setLocale:function(r){return s=M(r),this},getLocale:function(){return s},textdomain:function(r){return r?(b=r,this):b},gettext:function(r){return this.dcnpgettext.apply(this,[void 0,void 0,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(r,e,t){return this.dcnpgettext.apply(this,[void 0,void 0,r,e,t].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(r,e){return this.dcnpgettext.apply(this,[void 0,r,e,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(r,e,t,n,v){if(r=r||b,typeof t!="string")throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',t));var o,h={plural_form:!1},m=e?e+x+t:t,a,l,g=E(s);for(var w in g)if(l=g[w],a=p[r]&&p[r][l]&&p[r][l][m],n?a=a&&typeof p[r][l][m]!="string":a=a&&typeof p[r][l][m]=="string",a)break;return a?o=p[r][l][m]:(o=t,h.plural_func=y.plural_func),n?(h.plural_form=!0,S.apply(this,[a?o:[t,n],v,h].concat(Array.prototype.slice.call(arguments,5)))):S.apply(this,[[o],v,h].concat(Array.prototype.slice.call(arguments,5)))}}};module.exports=i18n; +diff --git a/node_modules/gettext.js/dist/gettext.esm.js b/node_modules/gettext.js/dist/gettext.esm.js +index 416dc97..c53046d 100644 +--- a/node_modules/gettext.js/dist/gettext.esm.js ++++ b/node_modules/gettext.js/dist/gettext.esm.js +@@ -86,6 +86,138 @@ var i18n = function (options) { + return locale; + }; + ++ var parsePlural = function (expr) { ++ var pos = 0; ++ var str = expr.replace(/\s+/g, ''); ++ ++ function parseTernary() { ++ var cond = parseOr(); ++ if (str[pos] === '?') { ++ pos++; ++ var then = parseTernary(); ++ if (str[pos] === ':') pos++; ++ var el = parseTernary(); ++ return { type: 'ter', cond: cond, then: then, else: el }; ++ } ++ return cond; ++ } ++ ++ function parseOr() { ++ var node = parseAnd(); ++ while (str.slice(pos, pos + 2) === '||') { ++ pos += 2; ++ node = { type: 'bin', op: '||', left: node, right: parseAnd() }; ++ } ++ return node; ++ } ++ ++ function parseAnd() { ++ var node = parseEquality(); ++ while (str.slice(pos, pos + 2) === '&&') { ++ pos += 2; ++ node = { type: 'bin', op: '&&', left: node, right: parseEquality() }; ++ } ++ return node; ++ } ++ ++ function parseEquality() { ++ var node = parseRelational(); ++ while (str.slice(pos, pos + 2) === '==' || str.slice(pos, pos + 2) === '!=') { ++ var op = str.slice(pos, pos + 2); ++ pos += 2; ++ node = { type: 'bin', op: op, left: node, right: parseRelational() }; ++ } ++ return node; ++ } ++ ++ function parseRelational() { ++ var node = parseAddSub(); ++ while (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=' || str[pos] === '<' || str[pos] === '>') { ++ var op = (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=') ? str.slice(pos, pos + 2) : str[pos]; ++ pos += op.length; ++ node = { type: 'bin', op: op, left: node, right: parseAddSub() }; ++ } ++ return node; ++ } ++ ++ function parseAddSub() { ++ var node = parseMulDivMod(); ++ while (str[pos] === '+' || str[pos] === '-') { ++ var op = str[pos]; ++ pos++; ++ node = { type: 'bin', op: op, left: node, right: parseMulDivMod() }; ++ } ++ return node; ++ } ++ ++ function parseMulDivMod() { ++ var node = parseUnary(); ++ while (str[pos] === '*' || str[pos] === '/' || str[pos] === '%') { ++ var op = str[pos]; ++ pos++; ++ node = { type: 'bin', op: op, left: node, right: parseUnary() }; ++ } ++ return node; ++ } ++ ++ function parseUnary() { ++ if (str[pos] === '!') { ++ pos++; ++ return { type: 'un', op: op, arg: parseUnary() }; ++ } ++ return parsePrimary(); ++ } ++ ++ function parsePrimary() { ++ if (str[pos] === '(') { ++ pos++; ++ var node = parseTernary(); ++ if (str[pos] === ')') pos++; ++ return node; ++ } ++ if (str[pos] === 'n') { ++ pos++; ++ return { type: 'n' }; ++ } ++ var m = str.slice(pos).match(/^[0-9]+/); ++ if (m) { ++ pos += m[0].length; ++ return { type: 'num', value: parseInt(m[0], 10) }; ++ } ++ return { type: 'num', value: 0 }; ++ } ++ ++ return parseTernary(); ++ }; ++ ++ var evalPluralNode = function (node, n) { ++ switch (node.type) { ++ case 'num': return node.value; ++ case 'n': return n; ++ case 'un': return node.op === '!' ? !evalPluralNode(node.arg, n) : -evalPluralNode(node.arg, n); ++ case 'bin': { ++ var l = evalPluralNode(node.left, n); ++ if (node.op === '&&') return l && evalPluralNode(node.right, n); ++ if (node.op === '||') return l || evalPluralNode(node.right, n); ++ var r = evalPluralNode(node.right, n); ++ if (node.op === '==') return Number(l) === Number(r); ++ if (node.op === '!=') return Number(l) !== Number(r); ++ if (node.op === '<=') return Number(l) <= Number(r); ++ if (node.op === '>=') return Number(l) >= Number(r); ++ if (node.op === '<') return Number(l) < Number(r); ++ if (node.op === '>') return Number(l) > Number(r); ++ if (node.op === '+') return Number(l) + Number(r); ++ if (node.op === '-') return Number(l) - Number(r); ++ if (node.op === '*') return Number(l) * Number(r); ++ if (node.op === '/') return Number(r) !== 0 ? Math.floor(Number(l) / Number(r)) : 0; ++ if (node.op === '%') return Number(r) !== 0 ? Number(l) % Number(r) : 0; ++ return 0; ++ } ++ case 'ter': return evalPluralNode(node.cond, n) ? evalPluralNode(node.then, n) : evalPluralNode(node.else, n); ++ } ++ return 0; ++ }; ++ + var getPluralFunc = function (plural_form) { + // Plural form string regexp + // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js +@@ -97,13 +229,16 @@ var i18n = function (options) { + if (!match || match[0] !== plural_form) + throw new Error(strfmt('The plural form "%1" is not valid', plural_form)); + +- console.log('>>> Plural form:', plural_form); ++ var npluralsMatch = plural_form.match(/nplurals\s*=\s*([0-9]+)/); ++ var pluralMatch = plural_form.match(/plural\s*=\s*([^;]+)/); ++ var nplurals = npluralsMatch ? parseInt(npluralsMatch[1], 10) : 2; ++ var expr = pluralMatch ? pluralMatch[1] : 'n != 1'; ++ var ast = parsePlural(expr); + +- // Careful here, this is a hidden eval() equivalent.. +- // Risk should be reasonable though since we test the plural_form through regex before +- // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js +- // TODO: should test if https://github.com/soney/jsep present and use it if so +- return new Function("n", 'var plural, nplurals; '+ plural_form +' return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };'); ++ return function (n) { ++ var res = evalPluralNode(ast, typeof n === 'number' ? n : Number(n) || 0); ++ return { nplurals: nplurals, plural: (res === true ? 1 : (res ? Number(res) : 0)) }; ++ }; + }; + + // Proper translation function that handle plurals and directives +diff --git a/node_modules/gettext.js/dist/gettext.esm.min.js b/node_modules/gettext.js/dist/gettext.esm.min.js +index e957ef1..35e4f0d 100644 +--- a/node_modules/gettext.js/dist/gettext.esm.min.js ++++ b/node_modules/gettext.js/dist/gettext.esm.min.js +@@ -1,2 +1 @@ +-/*! gettext.js - Guillaume Potier - MIT Licensed */ +-var i18n=function(t){t=t||{},this&&(this.__version="2.0.0");var r={domain:"messages",locale:"undefined"!=typeof document&&document.documentElement.getAttribute("lang")||"en",plural_func:function(t){return{nplurals:2,plural:1!=t?1:0}},ctxt_delimiter:String.fromCharCode(4)},e=function(t){var r=typeof t;return"function"===r||"object"===r&&!!t},n={},l=t.locale||r.locale,a=t.domain||r.domain,o={},i={},u=t.ctxt_delimiter||r.ctxt_delimiter;t.messages&&(o[a]={},o[a][l]=t.messages),t.plural_forms&&(i[l]=t.plural_forms);var s=function(t){var r=arguments;return t.replace(/%%/g,"%% ").replace(/%(\d+)/g,(function(t,e){return r[e]})).replace(/%% /g,"%")},p=function(t){return-1!==t.indexOf(u)?t.split(u)[1]:t},c=function(t){for(var r=[t],e=t.lastIndexOf("-");e>0;)t=t.slice(0,e),r.push(t),e=t.lastIndexOf("-");return r},f=function(t){var r=(t=t.replace("_","-")).search(/[.@]/);return-1!=r&&(t=t.slice(0,r)),t},d=function(t){var r=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),e=t.match(r);if(!e||e[0]!==t)throw new Error(s('The plural form "%1" is not valid',t));return console.log(">>> Plural form:",t),new Function("n","var plural, nplurals; "+t+" return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };")},g=function(t,r,e){return e.plural_form?(e.plural_func?a=e.plural_func(r):(n[l]||(n[l]=d(i[l])),a=n[l](r)),(void 0===a.plural||a.plural>a.nplurals||t.length<=a.plural)&&(a.plural=0),s.apply(this,[p(t[a.plural])].concat(Array.prototype.slice.call(arguments,3)))):s.apply(this,[p(t[0])].concat(Array.prototype.slice.call(arguments,3)));var a};return{strfmt:s,expand_locale:c,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(t,r,n,l){if(!t||!r||!n)throw new Error("You must provide a domain, a locale and messages");if("string"!=typeof t||"string"!=typeof r||!e(n))throw new Error("Invalid arguments");return r=f(r),l&&(i[r]=l),o[t]||(o[t]={}),o[t][r]=n,this},loadJSON:function(t,n){if(e(t)||(t=JSON.parse(t)),!t[""]||!t[""].language||!t[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var l=t[""];return delete t[""],this.setMessages(n||r.domain,l.language,t,l["plural-forms"])},setLocale:function(t){return l=f(t),this},getLocale:function(){return l},textdomain:function(t){return t?(a=t,this):a},gettext:function(t){return this.dcnpgettext.apply(this,[void 0,void 0,t,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(t,r,e){return this.dcnpgettext.apply(this,[void 0,void 0,t,r,e].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(t,r){return this.dcnpgettext.apply(this,[void 0,t,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(t,e,n,i,s){if(t=t||a,"string"!=typeof n)throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',n));var p,f,d,h={plural_form:!1},m=e?e+u+n:n,y=c(l);for(var v in y)if(d=y[v],f=o[t]&&o[t][d]&&o[t][d][m],f=i?f&&"string"!=typeof o[t][d][m]:f&&"string"==typeof o[t][d][m])break;return f?p=o[t][d][m]:(p=n,h.plural_func=r.plural_func),i?(h.plural_form=!0,g.apply(this,[f?p:[n,i],s,h].concat(Array.prototype.slice.call(arguments,5)))):g.apply(this,[[p],s,h].concat(Array.prototype.slice.call(arguments,5)))}}};export default i18n; +\ No newline at end of file ++/*! gettext.js - Guillaume Potier - MIT Licensed */var C=function(c){c=c||{},this&&(this.__version="2.0.0");var y={domain:"messages",locale:(typeof document<"u"?document.documentElement.getAttribute("lang"):!1)||"en",plural_func:function(r){return{nplurals:2,plural:r!=1?1:0}},ctxt_delimiter:""},A={isObject:function(r){var e=typeof r;return e==="function"||e==="object"&&!!r},isArray:function(r){return toString.call(r)==="[object Array]"}},d={},f=c.locale||y.locale,b=c.domain||y.domain,p={},_={},x=c.ctxt_delimiter||y.ctxt_delimiter;c.messages&&(p[b]={},p[b][f]=c.messages),c.plural_forms&&(_[f]=c.plural_forms);var N=function(r){var e=arguments;return r.replace(/%%/g,"%% ").replace(/%(\d+)/g,function(t,n){return e[n]}).replace(/%% /g,"%")},O=function(r){if(r.indexOf(x)!==-1){var e=r.split(x);return e[1]}return r},E=function(r){for(var e=[r],t=r.lastIndexOf("-");t>0;)r=r.slice(0,t),e.push(r),t=r.lastIndexOf("-");return e},M=function(r){r=r.replace("_","-");var e=r.search(/[.@]/);return e!=-1&&(r=r.slice(0,e)),r},I=function(r){var e=0,t=r.replace(/\s+/g,"");function n(){var u=m();if(t[e]==="?"){e++;var i=n();t[e]===":"&&e++;var k=n();return{type:"ter",cond:u,then:i,else:k}}return u}function m(){for(var u=o();t.slice(e,e+2)==="||";)e+=2,u={type:"bin",op:"||",left:u,right:o()};return u}function o(){for(var u=h();t.slice(e,e+2)==="&&";)e+=2,u={type:"bin",op:"&&",left:u,right:h()};return u}function h(){for(var u=v();t.slice(e,e+2)==="=="||t.slice(e,e+2)==="!=";){var i=t.slice(e,e+2);e+=2,u={type:"bin",op:i,left:u,right:v()}}return u}function v(){for(var u=a();t.slice(e,e+2)==="<="||t.slice(e,e+2)===">="||t[e]==="<"||t[e]===">";){var i=t.slice(e,e+2)==="<="||t.slice(e,e+2)===">="?t.slice(e,e+2):t[e];e+=i.length,u={type:"bin",op:i,left:u,right:a()}}return u}function a(){for(var u=l();t[e]==="+"||t[e]==="-";){var i=t[e];e++,u={type:"bin",op:i,left:u,right:l()}}return u}function l(){for(var u=g();t[e]==="*"||t[e]==="/"||t[e]==="%";){var i=t[e];e++,u={type:"bin",op:i,left:u,right:g()}}return u}function g(){return t[e]==="!"?(e++,{type:"un",op,arg:g()}):w()}function w(){if(t[e]==="("){e++;var u=n();return t[e]===")"&&e++,u}if(t[e]==="n")return e++,{type:"n"};var i=t.slice(e).match(/^[0-9]+/);return i?(e+=i[0].length,{type:"num",value:parseInt(i[0],10)}):{type:"num",value:0}}return n()},s=function(r,e){switch(r.type){case"num":return r.value;case"n":return e;case"un":return r.op==="!"?!s(r.arg,e):-s(r.arg,e);case"bin":{var t=s(r.left,e);if(r.op==="&&")return t&&s(r.right,e);if(r.op==="||")return t||s(r.right,e);var n=s(r.right,e);return r.op==="=="?Number(t)===Number(n):r.op==="!="?Number(t)!==Number(n):r.op==="<="?Number(t)<=Number(n):r.op===">="?Number(t)>=Number(n):r.op==="<"?Number(t)"?Number(t)>Number(n):r.op==="+"?Number(t)+Number(n):r.op==="-"?Number(t)-Number(n):r.op==="*"?Number(t)*Number(n):r.op==="/"?Number(n)!==0?Math.floor(Number(t)/Number(n)):0:r.op==="%"&&Number(n)!==0?Number(t)%Number(n):0}case"ter":return s(r.cond,e)?s(r.then,e):s(r.else,e)}return 0},P=function(r){var e=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),t=r.match(e);if(!t||t[0]!==r)throw new Error(N('The plural form "%1" is not valid',r));var n=r.match(/nplurals\s*=\s*([0-9]+)/),m=r.match(/plural\s*=\s*([^;]+)/),o=n?parseInt(n[1],10):2,h=m?m[1]:"n != 1",v=I(h);return function(a){var l=s(v,typeof a=="number"?a:Number(a)||0);return{nplurals:o,plural:l===!0?1:l?Number(l):0}}},S=function(r,e,t){if(!t.plural_form)return N.apply(this,[O(r[0])].concat(Array.prototype.slice.call(arguments,3)));var n;return t.plural_func?n=t.plural_func(e):(d[f]||(d[f]=P(_[f])),n=d[f](e)),(typeof n.plural>"u"||n.plural>n.nplurals||r.length<=n.plural)&&(n.plural=0),N.apply(this,[O(r[n.plural])].concat(Array.prototype.slice.call(arguments,3)))};return{strfmt:N,expand_locale:E,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(r,e,t,n){if(!r||!e||!t)throw new Error("You must provide a domain, a locale and messages");if(typeof r!="string"||typeof e!="string"||!A.isObject(t))throw new Error("Invalid arguments");return e=M(e),n&&(_[e]=n),p[r]||(p[r]={}),p[r][e]=t,this},loadJSON:function(r,e){if(A.isObject(r)||(r=JSON.parse(r)),!r[""]||!r[""].language||!r[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var t=r[""];return delete r[""],this.setMessages(e||y.domain,t.language,r,t["plural-forms"])},setLocale:function(r){return f=M(r),this},getLocale:function(){return f},textdomain:function(r){return r?(b=r,this):b},gettext:function(r){return this.dcnpgettext.apply(this,[void 0,void 0,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(r,e,t){return this.dcnpgettext.apply(this,[void 0,void 0,r,e,t].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(r,e){return this.dcnpgettext.apply(this,[void 0,r,e,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(r,e,t,n,m){if(r=r||b,typeof t!="string")throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',t));var o,h={plural_form:!1},v=e?e+x+t:t,a,l,g=E(f);for(var w in g)if(l=g[w],a=p[r]&&p[r][l]&&p[r][l][v],n?a=a&&typeof p[r][l][v]!="string":a=a&&typeof p[r][l][v]=="string",a)break;return a?o=p[r][l][v]:(o=t,h.plural_func=y.plural_func),n?(h.plural_form=!0,S.apply(this,[a?o:[t,n],m,h].concat(Array.prototype.slice.call(arguments,5)))):S.apply(this,[[o],m,h].concat(Array.prototype.slice.call(arguments,5)))}}};export default C; +diff --git a/node_modules/gettext.js/lib/gettext.js b/node_modules/gettext.js/lib/gettext.js +index 88aaafc..85db7cc 100644 +--- a/node_modules/gettext.js/lib/gettext.js ++++ b/node_modules/gettext.js/lib/gettext.js +@@ -86,6 +86,139 @@ var i18n = function (options) { + return locale; + }; + ++ var parsePlural = function (expr) { ++ var pos = 0; ++ var str = expr.replace(/\s+/g, ''); ++ ++ function parseTernary() { ++ var cond = parseOr(); ++ if (str[pos] === '?') { ++ pos++; ++ var then = parseTernary(); ++ if (str[pos] === ':') pos++; ++ var el = parseTernary(); ++ return { type: 'ter', cond: cond, then: then, else: el }; ++ } ++ return cond; ++ } ++ ++ function parseOr() { ++ var node = parseAnd(); ++ while (str.slice(pos, pos + 2) === '||') { ++ pos += 2; ++ node = { type: 'bin', op: '||', left: node, right: parseAnd() }; ++ } ++ return node; ++ } ++ ++ function parseAnd() { ++ var node = parseEquality(); ++ while (str.slice(pos, pos + 2) === '&&') { ++ pos += 2; ++ node = { type: 'bin', op: '&&', left: node, right: parseEquality() }; ++ } ++ return node; ++ } ++ ++ function parseEquality() { ++ var node = parseRelational(); ++ while (str.slice(pos, pos + 2) === '==' || str.slice(pos, pos + 2) === '!=') { ++ var op = str.slice(pos, pos + 2); ++ pos += 2; ++ node = { type: 'bin', op: op, left: node, right: parseRelational() }; ++ } ++ return node; ++ } ++ ++ function parseRelational() { ++ var node = parseAddSub(); ++ while (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=' || str[pos] === '<' || str[pos] === '>') { ++ var op = (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=') ? str.slice(pos, pos + 2) : str[pos]; ++ pos += op.length; ++ node = { type: 'bin', op: op, left: node, right: parseAddSub() }; ++ } ++ return node; ++ } ++ ++ function parseAddSub() { ++ var node = parseMulDivMod(); ++ while (str[pos] === '+' || str[pos] === '-') { ++ var op = str[pos]; ++ pos++; ++ node = { type: 'bin', op: op, left: node, right: parseMulDivMod() }; ++ } ++ return node; ++ } ++ ++ function parseMulDivMod() { ++ var node = parseUnary(); ++ while (str[pos] === '*' || str[pos] === '/' || str[pos] === '%') { ++ var op = str[pos]; ++ pos++; ++ node = { type: 'bin', op: op, left: node, right: parseUnary() }; ++ } ++ return node; ++ } ++ ++ function parseUnary() { ++ if (str[pos] === '!' || str[pos] === '+' || str[pos] === '-') { ++ var op = str[pos]; ++ pos++; ++ return { type: 'un', op: op, arg: parseUnary() }; ++ } ++ return parsePrimary(); ++ } ++ ++ function parsePrimary() { ++ if (str[pos] === '(') { ++ pos++; ++ var node = parseTernary(); ++ if (str[pos] === ')') pos++; ++ return node; ++ } ++ if (str[pos] === 'n') { ++ pos++; ++ return { type: 'n' }; ++ } ++ var m = str.slice(pos).match(/^[0-9]+/); ++ if (m) { ++ pos += m[0].length; ++ return { type: 'num', value: parseInt(m[0], 10) }; ++ } ++ return { type: 'num', value: 0 }; ++ } ++ ++ return parseTernary(); ++ }; ++ ++ var evalPluralNode = function (node, n) { ++ switch (node.type) { ++ case 'num': return node.value; ++ case 'n': return n; ++ case 'un': return node.op === '!' ? !evalPluralNode(node.arg, n) : -evalPluralNode(node.arg, n); ++ case 'bin': { ++ var l = evalPluralNode(node.left, n); ++ if (node.op === '&&') return l && evalPluralNode(node.right, n); ++ if (node.op === '||') return l || evalPluralNode(node.right, n); ++ var r = evalPluralNode(node.right, n); ++ if (node.op === '==') return Number(l) === Number(r); ++ if (node.op === '!=') return Number(l) !== Number(r); ++ if (node.op === '<=') return Number(l) <= Number(r); ++ if (node.op === '>=') return Number(l) >= Number(r); ++ if (node.op === '<') return Number(l) < Number(r); ++ if (node.op === '>') return Number(l) > Number(r); ++ if (node.op === '+') return Number(l) + Number(r); ++ if (node.op === '-') return Number(l) - Number(r); ++ if (node.op === '*') return Number(l) * Number(r); ++ if (node.op === '/') return Number(r) !== 0 ? Math.floor(Number(l) / Number(r)) : 0; ++ if (node.op === '%') return Number(r) !== 0 ? Number(l) % Number(r) : 0; ++ return 0; ++ } ++ case 'ter': return evalPluralNode(node.cond, n) ? evalPluralNode(node.then, n) : evalPluralNode(node.else, n); ++ } ++ return 0; ++ }; ++ + var getPluralFunc = function (plural_form) { + // Plural form string regexp + // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js +@@ -97,11 +230,16 @@ var i18n = function (options) { + if (!match || match[0] !== plural_form) + throw new Error(strfmt('The plural form "%1" is not valid', plural_form)); + +- // Careful here, this is a hidden eval() equivalent.. +- // Risk should be reasonable though since we test the plural_form through regex before +- // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js +- // TODO: should test if https://github.com/soney/jsep present and use it if so +- return new Function("n", 'var plural, nplurals; '+ plural_form +' return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };'); ++ var npluralsMatch = plural_form.match(/nplurals\s*=\s*([0-9]+)/); ++ var pluralMatch = plural_form.match(/plural\s*=\s*([^;]+)/); ++ var nplurals = npluralsMatch ? parseInt(npluralsMatch[1], 10) : 2; ++ var expr = pluralMatch ? pluralMatch[1] : 'n != 1'; ++ var ast = parsePlural(expr); ++ ++ return function (n) { ++ var res = evalPluralNode(ast, typeof n === 'number' ? n : Number(n) || 0); ++ return { nplurals: nplurals, plural: (res === true ? 1 : (res ? Number(res) : 0)) }; ++ }; + }; + + // Proper translation function that handle plurals and directives diff --git a/src/utilities/Intl.jsx b/src/utilities/Intl.jsx index 214bb5d48b..f7d095720c 100644 --- a/src/utilities/Intl.jsx +++ b/src/utilities/Intl.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types' import trimChars from 'lodash/fp/trimChars' -import makei18n from 'src/utilities/gettext' +import makei18n from 'gettext.js' export const i18n = new makei18n() diff --git a/src/utilities/__tests__/gettext-test.js b/src/utilities/__tests__/gettext-test.js index 6d443b3b3d..8a10926e67 100644 --- a/src/utilities/__tests__/gettext-test.js +++ b/src/utilities/__tests__/gettext-test.js @@ -1,4 +1,4 @@ -import i18n from 'src/utilities/gettext' +import i18n from 'gettext.js' describe('gettext safe implementation', () => { let originalFunction diff --git a/src/utilities/gettext.js b/src/utilities/gettext.js deleted file mode 100644 index acca20eeff..0000000000 --- a/src/utilities/gettext.js +++ /dev/null @@ -1,471 +0,0 @@ -/*! - * gettext.js - CSP-compliant implementation - * Evaluates GNU gettext plural forms safely without eval() or new Function(). - */ - -/** - * Parses and safely evaluates GNU gettext plural form expressions without dynamic code execution. - * Grammar supports: - * - Variables: 'n' - * - Integer numbers - * - Arithmetic operators: +, -, *, /, % (with integer division) - * - Comparison operators: ==, !=, <, <=, >, >= - * - Logical operators: &&, ||, ! - * - Conditional (ternary): ? : - * - Grouping: ( ) - */ -function evaluatePluralExpression(expr, n) { - let pos = 0 - const str = expr.replace(/\s+/g, '') - - function parseTernary() { - const condition = parseOr() - if (str[pos] === '?') { - pos++ // skip '?' - const trueBranch = parseTernary() - if (str[pos] !== ':') { - throw new Error(`Expected ':' at position ${pos} in plural expression: ${expr}`) - } - pos++ // skip ':' - const falseBranch = parseTernary() - return condition ? trueBranch : falseBranch - } - return condition - } - - function parseOr() { - let val = parseAnd() - while (str.slice(pos, pos + 2) === '||') { - pos += 2 - const right = parseAnd() - val = val || right - } - return val - } - - function parseAnd() { - let val = parseEquality() - while (str.slice(pos, pos + 2) === '&&') { - pos += 2 - const right = parseEquality() - val = val && right - } - return val - } - - function parseEquality() { - let val = parseRelational() - while (str.slice(pos, pos + 2) === '==' || str.slice(pos, pos + 2) === '!=') { - const op = str.slice(pos, pos + 2) - pos += 2 - const right = parseRelational() - val = op === '==' ? Number(val) === Number(right) : Number(val) !== Number(right) - } - return val - } - - function parseRelational() { - let val = parseAddSub() - while ( - str.slice(pos, pos + 2) === '<=' || - str.slice(pos, pos + 2) === '>=' || - str[pos] === '<' || - str[pos] === '>' - ) { - if (str.slice(pos, pos + 2) === '<=') { - pos += 2 - val = Number(val) <= Number(parseAddSub()) - } else if (str.slice(pos, pos + 2) === '>=') { - pos += 2 - val = Number(val) >= Number(parseAddSub()) - } else if (str[pos] === '<') { - pos++ - val = Number(val) < Number(parseAddSub()) - } else if (str[pos] === '>') { - pos++ - val = Number(val) > Number(parseAddSub()) - } - } - return val - } - - function parseAddSub() { - let val = parseMulDivMod() - while (str[pos] === '+' || str[pos] === '-') { - const op = str[pos] - pos++ - const right = parseMulDivMod() - val = op === '+' ? Number(val) + Number(right) : Number(val) - Number(right) - } - return val - } - - function parseMulDivMod() { - let val = parseUnary() - while (str[pos] === '*' || str[pos] === '/' || str[pos] === '%') { - const op = str[pos] - pos++ - const right = parseUnary() - if (op === '*') { - val = Number(val) * Number(right) - } else if (op === '/') { - val = Number(right) !== 0 ? Math.floor(Number(val) / Number(right)) : 0 - } else if (op === '%') { - val = Number(right) !== 0 ? Number(val) % Number(right) : 0 - } - } - return val - } - - function parseUnary() { - if (str[pos] === '!') { - pos++ - return !parseUnary() - } - if (str[pos] === '+') { - pos++ - return +parseUnary() - } - if (str[pos] === '-') { - pos++ - return -parseUnary() - } - return parsePrimary() - } - - function parsePrimary() { - if (str[pos] === '(') { - pos++ - const val = parseTernary() - if (str[pos] !== ')') { - throw new Error(`Expected ')' at position ${pos} in plural expression: ${expr}`) - } - pos++ - return val - } - if (str[pos] === 'n') { - pos++ - return n - } - const numMatch = str.slice(pos).match(/^[0-9]+/) - if (numMatch) { - pos += numMatch[0].length - return parseInt(numMatch[0], 10) - } - throw new Error(`Unexpected token at position ${pos} in plural expression: ${expr}`) - } - - return parseTernary() -} - -const compilePluralForm = function (pluralForm) { - const npluralsMatch = pluralForm.match(/nplurals\s*=\s*([0-9]+)/) - const pluralMatch = pluralForm.match(/plural\s*=\s*([^;]+)/) - - if (!npluralsMatch || !pluralMatch) { - throw new Error(`The plural form "${pluralForm}" is not valid`) - } - - const nplurals = parseInt(npluralsMatch[1], 10) - const expr = pluralMatch[1].trim() - - return function (n) { - const rawResult = evaluatePluralExpression(expr, typeof n === 'number' ? n : Number(n) || 0) - let plural = 0 - if (rawResult === true) { - plural = 1 - } else if (rawResult) { - plural = Number(rawResult) - } - - return { - nplurals, - plural, - } - } -} - -const i18n = function (options) { - const opts = options || {} - if (this) { - this.__version = '2.0.0' - } - - const defaults = { - domain: 'messages', - locale: - (typeof document !== 'undefined' ? document.documentElement.getAttribute('lang') : false) || - 'en', - plural_func: function (n) { - return { nplurals: 2, plural: n !== 1 ? 1 : 0 } - }, - ctxt_delimiter: String.fromCharCode(4), // \u0004 - } - - const _ = { - isObject: function (obj) { - const type = typeof obj - return type === 'function' || (type === 'object' && !!obj) - }, - } - - const _plural_funcs = {} - let _locale = opts.locale || defaults.locale - let _domain = opts.domain || defaults.domain - const _dictionary = {} - const _plural_forms = {} - const _ctxt_delimiter = opts.ctxt_delimiter || defaults.ctxt_delimiter - - if (opts.messages) { - _dictionary[_domain] = {} - _dictionary[_domain][_locale] = opts.messages - } - - if (opts.plural_forms) { - _plural_forms[_locale] = opts.plural_forms - } - - const strfmt = function (fmt) { - const args = arguments - return fmt - .replace(/%%/g, '%% ') - .replace(/%(\d+)/g, function (_str, p1) { - return args[p1] - }) - .replace(/%% /g, '%') - } - - const removeContext = function (str) { - if (str.indexOf(_ctxt_delimiter) !== -1) { - const parts = str.split(_ctxt_delimiter) - return parts[1] - } - return str - } - - const expand_locale = function (locale) { - const locales = [locale] - let curLocale = locale - let i = curLocale.lastIndexOf('-') - while (i > 0) { - curLocale = curLocale.slice(0, i) - locales.push(curLocale) - i = curLocale.lastIndexOf('-') - } - return locales - } - - const normalizeLocale = function (locale) { - let normalized = locale.replace('_', '-') - const i = normalized.search(/[.@]/) - if (i !== -1) { - normalized = normalized.slice(0, i) - } - return normalized - } - - const getPluralFunc = function (plural_form) { - const pf_re = new RegExp( - '^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_\\(\\)])+', - ) - const match = plural_form.match(pf_re) - - if (!match || match[0] !== plural_form) { - throw new Error(strfmt('The plural form "%1" is not valid', plural_form)) - } - - return compilePluralForm(plural_form) - } - - const t = function (messages, n, tOptions /* , extra */) { - if (!tOptions.plural_form) { - return strfmt.apply( - this, - [removeContext(messages[0])].concat(Array.prototype.slice.call(arguments, 3)), - ) - } - - let plural - if (tOptions.plural_func) { - plural = tOptions.plural_func(n) - } else if (!_plural_funcs[_locale]) { - _plural_funcs[_locale] = getPluralFunc(_plural_forms[_locale]) - plural = _plural_funcs[_locale](n) - } else { - plural = _plural_funcs[_locale](n) - } - - if ( - typeof plural.plural === 'undefined' || - plural.plural > plural.nplurals || - messages.length <= plural.plural - ) { - plural.plural = 0 - } - - return strfmt.apply( - this, - [removeContext(messages[plural.plural])].concat(Array.prototype.slice.call(arguments, 3)), - ) - } - - return { - strfmt, - expand_locale, - - __: function () { - return this.gettext.apply(this, arguments) - }, - _n: function () { - return this.ngettext.apply(this, arguments) - }, - _p: function () { - return this.pgettext.apply(this, arguments) - }, - - setMessages: function (domain, locale, messages, plural_forms) { - if (!domain || !locale || !messages) { - throw new Error('You must provide a domain, a locale and messages') - } - - if (typeof domain !== 'string' || typeof locale !== 'string' || !_.isObject(messages)) { - throw new Error('Invalid arguments') - } - - const normalizedLocale = normalizeLocale(locale) - - if (plural_forms) { - _plural_forms[normalizedLocale] = plural_forms - } - - if (!_dictionary[domain]) { - _dictionary[domain] = {} - } - - _dictionary[domain][normalizedLocale] = messages - - return this - }, - - loadJSON: function (jsonData, domain) { - const data = - typeof jsonData === 'object' && jsonData !== null ? jsonData : JSON.parse(jsonData) - - if (!data[''] || !data['']['language'] || !data['']['plural-forms']) { - throw new Error( - 'Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information', - ) - } - - const headers = data[''] - delete data[''] - - return this.setMessages( - domain || defaults.domain, - headers['language'], - data, - headers['plural-forms'], - ) - }, - - setLocale: function (locale) { - _locale = normalizeLocale(locale) - return this - }, - - getLocale: function () { - return _locale - }, - - textdomain: function (domain) { - if (!domain) { - return _domain - } - _domain = domain - return this - }, - - gettext: function (msgid /* , extra */) { - return this.dcnpgettext.apply( - this, - [undefined, undefined, msgid, undefined, undefined].concat( - Array.prototype.slice.call(arguments, 1), - ), - ) - }, - - ngettext: function (msgid, msgid_plural, n /* , extra */) { - return this.dcnpgettext.apply( - this, - [undefined, undefined, msgid, msgid_plural, n].concat( - Array.prototype.slice.call(arguments, 3), - ), - ) - }, - - pgettext: function (msgctxt, msgid /* , extra */) { - return this.dcnpgettext.apply( - this, - [undefined, msgctxt, msgid, undefined, undefined].concat( - Array.prototype.slice.call(arguments, 2), - ), - ) - }, - - dcnpgettext: function (domain, msgctxt, msgid, msgid_plural, n /* , extra */) { - const currentDomain = domain || _domain - - if (typeof msgid !== 'string') { - throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string', msgid)) - } - - let translation - const options = { plural_form: false } - const key = msgctxt ? msgctxt + _ctxt_delimiter + msgid : msgid - let exist - let foundLocale - const locales = expand_locale(_locale) - - for (const localeCandidate of locales) { - exist = - _dictionary[currentDomain] && - _dictionary[currentDomain][localeCandidate] && - _dictionary[currentDomain][localeCandidate][key] - - if (msgid_plural) { - exist = exist && typeof _dictionary[currentDomain][localeCandidate][key] !== 'string' - } else { - exist = exist && typeof _dictionary[currentDomain][localeCandidate][key] === 'string' - } - if (exist) { - foundLocale = localeCandidate - break - } - } - - if (!exist) { - translation = msgid - options.plural_func = defaults.plural_func - } else { - translation = _dictionary[currentDomain][foundLocale][key] - } - - if (!msgid_plural) { - return t.apply( - this, - [[translation], n, options].concat(Array.prototype.slice.call(arguments, 5)), - ) - } - - options.plural_form = true - return t.apply( - this, - [exist ? translation : [msgid, msgid_plural], n, options].concat( - Array.prototype.slice.call(arguments, 5), - ), - ) - }, - } -} - -export default i18n diff --git a/vite.config.ts b/vite.config.ts index 083cbb02d8..f5dc929983 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -72,7 +72,6 @@ export default defineConfig({ }, resolve: { alias: { - 'gettext.js': path.resolve(__dirname, './src/utilities/gettext.js'), src: path.resolve(__dirname, './src'), utils: path.join(__dirname, 'src/utils'), }, From 71ff05bd8713a6c917d41f0a1c617ea159db5b5c Mon Sep 17 00:00:00 2001 From: Logan Rasmussen Date: Fri, 11 Sep 2026 10:59:53 -0600 Subject: [PATCH 3/4] fix(i18n): minimize gettext.js patch diff for code review --- package-lock.json | 2 + patches/gettext.js+2.0.3.patch | 515 ++++++++------------------------- 2 files changed, 120 insertions(+), 397 deletions(-) diff --git a/package-lock.json b/package-lock.json index bca7c5f3d5..6e997fc4fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "@mxenabled/connect-widget", "version": "0.0.0-semantic-release", + "hasInstallScript": true, "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", @@ -8546,6 +8547,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/gettext.js/-/gettext.js-2.0.3.tgz", "integrity": "sha512-11gyttZWBUBkenEVgPMqJTL9TIKaH4PW6ZCMZr+lNXrgiYHXBg+bOGAc8OjfLE8lvi0dgwtqrSfScd310PlKJw==", + "license": "MIT", "dependencies": { "po2json": "^1.0.0-beta-3" }, diff --git a/patches/gettext.js+2.0.3.patch b/patches/gettext.js+2.0.3.patch index 373872eaa4..36c3834c3f 100644 --- a/patches/gettext.js+2.0.3.patch +++ b/patches/gettext.js+2.0.3.patch @@ -1,331 +1,109 @@ diff --git a/node_modules/gettext.js/dist/gettext.cjs.js b/node_modules/gettext.js/dist/gettext.cjs.js -index 85c83de..ff22c27 100644 +index 85c83de..01b22ab 100644 --- a/node_modules/gettext.js/dist/gettext.cjs.js +++ b/node_modules/gettext.js/dist/gettext.cjs.js -@@ -88,6 +88,138 @@ var i18n = function (options) { +@@ -88,6 +88,93 @@ var i18n = function (options) { return locale; }; -+ var parsePlural = function (expr) { -+ var pos = 0; -+ var str = expr.replace(/\s+/g, ''); -+ -+ function parseTernary() { -+ var cond = parseOr(); -+ if (str[pos] === '?') { -+ pos++; -+ var then = parseTernary(); -+ if (str[pos] === ':') pos++; -+ var el = parseTernary(); -+ return { type: 'ter', cond: cond, then: then, else: el }; -+ } -+ return cond; -+ } -+ -+ function parseOr() { -+ var node = parseAnd(); -+ while (str.slice(pos, pos + 2) === '||') { -+ pos += 2; -+ node = { type: 'bin', op: '||', left: node, right: parseAnd() }; -+ } -+ return node; -+ } -+ -+ function parseAnd() { -+ var node = parseEquality(); -+ while (str.slice(pos, pos + 2) === '&&') { -+ pos += 2; -+ node = { type: 'bin', op: '&&', left: node, right: parseEquality() }; -+ } -+ return node; -+ } -+ -+ function parseEquality() { -+ var node = parseRelational(); -+ while (str.slice(pos, pos + 2) === '==' || str.slice(pos, pos + 2) === '!=') { -+ var op = str.slice(pos, pos + 2); -+ pos += 2; -+ node = { type: 'bin', op: op, left: node, right: parseRelational() }; -+ } -+ return node; -+ } -+ -+ function parseRelational() { -+ var node = parseAddSub(); -+ while (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=' || str[pos] === '<' || str[pos] === '>') { -+ var op = (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=') ? str.slice(pos, pos + 2) : str[pos]; -+ pos += op.length; -+ node = { type: 'bin', op: op, left: node, right: parseAddSub() }; -+ } -+ return node; -+ } -+ -+ function parseAddSub() { -+ var node = parseMulDivMod(); -+ while (str[pos] === '+' || str[pos] === '-') { -+ var op = str[pos]; -+ pos++; -+ node = { type: 'bin', op: op, left: node, right: parseMulDivMod() }; -+ } -+ return node; -+ } -+ -+ function parseMulDivMod() { -+ var node = parseUnary(); -+ while (str[pos] === '*' || str[pos] === '/' || str[pos] === '%') { -+ var op = str[pos]; -+ pos++; -+ node = { type: 'bin', op: op, left: node, right: parseUnary() }; -+ } -+ return node; -+ } -+ -+ function parseUnary() { -+ if (str[pos] === '!') { -+ pos++; -+ return { type: 'un', op: op, arg: parseUnary() }; -+ } -+ return parsePrimary(); -+ } -+ -+ function parsePrimary() { -+ if (str[pos] === '(') { -+ pos++; -+ var node = parseTernary(); -+ if (str[pos] === ')') pos++; -+ return node; -+ } -+ if (str[pos] === 'n') { -+ pos++; -+ return { type: 'n' }; -+ } -+ var m = str.slice(pos).match(/^[0-9]+/); -+ if (m) { -+ pos += m[0].length; -+ return { type: 'num', value: parseInt(m[0], 10) }; -+ } -+ return { type: 'num', value: 0 }; -+ } -+ -+ return parseTernary(); -+ }; -+ -+ var evalPluralNode = function (node, n) { -+ switch (node.type) { -+ case 'num': return node.value; -+ case 'n': return n; -+ case 'un': return node.op === '!' ? !evalPluralNode(node.arg, n) : -evalPluralNode(node.arg, n); -+ case 'bin': { -+ var l = evalPluralNode(node.left, n); -+ if (node.op === '&&') return l && evalPluralNode(node.right, n); -+ if (node.op === '||') return l || evalPluralNode(node.right, n); -+ var r = evalPluralNode(node.right, n); -+ if (node.op === '==') return Number(l) === Number(r); -+ if (node.op === '!=') return Number(l) !== Number(r); -+ if (node.op === '<=') return Number(l) <= Number(r); -+ if (node.op === '>=') return Number(l) >= Number(r); -+ if (node.op === '<') return Number(l) < Number(r); -+ if (node.op === '>') return Number(l) > Number(r); -+ if (node.op === '+') return Number(l) + Number(r); -+ if (node.op === '-') return Number(l) - Number(r); -+ if (node.op === '*') return Number(l) * Number(r); -+ if (node.op === '/') return Number(r) !== 0 ? Math.floor(Number(l) / Number(r)) : 0; -+ if (node.op === '%') return Number(r) !== 0 ? Number(l) % Number(r) : 0; -+ return 0; -+ } -+ case 'ter': return evalPluralNode(node.cond, n) ? evalPluralNode(node.then, n) : evalPluralNode(node.else, n); -+ } -+ return 0; -+ }; -+ - var getPluralFunc = function (plural_form) { - // Plural form string regexp - // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js -@@ -99,13 +231,16 @@ var i18n = function (options) { - if (!match || match[0] !== plural_form) - throw new Error(strfmt('The plural form "%1" is not valid', plural_form)); - -- console.log('>>> Plural form:', plural_form); -+ var npluralsMatch = plural_form.match(/nplurals\s*=\s*([0-9]+)/); -+ var pluralMatch = plural_form.match(/plural\s*=\s*([^;]+)/); -+ var nplurals = npluralsMatch ? parseInt(npluralsMatch[1], 10) : 2; -+ var expr = pluralMatch ? pluralMatch[1] : 'n != 1'; -+ var ast = parsePlural(expr); - -- // Careful here, this is a hidden eval() equivalent.. -- // Risk should be reasonable though since we test the plural_form through regex before -- // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js -- // TODO: should test if https://github.com/soney/jsep present and use it if so -- return new Function("n", 'var plural, nplurals; '+ plural_form +' return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };'); -+ return function (n) { -+ var res = evalPluralNode(ast, typeof n === 'number' ? n : Number(n) || 0); -+ return { nplurals: nplurals, plural: (res === true ? 1 : (res ? Number(res) : 0)) }; -+ }; - }; - - // Proper translation function that handle plurals and directives -diff --git a/node_modules/gettext.js/dist/gettext.cjs.min.js b/node_modules/gettext.js/dist/gettext.cjs.min.js -index 9f5f2e6..63534e1 100644 ---- a/node_modules/gettext.js/dist/gettext.cjs.min.js -+++ b/node_modules/gettext.js/dist/gettext.cjs.min.js -@@ -1,2 +1 @@ --"use strict"; --/*! gettext.js - Guillaume Potier - MIT Licensed */var i18n=function(t){t=t||{},this&&(this.__version="2.0.0");var r={domain:"messages",locale:"undefined"!=typeof document&&document.documentElement.getAttribute("lang")||"en",plural_func:function(t){return{nplurals:2,plural:1!=t?1:0}},ctxt_delimiter:String.fromCharCode(4)},e=function(t){var r=typeof t;return"function"===r||"object"===r&&!!t},n={},l=t.locale||r.locale,a=t.domain||r.domain,o={},i={},u=t.ctxt_delimiter||r.ctxt_delimiter;t.messages&&(o[a]={},o[a][l]=t.messages),t.plural_forms&&(i[l]=t.plural_forms);var s=function(t){var r=arguments;return t.replace(/%%/g,"%% ").replace(/%(\d+)/g,(function(t,e){return r[e]})).replace(/%% /g,"%")},p=function(t){return-1!==t.indexOf(u)?t.split(u)[1]:t},c=function(t){for(var r=[t],e=t.lastIndexOf("-");e>0;)t=t.slice(0,e),r.push(t),e=t.lastIndexOf("-");return r},f=function(t){var r=(t=t.replace("_","-")).search(/[.@]/);return-1!=r&&(t=t.slice(0,r)),t},d=function(t){var r=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),e=t.match(r);if(!e||e[0]!==t)throw new Error(s('The plural form "%1" is not valid',t));return console.log(">>> Plural form:",t),new Function("n","var plural, nplurals; "+t+" return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };")},g=function(t,r,e){return e.plural_form?(e.plural_func?a=e.plural_func(r):(n[l]||(n[l]=d(i[l])),a=n[l](r)),(void 0===a.plural||a.plural>a.nplurals||t.length<=a.plural)&&(a.plural=0),s.apply(this,[p(t[a.plural])].concat(Array.prototype.slice.call(arguments,3)))):s.apply(this,[p(t[0])].concat(Array.prototype.slice.call(arguments,3)));var a};return{strfmt:s,expand_locale:c,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(t,r,n,l){if(!t||!r||!n)throw new Error("You must provide a domain, a locale and messages");if("string"!=typeof t||"string"!=typeof r||!e(n))throw new Error("Invalid arguments");return r=f(r),l&&(i[r]=l),o[t]||(o[t]={}),o[t][r]=n,this},loadJSON:function(t,n){if(e(t)||(t=JSON.parse(t)),!t[""]||!t[""].language||!t[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var l=t[""];return delete t[""],this.setMessages(n||r.domain,l.language,t,l["plural-forms"])},setLocale:function(t){return l=f(t),this},getLocale:function(){return l},textdomain:function(t){return t?(a=t,this):a},gettext:function(t){return this.dcnpgettext.apply(this,[void 0,void 0,t,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(t,r,e){return this.dcnpgettext.apply(this,[void 0,void 0,t,r,e].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(t,r){return this.dcnpgettext.apply(this,[void 0,t,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(t,e,n,i,s){if(t=t||a,"string"!=typeof n)throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',n));var p,f,d,m={plural_form:!1},h=e?e+u+n:n,y=c(l);for(var v in y)if(d=y[v],f=o[t]&&o[t][d]&&o[t][d][h],f=i?f&&"string"!=typeof o[t][d][h]:f&&"string"==typeof o[t][d][h])break;return f?p=o[t][d][h]:(p=n,m.plural_func=r.plural_func),i?(m.plural_form=!0,g.apply(this,[f?p:[n,i],s,m].concat(Array.prototype.slice.call(arguments,5)))):g.apply(this,[[p],s,m].concat(Array.prototype.slice.call(arguments,5)))}}};module.exports=i18n; -\ No newline at end of file -+"use strict";/*! gettext.js - Guillaume Potier - MIT Licensed */var i18n=function(c){c=c||{},this&&(this.__version="2.0.0");var y={domain:"messages",locale:(typeof document<"u"?document.documentElement.getAttribute("lang"):!1)||"en",plural_func:function(r){return{nplurals:2,plural:r!=1?1:0}},ctxt_delimiter:""},A={isObject:function(r){var e=typeof r;return e==="function"||e==="object"&&!!r},isArray:function(r){return toString.call(r)==="[object Array]"}},d={},s=c.locale||y.locale,b=c.domain||y.domain,p={},_={},x=c.ctxt_delimiter||y.ctxt_delimiter;c.messages&&(p[b]={},p[b][s]=c.messages),c.plural_forms&&(_[s]=c.plural_forms);var N=function(r){var e=arguments;return r.replace(/%%/g,"%% ").replace(/%(\d+)/g,function(t,n){return e[n]}).replace(/%% /g,"%")},O=function(r){if(r.indexOf(x)!==-1){var e=r.split(x);return e[1]}return r},E=function(r){for(var e=[r],t=r.lastIndexOf("-");t>0;)r=r.slice(0,t),e.push(r),t=r.lastIndexOf("-");return e},M=function(r){r=r.replace("_","-");var e=r.search(/[.@]/);return e!=-1&&(r=r.slice(0,e)),r},I=function(r){var e=0,t=r.replace(/\s+/g,"");function n(){var u=v();if(t[e]==="?"){e++;var i=n();t[e]===":"&&e++;var k=n();return{type:"ter",cond:u,then:i,else:k}}return u}function v(){for(var u=o();t.slice(e,e+2)==="||";)e+=2,u={type:"bin",op:"||",left:u,right:o()};return u}function o(){for(var u=h();t.slice(e,e+2)==="&&";)e+=2,u={type:"bin",op:"&&",left:u,right:h()};return u}function h(){for(var u=m();t.slice(e,e+2)==="=="||t.slice(e,e+2)==="!=";){var i=t.slice(e,e+2);e+=2,u={type:"bin",op:i,left:u,right:m()}}return u}function m(){for(var u=a();t.slice(e,e+2)==="<="||t.slice(e,e+2)===">="||t[e]==="<"||t[e]===">";){var i=t.slice(e,e+2)==="<="||t.slice(e,e+2)===">="?t.slice(e,e+2):t[e];e+=i.length,u={type:"bin",op:i,left:u,right:a()}}return u}function a(){for(var u=l();t[e]==="+"||t[e]==="-";){var i=t[e];e++,u={type:"bin",op:i,left:u,right:l()}}return u}function l(){for(var u=g();t[e]==="*"||t[e]==="/"||t[e]==="%";){var i=t[e];e++,u={type:"bin",op:i,left:u,right:g()}}return u}function g(){return t[e]==="!"?(e++,{type:"un",op,arg:g()}):w()}function w(){if(t[e]==="("){e++;var u=n();return t[e]===")"&&e++,u}if(t[e]==="n")return e++,{type:"n"};var i=t.slice(e).match(/^[0-9]+/);return i?(e+=i[0].length,{type:"num",value:parseInt(i[0],10)}):{type:"num",value:0}}return n()},f=function(r,e){switch(r.type){case"num":return r.value;case"n":return e;case"un":return r.op==="!"?!f(r.arg,e):-f(r.arg,e);case"bin":{var t=f(r.left,e);if(r.op==="&&")return t&&f(r.right,e);if(r.op==="||")return t||f(r.right,e);var n=f(r.right,e);return r.op==="=="?Number(t)===Number(n):r.op==="!="?Number(t)!==Number(n):r.op==="<="?Number(t)<=Number(n):r.op===">="?Number(t)>=Number(n):r.op==="<"?Number(t)"?Number(t)>Number(n):r.op==="+"?Number(t)+Number(n):r.op==="-"?Number(t)-Number(n):r.op==="*"?Number(t)*Number(n):r.op==="/"?Number(n)!==0?Math.floor(Number(t)/Number(n)):0:r.op==="%"&&Number(n)!==0?Number(t)%Number(n):0}case"ter":return f(r.cond,e)?f(r.then,e):f(r.else,e)}return 0},P=function(r){var e=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),t=r.match(e);if(!t||t[0]!==r)throw new Error(N('The plural form "%1" is not valid',r));var n=r.match(/nplurals\s*=\s*([0-9]+)/),v=r.match(/plural\s*=\s*([^;]+)/),o=n?parseInt(n[1],10):2,h=v?v[1]:"n != 1",m=I(h);return function(a){var l=f(m,typeof a=="number"?a:Number(a)||0);return{nplurals:o,plural:l===!0?1:l?Number(l):0}}},S=function(r,e,t){if(!t.plural_form)return N.apply(this,[O(r[0])].concat(Array.prototype.slice.call(arguments,3)));var n;return t.plural_func?n=t.plural_func(e):(d[s]||(d[s]=P(_[s])),n=d[s](e)),(typeof n.plural>"u"||n.plural>n.nplurals||r.length<=n.plural)&&(n.plural=0),N.apply(this,[O(r[n.plural])].concat(Array.prototype.slice.call(arguments,3)))};return{strfmt:N,expand_locale:E,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(r,e,t,n){if(!r||!e||!t)throw new Error("You must provide a domain, a locale and messages");if(typeof r!="string"||typeof e!="string"||!A.isObject(t))throw new Error("Invalid arguments");return e=M(e),n&&(_[e]=n),p[r]||(p[r]={}),p[r][e]=t,this},loadJSON:function(r,e){if(A.isObject(r)||(r=JSON.parse(r)),!r[""]||!r[""].language||!r[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var t=r[""];return delete r[""],this.setMessages(e||y.domain,t.language,r,t["plural-forms"])},setLocale:function(r){return s=M(r),this},getLocale:function(){return s},textdomain:function(r){return r?(b=r,this):b},gettext:function(r){return this.dcnpgettext.apply(this,[void 0,void 0,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(r,e,t){return this.dcnpgettext.apply(this,[void 0,void 0,r,e,t].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(r,e){return this.dcnpgettext.apply(this,[void 0,r,e,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(r,e,t,n,v){if(r=r||b,typeof t!="string")throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',t));var o,h={plural_form:!1},m=e?e+x+t:t,a,l,g=E(s);for(var w in g)if(l=g[w],a=p[r]&&p[r][l]&&p[r][l][m],n?a=a&&typeof p[r][l][m]!="string":a=a&&typeof p[r][l][m]=="string",a)break;return a?o=p[r][l][m]:(o=t,h.plural_func=y.plural_func),n?(h.plural_form=!0,S.apply(this,[a?o:[t,n],v,h].concat(Array.prototype.slice.call(arguments,5)))):S.apply(this,[[o],v,h].concat(Array.prototype.slice.call(arguments,5)))}}};module.exports=i18n; -diff --git a/node_modules/gettext.js/dist/gettext.esm.js b/node_modules/gettext.js/dist/gettext.esm.js -index 416dc97..c53046d 100644 ---- a/node_modules/gettext.js/dist/gettext.esm.js -+++ b/node_modules/gettext.js/dist/gettext.esm.js -@@ -86,6 +86,138 @@ var i18n = function (options) { - return locale; - }; - -+ var parsePlural = function (expr) { -+ var pos = 0; -+ var str = expr.replace(/\s+/g, ''); -+ ++ var parsePluralExpr = function (str) { ++ var tokens = str.match(/[0-9]+|&&|\|\||==|!=|<=|>=|[()?:!+\-*\/%<>n]/g) || []; ++ var i = 0; + function parseTernary() { + var cond = parseOr(); -+ if (str[pos] === '?') { -+ pos++; -+ var then = parseTernary(); -+ if (str[pos] === ':') pos++; -+ var el = parseTernary(); -+ return { type: 'ter', cond: cond, then: then, else: el }; ++ if (tokens[i] === '?') { ++ i++; ++ var thenBranch = parseTernary(); ++ i++; // ':' ++ return { type: '?', cond: cond, thenBranch: thenBranch, elseBranch: parseTernary() }; + } + return cond; + } -+ + function parseOr() { + var node = parseAnd(); -+ while (str.slice(pos, pos + 2) === '||') { -+ pos += 2; -+ node = { type: 'bin', op: '||', left: node, right: parseAnd() }; -+ } ++ while (tokens[i] === '||') { i++; node = { op: '||', left: node, right: parseAnd() }; } + return node; + } -+ + function parseAnd() { + var node = parseEquality(); -+ while (str.slice(pos, pos + 2) === '&&') { -+ pos += 2; -+ node = { type: 'bin', op: '&&', left: node, right: parseEquality() }; -+ } ++ while (tokens[i] === '&&') { i++; node = { op: '&&', left: node, right: parseEquality() }; } + return node; + } -+ + function parseEquality() { + var node = parseRelational(); -+ while (str.slice(pos, pos + 2) === '==' || str.slice(pos, pos + 2) === '!=') { -+ var op = str.slice(pos, pos + 2); -+ pos += 2; -+ node = { type: 'bin', op: op, left: node, right: parseRelational() }; ++ while (tokens[i] === '==' || tokens[i] === '!=') { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parseRelational() }; + } + return node; + } -+ + function parseRelational() { -+ var node = parseAddSub(); -+ while (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=' || str[pos] === '<' || str[pos] === '>') { -+ var op = (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=') ? str.slice(pos, pos + 2) : str[pos]; -+ pos += op.length; -+ node = { type: 'bin', op: op, left: node, right: parseAddSub() }; ++ var node = parseAdd(); ++ while (/^[<>]=?$/.test(tokens[i])) { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parseAdd() }; + } + return node; + } -+ -+ function parseAddSub() { -+ var node = parseMulDivMod(); -+ while (str[pos] === '+' || str[pos] === '-') { -+ var op = str[pos]; -+ pos++; -+ node = { type: 'bin', op: op, left: node, right: parseMulDivMod() }; ++ function parseAdd() { ++ var node = parseMul(); ++ while (tokens[i] === '+' || tokens[i] === '-') { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parseMul() }; + } + return node; + } -+ -+ function parseMulDivMod() { -+ var node = parseUnary(); -+ while (str[pos] === '*' || str[pos] === '/' || str[pos] === '%') { -+ var op = str[pos]; -+ pos++; -+ node = { type: 'bin', op: op, left: node, right: parseUnary() }; ++ function parseMul() { ++ var node = parsePrimary(); ++ while (tokens[i] === '*' || tokens[i] === '/' || tokens[i] === '%') { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parsePrimary() }; + } + return node; + } -+ -+ function parseUnary() { -+ if (str[pos] === '!') { -+ pos++; -+ return { type: 'un', op: op, arg: parseUnary() }; -+ } -+ return parsePrimary(); -+ } -+ + function parsePrimary() { -+ if (str[pos] === '(') { -+ pos++; -+ var node = parseTernary(); -+ if (str[pos] === ')') pos++; -+ return node; -+ } -+ if (str[pos] === 'n') { -+ pos++; -+ return { type: 'n' }; -+ } -+ var m = str.slice(pos).match(/^[0-9]+/); -+ if (m) { -+ pos += m[0].length; -+ return { type: 'num', value: parseInt(m[0], 10) }; -+ } -+ return { type: 'num', value: 0 }; ++ if (tokens[i] === '!') { i++; return { op: '!', right: parsePrimary() }; } ++ if (tokens[i] === '(') { i++; var node = parseTernary(); i++; return node; } ++ if (tokens[i] === 'n') { i++; return 'n'; } ++ return Number(tokens[i++]) || 0; + } -+ + return parseTernary(); + }; + -+ var evalPluralNode = function (node, n) { -+ switch (node.type) { -+ case 'num': return node.value; -+ case 'n': return n; -+ case 'un': return node.op === '!' ? !evalPluralNode(node.arg, n) : -evalPluralNode(node.arg, n); -+ case 'bin': { -+ var l = evalPluralNode(node.left, n); -+ if (node.op === '&&') return l && evalPluralNode(node.right, n); -+ if (node.op === '||') return l || evalPluralNode(node.right, n); -+ var r = evalPluralNode(node.right, n); -+ if (node.op === '==') return Number(l) === Number(r); -+ if (node.op === '!=') return Number(l) !== Number(r); -+ if (node.op === '<=') return Number(l) <= Number(r); -+ if (node.op === '>=') return Number(l) >= Number(r); -+ if (node.op === '<') return Number(l) < Number(r); -+ if (node.op === '>') return Number(l) > Number(r); -+ if (node.op === '+') return Number(l) + Number(r); -+ if (node.op === '-') return Number(l) - Number(r); -+ if (node.op === '*') return Number(l) * Number(r); -+ if (node.op === '/') return Number(r) !== 0 ? Math.floor(Number(l) / Number(r)) : 0; -+ if (node.op === '%') return Number(r) !== 0 ? Number(l) % Number(r) : 0; -+ return 0; -+ } -+ case 'ter': return evalPluralNode(node.cond, n) ? evalPluralNode(node.then, n) : evalPluralNode(node.else, n); -+ } ++ var evalPluralExpr = function (node, n) { ++ if (node === 'n') return n; ++ if (typeof node === 'number') return node; ++ if (node.type === '?') return evalPluralExpr(node.cond, n) ? evalPluralExpr(node.thenBranch, n) : evalPluralExpr(node.elseBranch, n); ++ if (node.op === '!') return !evalPluralExpr(node.right, n); ++ var left = evalPluralExpr(node.left, n); ++ if (node.op === '&&') return left && evalPluralExpr(node.right, n); ++ if (node.op === '||') return left || evalPluralExpr(node.right, n); ++ var right = evalPluralExpr(node.right, n); ++ if (node.op === '==') return left == right; ++ if (node.op === '!=') return left != right; ++ if (node.op === '<=') return left <= right; ++ if (node.op === '>=') return left >= right; ++ if (node.op === '<') return left < right; ++ if (node.op === '>') return left > right; ++ if (node.op === '+') return left + right; ++ if (node.op === '-') return left - right; ++ if (node.op === '*') return left * right; ++ if (node.op === '/') return right ? Math.floor(left / right) : 0; ++ if (node.op === '%') return right ? left % right : 0; + return 0; + }; + var getPluralFunc = function (plural_form) { // Plural form string regexp // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js -@@ -97,13 +229,16 @@ var i18n = function (options) { +@@ -99,13 +186,14 @@ var i18n = function (options) { if (!match || match[0] !== plural_form) throw new Error(strfmt('The plural form "%1" is not valid', plural_form)); - console.log('>>> Plural form:', plural_form); -+ var npluralsMatch = plural_form.match(/nplurals\s*=\s*([0-9]+)/); -+ var pluralMatch = plural_form.match(/plural\s*=\s*([^;]+)/); -+ var nplurals = npluralsMatch ? parseInt(npluralsMatch[1], 10) : 2; -+ var expr = pluralMatch ? pluralMatch[1] : 'n != 1'; -+ var ast = parsePlural(expr); ++ var nplurals = parseInt((plural_form.match(/nplurals\s*=\s*([0-9]+)/) || [, 2])[1], 10); ++ var expr = (plural_form.match(/plural\s*=\s*([^;]+)/) || [, 'n != 1'])[1]; ++ var ast = parsePluralExpr(expr); - // Careful here, this is a hidden eval() equivalent.. - // Risk should be reasonable though since we test the plural_form through regex before @@ -333,166 +111,111 @@ index 416dc97..c53046d 100644 - // TODO: should test if https://github.com/soney/jsep present and use it if so - return new Function("n", 'var plural, nplurals; '+ plural_form +' return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };'); + return function (n) { -+ var res = evalPluralNode(ast, typeof n === 'number' ? n : Number(n) || 0); -+ return { nplurals: nplurals, plural: (res === true ? 1 : (res ? Number(res) : 0)) }; ++ var plural = evalPluralExpr(ast, typeof n === 'number' ? n : Number(n) || 0); ++ return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) }; + }; }; // Proper translation function that handle plurals and directives -diff --git a/node_modules/gettext.js/dist/gettext.esm.min.js b/node_modules/gettext.js/dist/gettext.esm.min.js -index e957ef1..35e4f0d 100644 ---- a/node_modules/gettext.js/dist/gettext.esm.min.js -+++ b/node_modules/gettext.js/dist/gettext.esm.min.js -@@ -1,2 +1 @@ --/*! gettext.js - Guillaume Potier - MIT Licensed */ --var i18n=function(t){t=t||{},this&&(this.__version="2.0.0");var r={domain:"messages",locale:"undefined"!=typeof document&&document.documentElement.getAttribute("lang")||"en",plural_func:function(t){return{nplurals:2,plural:1!=t?1:0}},ctxt_delimiter:String.fromCharCode(4)},e=function(t){var r=typeof t;return"function"===r||"object"===r&&!!t},n={},l=t.locale||r.locale,a=t.domain||r.domain,o={},i={},u=t.ctxt_delimiter||r.ctxt_delimiter;t.messages&&(o[a]={},o[a][l]=t.messages),t.plural_forms&&(i[l]=t.plural_forms);var s=function(t){var r=arguments;return t.replace(/%%/g,"%% ").replace(/%(\d+)/g,(function(t,e){return r[e]})).replace(/%% /g,"%")},p=function(t){return-1!==t.indexOf(u)?t.split(u)[1]:t},c=function(t){for(var r=[t],e=t.lastIndexOf("-");e>0;)t=t.slice(0,e),r.push(t),e=t.lastIndexOf("-");return r},f=function(t){var r=(t=t.replace("_","-")).search(/[.@]/);return-1!=r&&(t=t.slice(0,r)),t},d=function(t){var r=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),e=t.match(r);if(!e||e[0]!==t)throw new Error(s('The plural form "%1" is not valid',t));return console.log(">>> Plural form:",t),new Function("n","var plural, nplurals; "+t+" return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };")},g=function(t,r,e){return e.plural_form?(e.plural_func?a=e.plural_func(r):(n[l]||(n[l]=d(i[l])),a=n[l](r)),(void 0===a.plural||a.plural>a.nplurals||t.length<=a.plural)&&(a.plural=0),s.apply(this,[p(t[a.plural])].concat(Array.prototype.slice.call(arguments,3)))):s.apply(this,[p(t[0])].concat(Array.prototype.slice.call(arguments,3)));var a};return{strfmt:s,expand_locale:c,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(t,r,n,l){if(!t||!r||!n)throw new Error("You must provide a domain, a locale and messages");if("string"!=typeof t||"string"!=typeof r||!e(n))throw new Error("Invalid arguments");return r=f(r),l&&(i[r]=l),o[t]||(o[t]={}),o[t][r]=n,this},loadJSON:function(t,n){if(e(t)||(t=JSON.parse(t)),!t[""]||!t[""].language||!t[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var l=t[""];return delete t[""],this.setMessages(n||r.domain,l.language,t,l["plural-forms"])},setLocale:function(t){return l=f(t),this},getLocale:function(){return l},textdomain:function(t){return t?(a=t,this):a},gettext:function(t){return this.dcnpgettext.apply(this,[void 0,void 0,t,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(t,r,e){return this.dcnpgettext.apply(this,[void 0,void 0,t,r,e].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(t,r){return this.dcnpgettext.apply(this,[void 0,t,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(t,e,n,i,s){if(t=t||a,"string"!=typeof n)throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',n));var p,f,d,h={plural_form:!1},m=e?e+u+n:n,y=c(l);for(var v in y)if(d=y[v],f=o[t]&&o[t][d]&&o[t][d][m],f=i?f&&"string"!=typeof o[t][d][m]:f&&"string"==typeof o[t][d][m])break;return f?p=o[t][d][m]:(p=n,h.plural_func=r.plural_func),i?(h.plural_form=!0,g.apply(this,[f?p:[n,i],s,h].concat(Array.prototype.slice.call(arguments,5)))):g.apply(this,[[p],s,h].concat(Array.prototype.slice.call(arguments,5)))}}};export default i18n; -\ No newline at end of file -+/*! gettext.js - Guillaume Potier - MIT Licensed */var C=function(c){c=c||{},this&&(this.__version="2.0.0");var y={domain:"messages",locale:(typeof document<"u"?document.documentElement.getAttribute("lang"):!1)||"en",plural_func:function(r){return{nplurals:2,plural:r!=1?1:0}},ctxt_delimiter:""},A={isObject:function(r){var e=typeof r;return e==="function"||e==="object"&&!!r},isArray:function(r){return toString.call(r)==="[object Array]"}},d={},f=c.locale||y.locale,b=c.domain||y.domain,p={},_={},x=c.ctxt_delimiter||y.ctxt_delimiter;c.messages&&(p[b]={},p[b][f]=c.messages),c.plural_forms&&(_[f]=c.plural_forms);var N=function(r){var e=arguments;return r.replace(/%%/g,"%% ").replace(/%(\d+)/g,function(t,n){return e[n]}).replace(/%% /g,"%")},O=function(r){if(r.indexOf(x)!==-1){var e=r.split(x);return e[1]}return r},E=function(r){for(var e=[r],t=r.lastIndexOf("-");t>0;)r=r.slice(0,t),e.push(r),t=r.lastIndexOf("-");return e},M=function(r){r=r.replace("_","-");var e=r.search(/[.@]/);return e!=-1&&(r=r.slice(0,e)),r},I=function(r){var e=0,t=r.replace(/\s+/g,"");function n(){var u=m();if(t[e]==="?"){e++;var i=n();t[e]===":"&&e++;var k=n();return{type:"ter",cond:u,then:i,else:k}}return u}function m(){for(var u=o();t.slice(e,e+2)==="||";)e+=2,u={type:"bin",op:"||",left:u,right:o()};return u}function o(){for(var u=h();t.slice(e,e+2)==="&&";)e+=2,u={type:"bin",op:"&&",left:u,right:h()};return u}function h(){for(var u=v();t.slice(e,e+2)==="=="||t.slice(e,e+2)==="!=";){var i=t.slice(e,e+2);e+=2,u={type:"bin",op:i,left:u,right:v()}}return u}function v(){for(var u=a();t.slice(e,e+2)==="<="||t.slice(e,e+2)===">="||t[e]==="<"||t[e]===">";){var i=t.slice(e,e+2)==="<="||t.slice(e,e+2)===">="?t.slice(e,e+2):t[e];e+=i.length,u={type:"bin",op:i,left:u,right:a()}}return u}function a(){for(var u=l();t[e]==="+"||t[e]==="-";){var i=t[e];e++,u={type:"bin",op:i,left:u,right:l()}}return u}function l(){for(var u=g();t[e]==="*"||t[e]==="/"||t[e]==="%";){var i=t[e];e++,u={type:"bin",op:i,left:u,right:g()}}return u}function g(){return t[e]==="!"?(e++,{type:"un",op,arg:g()}):w()}function w(){if(t[e]==="("){e++;var u=n();return t[e]===")"&&e++,u}if(t[e]==="n")return e++,{type:"n"};var i=t.slice(e).match(/^[0-9]+/);return i?(e+=i[0].length,{type:"num",value:parseInt(i[0],10)}):{type:"num",value:0}}return n()},s=function(r,e){switch(r.type){case"num":return r.value;case"n":return e;case"un":return r.op==="!"?!s(r.arg,e):-s(r.arg,e);case"bin":{var t=s(r.left,e);if(r.op==="&&")return t&&s(r.right,e);if(r.op==="||")return t||s(r.right,e);var n=s(r.right,e);return r.op==="=="?Number(t)===Number(n):r.op==="!="?Number(t)!==Number(n):r.op==="<="?Number(t)<=Number(n):r.op===">="?Number(t)>=Number(n):r.op==="<"?Number(t)"?Number(t)>Number(n):r.op==="+"?Number(t)+Number(n):r.op==="-"?Number(t)-Number(n):r.op==="*"?Number(t)*Number(n):r.op==="/"?Number(n)!==0?Math.floor(Number(t)/Number(n)):0:r.op==="%"&&Number(n)!==0?Number(t)%Number(n):0}case"ter":return s(r.cond,e)?s(r.then,e):s(r.else,e)}return 0},P=function(r){var e=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),t=r.match(e);if(!t||t[0]!==r)throw new Error(N('The plural form "%1" is not valid',r));var n=r.match(/nplurals\s*=\s*([0-9]+)/),m=r.match(/plural\s*=\s*([^;]+)/),o=n?parseInt(n[1],10):2,h=m?m[1]:"n != 1",v=I(h);return function(a){var l=s(v,typeof a=="number"?a:Number(a)||0);return{nplurals:o,plural:l===!0?1:l?Number(l):0}}},S=function(r,e,t){if(!t.plural_form)return N.apply(this,[O(r[0])].concat(Array.prototype.slice.call(arguments,3)));var n;return t.plural_func?n=t.plural_func(e):(d[f]||(d[f]=P(_[f])),n=d[f](e)),(typeof n.plural>"u"||n.plural>n.nplurals||r.length<=n.plural)&&(n.plural=0),N.apply(this,[O(r[n.plural])].concat(Array.prototype.slice.call(arguments,3)))};return{strfmt:N,expand_locale:E,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(r,e,t,n){if(!r||!e||!t)throw new Error("You must provide a domain, a locale and messages");if(typeof r!="string"||typeof e!="string"||!A.isObject(t))throw new Error("Invalid arguments");return e=M(e),n&&(_[e]=n),p[r]||(p[r]={}),p[r][e]=t,this},loadJSON:function(r,e){if(A.isObject(r)||(r=JSON.parse(r)),!r[""]||!r[""].language||!r[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var t=r[""];return delete r[""],this.setMessages(e||y.domain,t.language,r,t["plural-forms"])},setLocale:function(r){return f=M(r),this},getLocale:function(){return f},textdomain:function(r){return r?(b=r,this):b},gettext:function(r){return this.dcnpgettext.apply(this,[void 0,void 0,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(r,e,t){return this.dcnpgettext.apply(this,[void 0,void 0,r,e,t].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(r,e){return this.dcnpgettext.apply(this,[void 0,r,e,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(r,e,t,n,m){if(r=r||b,typeof t!="string")throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',t));var o,h={plural_form:!1},v=e?e+x+t:t,a,l,g=E(f);for(var w in g)if(l=g[w],a=p[r]&&p[r][l]&&p[r][l][v],n?a=a&&typeof p[r][l][v]!="string":a=a&&typeof p[r][l][v]=="string",a)break;return a?o=p[r][l][v]:(o=t,h.plural_func=y.plural_func),n?(h.plural_form=!0,S.apply(this,[a?o:[t,n],m,h].concat(Array.prototype.slice.call(arguments,5)))):S.apply(this,[[o],m,h].concat(Array.prototype.slice.call(arguments,5)))}}};export default C; diff --git a/node_modules/gettext.js/lib/gettext.js b/node_modules/gettext.js/lib/gettext.js -index 88aaafc..85db7cc 100644 +index 88aaafc..7466a38 100644 --- a/node_modules/gettext.js/lib/gettext.js +++ b/node_modules/gettext.js/lib/gettext.js -@@ -86,6 +86,139 @@ var i18n = function (options) { +@@ -86,6 +86,93 @@ var i18n = function (options) { return locale; }; -+ var parsePlural = function (expr) { -+ var pos = 0; -+ var str = expr.replace(/\s+/g, ''); -+ ++ var parsePluralExpr = function (str) { ++ var tokens = str.match(/[0-9]+|&&|\|\||==|!=|<=|>=|[()?:!+\-*\/%<>n]/g) || []; ++ var i = 0; + function parseTernary() { + var cond = parseOr(); -+ if (str[pos] === '?') { -+ pos++; -+ var then = parseTernary(); -+ if (str[pos] === ':') pos++; -+ var el = parseTernary(); -+ return { type: 'ter', cond: cond, then: then, else: el }; ++ if (tokens[i] === '?') { ++ i++; ++ var thenBranch = parseTernary(); ++ i++; // ':' ++ return { type: '?', cond: cond, thenBranch: thenBranch, elseBranch: parseTernary() }; + } + return cond; + } -+ + function parseOr() { + var node = parseAnd(); -+ while (str.slice(pos, pos + 2) === '||') { -+ pos += 2; -+ node = { type: 'bin', op: '||', left: node, right: parseAnd() }; -+ } ++ while (tokens[i] === '||') { i++; node = { op: '||', left: node, right: parseAnd() }; } + return node; + } -+ + function parseAnd() { + var node = parseEquality(); -+ while (str.slice(pos, pos + 2) === '&&') { -+ pos += 2; -+ node = { type: 'bin', op: '&&', left: node, right: parseEquality() }; -+ } ++ while (tokens[i] === '&&') { i++; node = { op: '&&', left: node, right: parseEquality() }; } + return node; + } -+ + function parseEquality() { + var node = parseRelational(); -+ while (str.slice(pos, pos + 2) === '==' || str.slice(pos, pos + 2) === '!=') { -+ var op = str.slice(pos, pos + 2); -+ pos += 2; -+ node = { type: 'bin', op: op, left: node, right: parseRelational() }; ++ while (tokens[i] === '==' || tokens[i] === '!=') { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parseRelational() }; + } + return node; + } -+ + function parseRelational() { -+ var node = parseAddSub(); -+ while (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=' || str[pos] === '<' || str[pos] === '>') { -+ var op = (str.slice(pos, pos + 2) === '<=' || str.slice(pos, pos + 2) === '>=') ? str.slice(pos, pos + 2) : str[pos]; -+ pos += op.length; -+ node = { type: 'bin', op: op, left: node, right: parseAddSub() }; ++ var node = parseAdd(); ++ while (/^[<>]=?$/.test(tokens[i])) { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parseAdd() }; + } + return node; + } -+ -+ function parseAddSub() { -+ var node = parseMulDivMod(); -+ while (str[pos] === '+' || str[pos] === '-') { -+ var op = str[pos]; -+ pos++; -+ node = { type: 'bin', op: op, left: node, right: parseMulDivMod() }; ++ function parseAdd() { ++ var node = parseMul(); ++ while (tokens[i] === '+' || tokens[i] === '-') { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parseMul() }; + } + return node; + } -+ -+ function parseMulDivMod() { -+ var node = parseUnary(); -+ while (str[pos] === '*' || str[pos] === '/' || str[pos] === '%') { -+ var op = str[pos]; -+ pos++; -+ node = { type: 'bin', op: op, left: node, right: parseUnary() }; ++ function parseMul() { ++ var node = parsePrimary(); ++ while (tokens[i] === '*' || tokens[i] === '/' || tokens[i] === '%') { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parsePrimary() }; + } + return node; + } -+ -+ function parseUnary() { -+ if (str[pos] === '!' || str[pos] === '+' || str[pos] === '-') { -+ var op = str[pos]; -+ pos++; -+ return { type: 'un', op: op, arg: parseUnary() }; -+ } -+ return parsePrimary(); -+ } -+ + function parsePrimary() { -+ if (str[pos] === '(') { -+ pos++; -+ var node = parseTernary(); -+ if (str[pos] === ')') pos++; -+ return node; -+ } -+ if (str[pos] === 'n') { -+ pos++; -+ return { type: 'n' }; -+ } -+ var m = str.slice(pos).match(/^[0-9]+/); -+ if (m) { -+ pos += m[0].length; -+ return { type: 'num', value: parseInt(m[0], 10) }; -+ } -+ return { type: 'num', value: 0 }; ++ if (tokens[i] === '!') { i++; return { op: '!', right: parsePrimary() }; } ++ if (tokens[i] === '(') { i++; var node = parseTernary(); i++; return node; } ++ if (tokens[i] === 'n') { i++; return 'n'; } ++ return Number(tokens[i++]) || 0; + } -+ + return parseTernary(); + }; + -+ var evalPluralNode = function (node, n) { -+ switch (node.type) { -+ case 'num': return node.value; -+ case 'n': return n; -+ case 'un': return node.op === '!' ? !evalPluralNode(node.arg, n) : -evalPluralNode(node.arg, n); -+ case 'bin': { -+ var l = evalPluralNode(node.left, n); -+ if (node.op === '&&') return l && evalPluralNode(node.right, n); -+ if (node.op === '||') return l || evalPluralNode(node.right, n); -+ var r = evalPluralNode(node.right, n); -+ if (node.op === '==') return Number(l) === Number(r); -+ if (node.op === '!=') return Number(l) !== Number(r); -+ if (node.op === '<=') return Number(l) <= Number(r); -+ if (node.op === '>=') return Number(l) >= Number(r); -+ if (node.op === '<') return Number(l) < Number(r); -+ if (node.op === '>') return Number(l) > Number(r); -+ if (node.op === '+') return Number(l) + Number(r); -+ if (node.op === '-') return Number(l) - Number(r); -+ if (node.op === '*') return Number(l) * Number(r); -+ if (node.op === '/') return Number(r) !== 0 ? Math.floor(Number(l) / Number(r)) : 0; -+ if (node.op === '%') return Number(r) !== 0 ? Number(l) % Number(r) : 0; -+ return 0; -+ } -+ case 'ter': return evalPluralNode(node.cond, n) ? evalPluralNode(node.then, n) : evalPluralNode(node.else, n); -+ } ++ var evalPluralExpr = function (node, n) { ++ if (node === 'n') return n; ++ if (typeof node === 'number') return node; ++ if (node.type === '?') return evalPluralExpr(node.cond, n) ? evalPluralExpr(node.thenBranch, n) : evalPluralExpr(node.elseBranch, n); ++ if (node.op === '!') return !evalPluralExpr(node.right, n); ++ var left = evalPluralExpr(node.left, n); ++ if (node.op === '&&') return left && evalPluralExpr(node.right, n); ++ if (node.op === '||') return left || evalPluralExpr(node.right, n); ++ var right = evalPluralExpr(node.right, n); ++ if (node.op === '==') return left == right; ++ if (node.op === '!=') return left != right; ++ if (node.op === '<=') return left <= right; ++ if (node.op === '>=') return left >= right; ++ if (node.op === '<') return left < right; ++ if (node.op === '>') return left > right; ++ if (node.op === '+') return left + right; ++ if (node.op === '-') return left - right; ++ if (node.op === '*') return left * right; ++ if (node.op === '/') return right ? Math.floor(left / right) : 0; ++ if (node.op === '%') return right ? left % right : 0; + return 0; + }; + var getPluralFunc = function (plural_form) { // Plural form string regexp // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js -@@ -97,11 +230,16 @@ var i18n = function (options) { +@@ -97,11 +184,14 @@ var i18n = function (options) { if (!match || match[0] !== plural_form) throw new Error(strfmt('The plural form "%1" is not valid', plural_form)); @@ -501,15 +224,13 @@ index 88aaafc..85db7cc 100644 - // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js - // TODO: should test if https://github.com/soney/jsep present and use it if so - return new Function("n", 'var plural, nplurals; '+ plural_form +' return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };'); -+ var npluralsMatch = plural_form.match(/nplurals\s*=\s*([0-9]+)/); -+ var pluralMatch = plural_form.match(/plural\s*=\s*([^;]+)/); -+ var nplurals = npluralsMatch ? parseInt(npluralsMatch[1], 10) : 2; -+ var expr = pluralMatch ? pluralMatch[1] : 'n != 1'; -+ var ast = parsePlural(expr); ++ var nplurals = parseInt((plural_form.match(/nplurals\s*=\s*([0-9]+)/) || [, 2])[1], 10); ++ var expr = (plural_form.match(/plural\s*=\s*([^;]+)/) || [, 'n != 1'])[1]; ++ var ast = parsePluralExpr(expr); + + return function (n) { -+ var res = evalPluralNode(ast, typeof n === 'number' ? n : Number(n) || 0); -+ return { nplurals: nplurals, plural: (res === true ? 1 : (res ? Number(res) : 0)) }; ++ var plural = evalPluralExpr(ast, typeof n === 'number' ? n : Number(n) || 0); ++ return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) }; + }; }; From 73c972cad06250a11cb844b5e8b32fb90daa7d1b Mon Sep 17 00:00:00 2001 From: Logan Rasmussen Date: Fri, 11 Sep 2026 11:11:50 -0600 Subject: [PATCH 4/4] fix(i18n): retain original package entrypoint and include minified distribution in patch --- patches/gettext.js+2.0.3.patch | 137 +++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/patches/gettext.js+2.0.3.patch b/patches/gettext.js+2.0.3.patch index 36c3834c3f..dddd6d100b 100644 --- a/patches/gettext.js+2.0.3.patch +++ b/patches/gettext.js+2.0.3.patch @@ -117,6 +117,143 @@ index 85c83de..01b22ab 100644 }; // Proper translation function that handle plurals and directives +diff --git a/node_modules/gettext.js/dist/gettext.cjs.min.js b/node_modules/gettext.js/dist/gettext.cjs.min.js +index 9f5f2e6..ef7d73e 100644 +--- a/node_modules/gettext.js/dist/gettext.cjs.min.js ++++ b/node_modules/gettext.js/dist/gettext.cjs.min.js +@@ -1,2 +1 @@ +-"use strict"; +-/*! gettext.js - Guillaume Potier - MIT Licensed */var i18n=function(t){t=t||{},this&&(this.__version="2.0.0");var r={domain:"messages",locale:"undefined"!=typeof document&&document.documentElement.getAttribute("lang")||"en",plural_func:function(t){return{nplurals:2,plural:1!=t?1:0}},ctxt_delimiter:String.fromCharCode(4)},e=function(t){var r=typeof t;return"function"===r||"object"===r&&!!t},n={},l=t.locale||r.locale,a=t.domain||r.domain,o={},i={},u=t.ctxt_delimiter||r.ctxt_delimiter;t.messages&&(o[a]={},o[a][l]=t.messages),t.plural_forms&&(i[l]=t.plural_forms);var s=function(t){var r=arguments;return t.replace(/%%/g,"%% ").replace(/%(\d+)/g,(function(t,e){return r[e]})).replace(/%% /g,"%")},p=function(t){return-1!==t.indexOf(u)?t.split(u)[1]:t},c=function(t){for(var r=[t],e=t.lastIndexOf("-");e>0;)t=t.slice(0,e),r.push(t),e=t.lastIndexOf("-");return r},f=function(t){var r=(t=t.replace("_","-")).search(/[.@]/);return-1!=r&&(t=t.slice(0,r)),t},d=function(t){var r=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),e=t.match(r);if(!e||e[0]!==t)throw new Error(s('The plural form "%1" is not valid',t));return console.log(">>> Plural form:",t),new Function("n","var plural, nplurals; "+t+" return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };")},g=function(t,r,e){return e.plural_form?(e.plural_func?a=e.plural_func(r):(n[l]||(n[l]=d(i[l])),a=n[l](r)),(void 0===a.plural||a.plural>a.nplurals||t.length<=a.plural)&&(a.plural=0),s.apply(this,[p(t[a.plural])].concat(Array.prototype.slice.call(arguments,3)))):s.apply(this,[p(t[0])].concat(Array.prototype.slice.call(arguments,3)));var a};return{strfmt:s,expand_locale:c,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(t,r,n,l){if(!t||!r||!n)throw new Error("You must provide a domain, a locale and messages");if("string"!=typeof t||"string"!=typeof r||!e(n))throw new Error("Invalid arguments");return r=f(r),l&&(i[r]=l),o[t]||(o[t]={}),o[t][r]=n,this},loadJSON:function(t,n){if(e(t)||(t=JSON.parse(t)),!t[""]||!t[""].language||!t[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var l=t[""];return delete t[""],this.setMessages(n||r.domain,l.language,t,l["plural-forms"])},setLocale:function(t){return l=f(t),this},getLocale:function(){return l},textdomain:function(t){return t?(a=t,this):a},gettext:function(t){return this.dcnpgettext.apply(this,[void 0,void 0,t,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(t,r,e){return this.dcnpgettext.apply(this,[void 0,void 0,t,r,e].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(t,r){return this.dcnpgettext.apply(this,[void 0,t,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(t,e,n,i,s){if(t=t||a,"string"!=typeof n)throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',n));var p,f,d,m={plural_form:!1},h=e?e+u+n:n,y=c(l);for(var v in y)if(d=y[v],f=o[t]&&o[t][d]&&o[t][d][h],f=i?f&&"string"!=typeof o[t][d][h]:f&&"string"==typeof o[t][d][h])break;return f?p=o[t][d][h]:(p=n,m.plural_func=r.plural_func),i?(m.plural_form=!0,g.apply(this,[f?p:[n,i],s,m].concat(Array.prototype.slice.call(arguments,5)))):g.apply(this,[[p],s,m].concat(Array.prototype.slice.call(arguments,5)))}}};module.exports=i18n; +\ No newline at end of file ++"use strict";/*! gettext.js - Guillaume Potier - MIT Licensed */var i18n=function(s){s=s||{},this&&(this.__version="2.0.0");var m={domain:"messages",locale:(typeof document<"u"?document.documentElement.getAttribute("lang"):!1)||"en",plural_func:function(r){return{nplurals:2,plural:r!=1?1:0}},ctxt_delimiter:""},b={isObject:function(r){var e=typeof r;return e==="function"||e==="object"&&!!r},isArray:function(r){return toString.call(r)==="[object Array]"}},_={},p=s.locale||m.locale,d=s.domain||m.domain,a={},w={},A=s.ctxt_delimiter||m.ctxt_delimiter;s.messages&&(a[d]={},a[d][p]=s.messages),s.plural_forms&&(w[p]=s.plural_forms);var x=function(r){var e=arguments;return r.replace(/%%/g,"%% ").replace(/%(\d+)/g,function(t,n){return e[n]}).replace(/%% /g,"%")},E=function(r){if(r.indexOf(A)!==-1){var e=r.split(A);return e[1]}return r},O=function(r){for(var e=[r],t=r.lastIndexOf("-");t>0;)r=r.slice(0,t),e.push(r),t=r.lastIndexOf("-");return e},M=function(r){r=r.replace("_","-");var e=r.search(/[.@]/);return e!=-1&&(r=r.slice(0,e)),r},S=function(r){var e=r.match(/[0-9]+|&&|\|\||==|!=|<=|>=|[()?:!+\-*\/%<>n]/g)||[],t=0;function n(){var u=v();if(e[t]==="?"){t++;var g=n();return t++,{type:"?",cond:u,thenBranch:g,elseBranch:n()}}return u}function v(){for(var u=o();e[t]==="||";)t++,u={op:"||",left:u,right:o()};return u}function o(){for(var u=i();e[t]==="&&";)t++,u={op:"&&",left:u,right:i()};return u}function i(){for(var u=l();e[t]==="=="||e[t]==="!=";){var g=e[t++];u={op:g,left:u,right:l()}}return u}function l(){for(var u=f();/^[<>]=?$/.test(e[t]);){var g=e[t++];u={op:g,left:u,right:f()}}return u}function f(){for(var u=h();e[t]==="+"||e[t]==="-";){var g=e[t++];u={op:g,left:u,right:h()}}return u}function h(){for(var u=y();e[t]==="*"||e[t]==="/"||e[t]==="%";){var g=e[t++];u={op:g,left:u,right:y()}}return u}function y(){if(e[t]==="!")return t++,{op:"!",right:y()};if(e[t]==="("){t++;var u=n();return t++,u}return e[t]==="n"?(t++,"n"):Number(e[t++])||0}return n()},c=function(r,e){if(r==="n")return e;if(typeof r=="number")return r;if(r.type==="?")return c(r.cond,e)?c(r.thenBranch,e):c(r.elseBranch,e);if(r.op==="!")return!c(r.right,e);var t=c(r.left,e);if(r.op==="&&")return t&&c(r.right,e);if(r.op==="||")return t||c(r.right,e);var n=c(r.right,e);return r.op==="=="?t==n:r.op==="!="?t!=n:r.op==="<="?t<=n:r.op===">="?t>=n:r.op==="<"?t"?t>n:r.op==="+"?t+n:r.op==="-"?t-n:r.op==="*"?t*n:r.op==="/"?n?Math.floor(t/n):0:r.op==="%"&&n?t%n:0},k=function(r){var e=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),t=r.match(e);if(!t||t[0]!==r)throw new Error(x('The plural form "%1" is not valid',r));var n=parseInt((r.match(/nplurals\s*=\s*([0-9]+)/)||[,2])[1],10),v=(r.match(/plural\s*=\s*([^;]+)/)||[,"n != 1"])[1],o=S(v);return function(i){var l=c(o,typeof i=="number"?i:Number(i)||0);return{nplurals:n,plural:l===!0?1:l||0}}},N=function(r,e,t){if(!t.plural_form)return x.apply(this,[E(r[0])].concat(Array.prototype.slice.call(arguments,3)));var n;return t.plural_func?n=t.plural_func(e):(_[p]||(_[p]=k(w[p])),n=_[p](e)),(typeof n.plural>"u"||n.plural>n.nplurals||r.length<=n.plural)&&(n.plural=0),x.apply(this,[E(r[n.plural])].concat(Array.prototype.slice.call(arguments,3)))};return{strfmt:x,expand_locale:O,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(r,e,t,n){if(!r||!e||!t)throw new Error("You must provide a domain, a locale and messages");if(typeof r!="string"||typeof e!="string"||!b.isObject(t))throw new Error("Invalid arguments");return e=M(e),n&&(w[e]=n),a[r]||(a[r]={}),a[r][e]=t,this},loadJSON:function(r,e){if(b.isObject(r)||(r=JSON.parse(r)),!r[""]||!r[""].language||!r[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var t=r[""];return delete r[""],this.setMessages(e||m.domain,t.language,r,t["plural-forms"])},setLocale:function(r){return p=M(r),this},getLocale:function(){return p},textdomain:function(r){return r?(d=r,this):d},gettext:function(r){return this.dcnpgettext.apply(this,[void 0,void 0,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(r,e,t){return this.dcnpgettext.apply(this,[void 0,void 0,r,e,t].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(r,e){return this.dcnpgettext.apply(this,[void 0,r,e,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(r,e,t,n,v){if(r=r||d,typeof t!="string")throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',t));var o,i={plural_form:!1},l=e?e+A+t:t,f,h,y=O(p);for(var u in y)if(h=y[u],f=a[r]&&a[r][h]&&a[r][h][l],n?f=f&&typeof a[r][h][l]!="string":f=f&&typeof a[r][h][l]=="string",f)break;return f?o=a[r][h][l]:(o=t,i.plural_func=m.plural_func),n?(i.plural_form=!0,N.apply(this,[f?o:[t,n],v,i].concat(Array.prototype.slice.call(arguments,5)))):N.apply(this,[[o],v,i].concat(Array.prototype.slice.call(arguments,5)))}}};module.exports=i18n; +diff --git a/node_modules/gettext.js/dist/gettext.esm.js b/node_modules/gettext.js/dist/gettext.esm.js +index 416dc97..7466a38 100644 +--- a/node_modules/gettext.js/dist/gettext.esm.js ++++ b/node_modules/gettext.js/dist/gettext.esm.js +@@ -86,6 +86,93 @@ var i18n = function (options) { + return locale; + }; + ++ var parsePluralExpr = function (str) { ++ var tokens = str.match(/[0-9]+|&&|\|\||==|!=|<=|>=|[()?:!+\-*\/%<>n]/g) || []; ++ var i = 0; ++ function parseTernary() { ++ var cond = parseOr(); ++ if (tokens[i] === '?') { ++ i++; ++ var thenBranch = parseTernary(); ++ i++; // ':' ++ return { type: '?', cond: cond, thenBranch: thenBranch, elseBranch: parseTernary() }; ++ } ++ return cond; ++ } ++ function parseOr() { ++ var node = parseAnd(); ++ while (tokens[i] === '||') { i++; node = { op: '||', left: node, right: parseAnd() }; } ++ return node; ++ } ++ function parseAnd() { ++ var node = parseEquality(); ++ while (tokens[i] === '&&') { i++; node = { op: '&&', left: node, right: parseEquality() }; } ++ return node; ++ } ++ function parseEquality() { ++ var node = parseRelational(); ++ while (tokens[i] === '==' || tokens[i] === '!=') { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parseRelational() }; ++ } ++ return node; ++ } ++ function parseRelational() { ++ var node = parseAdd(); ++ while (/^[<>]=?$/.test(tokens[i])) { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parseAdd() }; ++ } ++ return node; ++ } ++ function parseAdd() { ++ var node = parseMul(); ++ while (tokens[i] === '+' || tokens[i] === '-') { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parseMul() }; ++ } ++ return node; ++ } ++ function parseMul() { ++ var node = parsePrimary(); ++ while (tokens[i] === '*' || tokens[i] === '/' || tokens[i] === '%') { ++ var op = tokens[i++]; ++ node = { op: op, left: node, right: parsePrimary() }; ++ } ++ return node; ++ } ++ function parsePrimary() { ++ if (tokens[i] === '!') { i++; return { op: '!', right: parsePrimary() }; } ++ if (tokens[i] === '(') { i++; var node = parseTernary(); i++; return node; } ++ if (tokens[i] === 'n') { i++; return 'n'; } ++ return Number(tokens[i++]) || 0; ++ } ++ return parseTernary(); ++ }; ++ ++ var evalPluralExpr = function (node, n) { ++ if (node === 'n') return n; ++ if (typeof node === 'number') return node; ++ if (node.type === '?') return evalPluralExpr(node.cond, n) ? evalPluralExpr(node.thenBranch, n) : evalPluralExpr(node.elseBranch, n); ++ if (node.op === '!') return !evalPluralExpr(node.right, n); ++ var left = evalPluralExpr(node.left, n); ++ if (node.op === '&&') return left && evalPluralExpr(node.right, n); ++ if (node.op === '||') return left || evalPluralExpr(node.right, n); ++ var right = evalPluralExpr(node.right, n); ++ if (node.op === '==') return left == right; ++ if (node.op === '!=') return left != right; ++ if (node.op === '<=') return left <= right; ++ if (node.op === '>=') return left >= right; ++ if (node.op === '<') return left < right; ++ if (node.op === '>') return left > right; ++ if (node.op === '+') return left + right; ++ if (node.op === '-') return left - right; ++ if (node.op === '*') return left * right; ++ if (node.op === '/') return right ? Math.floor(left / right) : 0; ++ if (node.op === '%') return right ? left % right : 0; ++ return 0; ++ }; ++ + var getPluralFunc = function (plural_form) { + // Plural form string regexp + // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js +@@ -97,13 +184,14 @@ var i18n = function (options) { + if (!match || match[0] !== plural_form) + throw new Error(strfmt('The plural form "%1" is not valid', plural_form)); + +- console.log('>>> Plural form:', plural_form); ++ var nplurals = parseInt((plural_form.match(/nplurals\s*=\s*([0-9]+)/) || [, 2])[1], 10); ++ var expr = (plural_form.match(/plural\s*=\s*([^;]+)/) || [, 'n != 1'])[1]; ++ var ast = parsePluralExpr(expr); + +- // Careful here, this is a hidden eval() equivalent.. +- // Risk should be reasonable though since we test the plural_form through regex before +- // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js +- // TODO: should test if https://github.com/soney/jsep present and use it if so +- return new Function("n", 'var plural, nplurals; '+ plural_form +' return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };'); ++ return function (n) { ++ var plural = evalPluralExpr(ast, typeof n === 'number' ? n : Number(n) || 0); ++ return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) }; ++ }; + }; + + // Proper translation function that handle plurals and directives +diff --git a/node_modules/gettext.js/dist/gettext.esm.min.js b/node_modules/gettext.js/dist/gettext.esm.min.js +index e957ef1..286e3b5 100644 +--- a/node_modules/gettext.js/dist/gettext.esm.min.js ++++ b/node_modules/gettext.js/dist/gettext.esm.min.js +@@ -1,2 +1 @@ +-/*! gettext.js - Guillaume Potier - MIT Licensed */ +-var i18n=function(t){t=t||{},this&&(this.__version="2.0.0");var r={domain:"messages",locale:"undefined"!=typeof document&&document.documentElement.getAttribute("lang")||"en",plural_func:function(t){return{nplurals:2,plural:1!=t?1:0}},ctxt_delimiter:String.fromCharCode(4)},e=function(t){var r=typeof t;return"function"===r||"object"===r&&!!t},n={},l=t.locale||r.locale,a=t.domain||r.domain,o={},i={},u=t.ctxt_delimiter||r.ctxt_delimiter;t.messages&&(o[a]={},o[a][l]=t.messages),t.plural_forms&&(i[l]=t.plural_forms);var s=function(t){var r=arguments;return t.replace(/%%/g,"%% ").replace(/%(\d+)/g,(function(t,e){return r[e]})).replace(/%% /g,"%")},p=function(t){return-1!==t.indexOf(u)?t.split(u)[1]:t},c=function(t){for(var r=[t],e=t.lastIndexOf("-");e>0;)t=t.slice(0,e),r.push(t),e=t.lastIndexOf("-");return r},f=function(t){var r=(t=t.replace("_","-")).search(/[.@]/);return-1!=r&&(t=t.slice(0,r)),t},d=function(t){var r=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),e=t.match(r);if(!e||e[0]!==t)throw new Error(s('The plural form "%1" is not valid',t));return console.log(">>> Plural form:",t),new Function("n","var plural, nplurals; "+t+" return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };")},g=function(t,r,e){return e.plural_form?(e.plural_func?a=e.plural_func(r):(n[l]||(n[l]=d(i[l])),a=n[l](r)),(void 0===a.plural||a.plural>a.nplurals||t.length<=a.plural)&&(a.plural=0),s.apply(this,[p(t[a.plural])].concat(Array.prototype.slice.call(arguments,3)))):s.apply(this,[p(t[0])].concat(Array.prototype.slice.call(arguments,3)));var a};return{strfmt:s,expand_locale:c,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(t,r,n,l){if(!t||!r||!n)throw new Error("You must provide a domain, a locale and messages");if("string"!=typeof t||"string"!=typeof r||!e(n))throw new Error("Invalid arguments");return r=f(r),l&&(i[r]=l),o[t]||(o[t]={}),o[t][r]=n,this},loadJSON:function(t,n){if(e(t)||(t=JSON.parse(t)),!t[""]||!t[""].language||!t[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var l=t[""];return delete t[""],this.setMessages(n||r.domain,l.language,t,l["plural-forms"])},setLocale:function(t){return l=f(t),this},getLocale:function(){return l},textdomain:function(t){return t?(a=t,this):a},gettext:function(t){return this.dcnpgettext.apply(this,[void 0,void 0,t,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(t,r,e){return this.dcnpgettext.apply(this,[void 0,void 0,t,r,e].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(t,r){return this.dcnpgettext.apply(this,[void 0,t,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(t,e,n,i,s){if(t=t||a,"string"!=typeof n)throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',n));var p,f,d,h={plural_form:!1},m=e?e+u+n:n,y=c(l);for(var v in y)if(d=y[v],f=o[t]&&o[t][d]&&o[t][d][m],f=i?f&&"string"!=typeof o[t][d][m]:f&&"string"==typeof o[t][d][m])break;return f?p=o[t][d][m]:(p=n,h.plural_func=r.plural_func),i?(h.plural_form=!0,g.apply(this,[f?p:[n,i],s,h].concat(Array.prototype.slice.call(arguments,5)))):g.apply(this,[[p],s,h].concat(Array.prototype.slice.call(arguments,5)))}}};export default i18n; +\ No newline at end of file ++/*! gettext.js - Guillaume Potier - MIT Licensed */var B=function(s){s=s||{},this&&(this.__version="2.0.0");var m={domain:"messages",locale:(typeof document<"u"?document.documentElement.getAttribute("lang"):!1)||"en",plural_func:function(r){return{nplurals:2,plural:r!=1?1:0}},ctxt_delimiter:""},b={isObject:function(r){var e=typeof r;return e==="function"||e==="object"&&!!r},isArray:function(r){return toString.call(r)==="[object Array]"}},_={},p=s.locale||m.locale,d=s.domain||m.domain,a={},w={},A=s.ctxt_delimiter||m.ctxt_delimiter;s.messages&&(a[d]={},a[d][p]=s.messages),s.plural_forms&&(w[p]=s.plural_forms);var x=function(r){var e=arguments;return r.replace(/%%/g,"%% ").replace(/%(\d+)/g,function(t,n){return e[n]}).replace(/%% /g,"%")},E=function(r){if(r.indexOf(A)!==-1){var e=r.split(A);return e[1]}return r},O=function(r){for(var e=[r],t=r.lastIndexOf("-");t>0;)r=r.slice(0,t),e.push(r),t=r.lastIndexOf("-");return e},M=function(r){r=r.replace("_","-");var e=r.search(/[.@]/);return e!=-1&&(r=r.slice(0,e)),r},S=function(r){var e=r.match(/[0-9]+|&&|\|\||==|!=|<=|>=|[()?:!+\-*\/%<>n]/g)||[],t=0;function n(){var u=v();if(e[t]==="?"){t++;var g=n();return t++,{type:"?",cond:u,thenBranch:g,elseBranch:n()}}return u}function v(){for(var u=o();e[t]==="||";)t++,u={op:"||",left:u,right:o()};return u}function o(){for(var u=i();e[t]==="&&";)t++,u={op:"&&",left:u,right:i()};return u}function i(){for(var u=l();e[t]==="=="||e[t]==="!=";){var g=e[t++];u={op:g,left:u,right:l()}}return u}function l(){for(var u=f();/^[<>]=?$/.test(e[t]);){var g=e[t++];u={op:g,left:u,right:f()}}return u}function f(){for(var u=h();e[t]==="+"||e[t]==="-";){var g=e[t++];u={op:g,left:u,right:h()}}return u}function h(){for(var u=y();e[t]==="*"||e[t]==="/"||e[t]==="%";){var g=e[t++];u={op:g,left:u,right:y()}}return u}function y(){if(e[t]==="!")return t++,{op:"!",right:y()};if(e[t]==="("){t++;var u=n();return t++,u}return e[t]==="n"?(t++,"n"):Number(e[t++])||0}return n()},c=function(r,e){if(r==="n")return e;if(typeof r=="number")return r;if(r.type==="?")return c(r.cond,e)?c(r.thenBranch,e):c(r.elseBranch,e);if(r.op==="!")return!c(r.right,e);var t=c(r.left,e);if(r.op==="&&")return t&&c(r.right,e);if(r.op==="||")return t||c(r.right,e);var n=c(r.right,e);return r.op==="=="?t==n:r.op==="!="?t!=n:r.op==="<="?t<=n:r.op===">="?t>=n:r.op==="<"?t"?t>n:r.op==="+"?t+n:r.op==="-"?t-n:r.op==="*"?t*n:r.op==="/"?n?Math.floor(t/n):0:r.op==="%"&&n?t%n:0},k=function(r){var e=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+"),t=r.match(e);if(!t||t[0]!==r)throw new Error(x('The plural form "%1" is not valid',r));var n=parseInt((r.match(/nplurals\s*=\s*([0-9]+)/)||[,2])[1],10),v=(r.match(/plural\s*=\s*([^;]+)/)||[,"n != 1"])[1],o=S(v);return function(i){var l=c(o,typeof i=="number"?i:Number(i)||0);return{nplurals:n,plural:l===!0?1:l||0}}},N=function(r,e,t){if(!t.plural_form)return x.apply(this,[E(r[0])].concat(Array.prototype.slice.call(arguments,3)));var n;return t.plural_func?n=t.plural_func(e):(_[p]||(_[p]=k(w[p])),n=_[p](e)),(typeof n.plural>"u"||n.plural>n.nplurals||r.length<=n.plural)&&(n.plural=0),x.apply(this,[E(r[n.plural])].concat(Array.prototype.slice.call(arguments,3)))};return{strfmt:x,expand_locale:O,__:function(){return this.gettext.apply(this,arguments)},_n:function(){return this.ngettext.apply(this,arguments)},_p:function(){return this.pgettext.apply(this,arguments)},setMessages:function(r,e,t,n){if(!r||!e||!t)throw new Error("You must provide a domain, a locale and messages");if(typeof r!="string"||typeof e!="string"||!b.isObject(t))throw new Error("Invalid arguments");return e=M(e),n&&(w[e]=n),a[r]||(a[r]={}),a[r][e]=t,this},loadJSON:function(r,e){if(b.isObject(r)||(r=JSON.parse(r)),!r[""]||!r[""].language||!r[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var t=r[""];return delete r[""],this.setMessages(e||m.domain,t.language,r,t["plural-forms"])},setLocale:function(r){return p=M(r),this},getLocale:function(){return p},textdomain:function(r){return r?(d=r,this):d},gettext:function(r){return this.dcnpgettext.apply(this,[void 0,void 0,r,void 0,void 0].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(r,e,t){return this.dcnpgettext.apply(this,[void 0,void 0,r,e,t].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(r,e){return this.dcnpgettext.apply(this,[void 0,r,e,void 0,void 0].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(r,e,t,n,v){if(r=r||d,typeof t!="string")throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',t));var o,i={plural_form:!1},l=e?e+A+t:t,f,h,y=O(p);for(var u in y)if(h=y[u],f=a[r]&&a[r][h]&&a[r][h][l],n?f=f&&typeof a[r][h][l]!="string":f=f&&typeof a[r][h][l]=="string",f)break;return f?o=a[r][h][l]:(o=t,i.plural_func=m.plural_func),n?(i.plural_form=!0,N.apply(this,[f?o:[t,n],v,i].concat(Array.prototype.slice.call(arguments,5)))):N.apply(this,[[o],v,i].concat(Array.prototype.slice.call(arguments,5)))}}};export default B; diff --git a/node_modules/gettext.js/lib/gettext.js b/node_modules/gettext.js/lib/gettext.js index 88aaafc..7466a38 100644 --- a/node_modules/gettext.js/lib/gettext.js