@ultraq/icu-message-formatter 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +5 -6
  3. package/dist/icu-message-formatter.browser.es.min.js +2 -0
  4. package/dist/icu-message-formatter.browser.es.min.js.map +1 -0
  5. package/dist/icu-message-formatter.browser.min.js +2 -0
  6. package/dist/icu-message-formatter.browser.min.js.map +1 -0
  7. package/dist/icu-message-formatter.cjs +473 -0
  8. package/dist/icu-message-formatter.cjs.map +1 -0
  9. package/dist/icu-message-formatter.d.cts +153 -0
  10. package/dist/icu-message-formatter.d.ts +153 -0
  11. package/dist/icu-message-formatter.js +466 -0
  12. package/dist/icu-message-formatter.js.map +1 -0
  13. package/package.json +43 -26
  14. package/dist/icu-message-formatter.es.min.js +0 -2
  15. package/dist/icu-message-formatter.es.min.js.map +0 -1
  16. package/dist/icu-message-formatter.min.js +0 -2
  17. package/dist/icu-message-formatter.min.js.map +0 -1
  18. package/lib/icu-message-formatter.cjs.js +0 -476
  19. package/lib/icu-message-formatter.cjs.js.map +0 -1
  20. package/lib/icu-message-formatter.es.js +0 -460
  21. package/lib/icu-message-formatter.es.js.map +0 -1
  22. package/rollup.config.dist.js +0 -37
  23. package/rollup.config.js +0 -35
  24. package/source/IcuMessageFormatter.js +0 -9
  25. package/source/MessageFormatter.js +0 -112
  26. package/source/MessageFormatter.test.js +0 -153
  27. package/source/pluralTypeHandler.js +0 -122
  28. package/source/pluralTypeHandler.test.js +0 -188
  29. package/source/selectTypeHandler.js +0 -46
  30. package/source/selectTypeHandler.test.js +0 -59
  31. package/source/utilities.js +0 -166
  32. package/source/utilities.test.js +0 -123
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ ### 0.14.0
6
+ - On the road to making this pure ESM, the following internal changes have been
7
+ made:
8
+ - Added `"type": "module"` so ESM is now the default
9
+ - Package outputs defined using an `exports` map w/ `import` pointing to the
10
+ main source and `require` to a transpiled version of the source
11
+
12
+ ### 0.13.0
13
+ - Minimum supported version of Node is now 18
14
+ - Browser target in `.browserslistrc` file is now `defaults`, so the explicit
15
+ `ie11` target has been removed and thus IE11 support has been dropped
16
+ - Type definitions are now included in this project
17
+
5
18
  ### 0.12.0
6
19
  - Dropped support for Node 10
7
20
  - Use [`Intl.Pluralrules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules)
package/README.md CHANGED
@@ -34,13 +34,11 @@ npm install @ultraq/icu-message-formatter
34
34
  ### As a script for the browser via the unpkg CDN:
35
35
 
36
36
  An IIFE version of this library is available at:
37
- https://unpkg.com/@ultraq/icu-message-formatter/dist/icu-message-formatter.min.js
38
-
37
+ https://unpkg.com/@ultraq/icu-message-formatter/dist/icu-message-formatter.browser.min.js
39
38
  In this form, this module will then be present in the global scope as `IcuMessageFormatter`.
40
39
 
41
40
  An ESM version of this library is available at:
42
- https://unpkg.com/@ultraq/icu-message-formatter/dist/icu-message-formatter.es.min.js
43
-
41
+ https://unpkg.com/@ultraq/icu-message-formatter/dist/icu-message-formatter.browser.es.min.js
44
42
  That URL can be used directly in ESM scripts made for the browser, and otherwise
45
43
  works like the NPM package.
46
44
 
@@ -98,8 +96,9 @@ register.
98
96
  - the object which matched the key of the block being processed
99
97
  - any format options associated with the block being processed
100
98
  - the locale to use for formatting
101
- - the object of placeholder data given to the original `format` call
102
- - and the `format` function itself (see below) so that sub-messages can be
99
+ - the object of placeholder data given to the original `format`/`process`
100
+ call
101
+ - and the `process` function itself (see below) so that sub-messages can be
103
102
  processed by type handlers
104
103
 
105
104
  #### format(message, values = {})
@@ -0,0 +1,2 @@
1
+ function t(t){const n=t=>/\s/.test(t),r=[],s={};let l=0,i=null,o=!1,u=0;for(;u<t.length;){if(o&&(n(t[u])||"{"===t[u]))o=!1,i=t.slice(l,u),"{"===t[u]&&u--;else if(!o&&!n(t[u])){const n="{"===t[u];if(i&&n){const n=e(t,u);if(-1===n)throw new Error(`Unbalanced curly braces in string: "${t}"`);s[i]=t.slice(u+1,n),u=n,i=null}else i&&(r.push(i),i=null),o=!0,l=u}u++}return o&&(i=t.slice(l)),i&&r.push(i),{args:r,cases:s}}function e(t,e){let n=0;for(let r=e+1;r<t.length;r++){let e=t.charAt(r);if("}"===e){if(0===n)return r;n--}else"{"===e&&n++}return-1}function n(t){return r(t.slice(1,-1),",",3)}function r(t,e,n){let s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];if(!t)return s;if(1===n)return s.push(t),s;let l=t.indexOf(e);if(-1===l)return s.push(t),s;let i=t.substring(0,l).trim(),o=t.substring(l+e.length+1).trim();return s.push(i),r(o,e,n-1,s)}class s{constructor(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};this.locale=t,this.typeHandlers=e}format=function(t){const e={};return function(...n){let r=n.length?n.map((t=>null===t?"null":void 0===t?"undefined":"function"==typeof t?t.toString():t instanceof Date?t.toISOString():JSON.stringify(t))).join("|"):"_(no-args)_";if(Object.prototype.hasOwnProperty.call(e,r))return e[r];let s=t(...n);return e[r]=s,s}}((()=>{var t=this;return function(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return t.process(e,n).flat(1/0).join("")}})());process(t){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!t)return[];let s=t.indexOf("{");if(-1!==s){let l=e(t,s);if(-1===l)throw new Error(`Unbalanced curly braces in string: "${t}"`);{let e=t.substring(s,l+1);if(e){let i=[],o=t.substring(0,s);o&&i.push(o);let[u,c,h]=n(e),f=r[u];null==f&&(f="");let a=c&&this.typeHandlers[c];i.push(a?a(f,h,this.locale,r,this.process.bind(this)):f);let g=t.substring(l+1);return g&&i.push(this.process(g,r)),i}}}return[t]}}let l,i=0;const o="other";function u(t,e){let n=0,r="",s=0;const l={};for(;n<t.length;){if("#"!==t[n]||s)r+=t[n];else{let t="__hashToken"+i++;r+=`{${t}, number}`,l[t]=e}"{"===t[n]?s++:"}"===t[n]&&s--,n++}return{caseBody:r,numberValues:l}}function c(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",r=arguments.length>2?arguments[2]:void 0,s=arguments.length>3?arguments[3]:void 0,i=arguments.length>4?arguments[4]:void 0;const{args:c,cases:h}=t(n);let f=parseInt(e);c.forEach((t=>{t.startsWith("offset:")&&(f-=parseInt(t.slice(7)))}));const a=[];if("PluralRules"in Intl){void 0!==l&&l.resolvedOptions().locale===r||(l=new Intl.PluralRules(r));const t=l.select(f);t!==o&&a.push(t)}1===f&&a.push("one"),a.push(`=${f}`,o);for(let t=0;t<a.length;t++){const e=a[t];if(e in h){const{caseBody:t,numberValues:n}=u(h[e],f);return i(t,{...s,...n})}}return e}const h="other";function f(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",r=arguments.length>3?arguments[3]:void 0,s=arguments.length>4?arguments[4]:void 0;const{cases:l}=t(n);return e in l?s(l[e],r):h in l?s(l[h],r):e}export{s as MessageFormatter,e as findClosingBracket,t as parseCases,c as pluralTypeHandler,f as selectTypeHandler,n as splitFormattedArgument};
2
+ //# sourceMappingURL=icu-message-formatter.browser.es.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icu-message-formatter.browser.es.min.js","sources":["../source/utilities.js","../source/MessageFormatter.js","../node_modules/@ultraq/function-utils/function-utils.js","../source/pluralTypeHandler.js","../source/selectTypeHandler.js"],"sourcesContent":["/* \n * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * \n * http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @typedef ParseCasesResult\n * @property {string[]} args\n * A list of prepended arguments.\n * @property {Record<string,string>} cases\n * A map of all cases.\n */\n\n/**\n * Most branch-based type handlers are based around \"cases\". For example,\n * `select` and `plural` compare compare a value to \"case keys\" to choose a\n * subtranslation.\n * \n * This util splits \"matches\" portions provided to the aforementioned handlers\n * into case strings, and extracts any prepended arguments (for example,\n * `plural` supports an `offset:n` argument used for populating the magic `#`\n * variable).\n * \n * @param {string} string\n * @return {ParseCasesResult}\n */\nexport function parseCases(string) {\n\tconst isWhitespace = ch => /\\s/.test(ch);\n\n\tconst args = [];\n\tconst cases = {};\n\n\tlet currTermStart = 0;\n\tlet latestTerm = null;\n\tlet inTerm = false;\n\n\tlet i = 0;\n\twhile (i < string.length) {\n\t\t// Term ended\n\t\tif (inTerm && (isWhitespace(string[i]) || string[i] === '{')) {\n\t\t\tinTerm = false;\n\t\t\tlatestTerm = string.slice(currTermStart, i);\n\n\t\t\t// We want to process the opening char again so the case will be properly registered.\n\t\t\tif (string[i] === '{') {\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\n\t\t// New term\n\t\telse if (!inTerm && !isWhitespace(string[i])) {\n\t\t\tconst caseBody = string[i] === '{';\n\n\t\t\t// If there's a previous term, we can either handle a whole\n\t\t\t// case, or add that as an argument.\n\t\t\tif (latestTerm && caseBody) {\n\t\t\t\tconst branchEndIndex = findClosingBracket(string, i);\n\n\t\t\t\tif (branchEndIndex === -1) {\n\t\t\t\t\tthrow new Error(`Unbalanced curly braces in string: \"${string}\"`);\n\t\t\t\t}\n\n\t\t\t\tcases[latestTerm] = string.slice(i + 1, branchEndIndex); // Don't include the braces\n\n\t\t\t\ti = branchEndIndex; // Will be moved up where needed at end of loop.\n\t\t\t\tlatestTerm = null;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (latestTerm) {\n\t\t\t\t\targs.push(latestTerm);\n\t\t\t\t\tlatestTerm = null;\n\t\t\t\t}\n\n\t\t\t\tinTerm = true;\n\t\t\t\tcurrTermStart = i;\n\t\t\t}\n\t\t}\n\t\ti++;\n\t}\n\n\tif (inTerm) {\n\t\tlatestTerm = string.slice(currTermStart);\n\t}\n\n\tif (latestTerm) {\n\t\targs.push(latestTerm);\n\t}\n\n\treturn {\n\t\targs,\n\t\tcases\n\t};\n}\n\n/**\n * Finds the index of the matching closing curly bracket, including through\n * strings that could have nested brackets.\n * \n * @param {string} string\n * @param {number} fromIndex\n * @return {number}\n * The index of the matching closing bracket, or -1 if no closing bracket\n * could be found.\n */\nexport function findClosingBracket(string, fromIndex) {\n\tlet depth = 0;\n\tfor (let i = fromIndex + 1; i < string.length; i++) {\n\t\tlet char = string.charAt(i);\n\t\tif (char === '}') {\n\t\t\tif (depth === 0) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t\tdepth--;\n\t\t}\n\t\telse if (char === '{') {\n\t\t\tdepth++;\n\t\t}\n\t}\n\treturn -1;\n}\n\n/**\n * Split a `{key, type, format}` block into those 3 parts, taking into account\n * nested message syntax that can exist in the `format` part.\n * \n * @param {string} block\n * @return {string[]}\n * An array with `key`, `type`, and `format` items in that order, if present\n * in the formatted argument block.\n */\nexport function splitFormattedArgument(block) {\n\treturn split(block.slice(1, -1), ',', 3);\n}\n\n/**\n * Like `String.prototype.split()` but where the limit parameter causes the\n * remainder of the string to be grouped together in a final entry.\n * \n * @private\n * @param {string} string\n * @param {string} separator\n * @param {number} limit\n * @param {string[]} accumulator\n * @return {string[]}\n */\nfunction split(string, separator, limit, accumulator = []) {\n\tif (!string) {\n\t\treturn accumulator;\n\t}\n\tif (limit === 1) {\n\t\taccumulator.push(string);\n\t\treturn accumulator;\n\t}\n\tlet indexOfDelimiter = string.indexOf(separator);\n\tif (indexOfDelimiter === -1) {\n\t\taccumulator.push(string);\n\t\treturn accumulator;\n\t}\n\tlet head = string.substring(0, indexOfDelimiter).trim();\n\tlet tail = string.substring(indexOfDelimiter + separator.length + 1).trim();\n\taccumulator.push(head);\n\treturn split(tail, separator, limit - 1, accumulator);\n}\n","/*\n * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {findClosingBracket, splitFormattedArgument} from './utilities.js';\n\nimport {memoize} from '@ultraq/function-utils';\n\n/**\n * @typedef {Record<string,any>} FormatValues\n */\n\n/**\n * @callback ProcessFunction\n * @param {string} message\n * @param {FormatValues} [values={}]\n * @return {any[]}\n */\n\n/**\n * @callback TypeHandler\n * @param {any} value\n * The object which matched the key of the block being processed.\n * @param {string} matches\n * Any format options associated with the block being processed.\n * @param {string} locale\n * The locale to use for formatting.\n * @param {FormatValues} values\n * The object of placeholder data given to the original `format`/`process`\n * call.\n * @param {ProcessFunction} process\n * The `process` function itself so that sub-messages can be processed by type\n * handlers.\n * @return {any | any[]}\n */\n\n/**\n * The main class for formatting messages.\n *\n * @author Emanuel Rabina\n */\nexport default class MessageFormatter {\n\n\t/**\n\t * Creates a new formatter that can work using any of the custom type handlers\n\t * you register.\n\t *\n\t * @param {string} locale\n\t * @param {Record<string,TypeHandler>} [typeHandlers]\n\t * Optional object where the keys are the names of the types to register,\n\t * their values being the functions that will return a nicely formatted\n\t * string for the data and locale they are given.\n\t */\n\tconstructor(locale, typeHandlers = {}) {\n\n\t\tthis.locale = locale;\n\t\tthis.typeHandlers = typeHandlers;\n\t}\n\n\t/**\n\t * Formats an ICU message syntax string using `values` for placeholder data\n\t * and any currently-registered type handlers.\n\t *\n\t * @type {(message: string, values?: FormatValues) => string}\n\t */\n\tformat = memoize((message, values = {}) => {\n\n\t\treturn this.process(message, values).flat(Infinity).join('');\n\t});\n\n\t/**\n\t * Process an ICU message syntax string using `values` for placeholder data\n\t * and any currently-registered type handlers. The result of this method is\n\t * an array of the component parts after they have been processed in turn by\n\t * their own type handlers. This raw output is useful for other renderers,\n\t * eg: React where components can be used instead of being forced to return\n\t * raw strings.\n\t *\n\t * This method is used by {@link MessageFormatter#format} where it acts as a\n\t * string renderer.\n\t *\n\t * @param {string} message\n\t * @param {FormatValues} [values]\n\t * @return {any[]}\n\t */\n\tprocess(message, values = {}) {\n\n\t\tif (!message) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet blockStartIndex = message.indexOf('{');\n\t\tif (blockStartIndex !== -1) {\n\t\t\tlet blockEndIndex = findClosingBracket(message, blockStartIndex);\n\t\t\tif (blockEndIndex !== -1) {\n\t\t\t\tlet block = message.substring(blockStartIndex, blockEndIndex + 1);\n\t\t\t\tif (block) {\n\t\t\t\t\tlet result = [];\n\t\t\t\t\tlet head = message.substring(0, blockStartIndex);\n\t\t\t\t\tif (head) {\n\t\t\t\t\t\tresult.push(head);\n\t\t\t\t\t}\n\t\t\t\t\tlet [key, type, format] = splitFormattedArgument(block);\n\t\t\t\t\tlet body = values[key];\n\t\t\t\t\tif (body === null || body === undefined) {\n\t\t\t\t\t\tbody = '';\n\t\t\t\t\t}\n\t\t\t\t\tlet typeHandler = type && this.typeHandlers[type];\n\t\t\t\t\tresult.push(typeHandler ?\n\t\t\t\t\t\ttypeHandler(body, format, this.locale, values, this.process.bind(this)) :\n\t\t\t\t\t\tbody);\n\t\t\t\t\tlet tail = message.substring(blockEndIndex + 1);\n\t\t\t\t\tif (tail) {\n\t\t\t\t\t\tresult.push(this.process(tail, values));\n\t\t\t\t\t}\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthrow new Error(`Unbalanced curly braces in string: \"${message}\"`);\n\t\t\t}\n\t\t}\n\t\treturn [message];\n\t}\n}\n","/**\n * A higher-order function to apply [memoization](https://en.wikipedia.org/wiki/Memoization).\n * \n * If memoizing a recursive function, then memoize and define the function at\n * the same time so you can make a call to the memoized function, eg:\n * \n * ```javascript\n * const myFunction = memoize(() => myFunction());\n * ```\n * \n * @param {Function} func\n * @return {Function} \n */\nexport function memoize(func) {\n\tconst cache = {};\n\treturn function(...args) {\n\t\tlet key = args.length ? args\n\t\t\t.map(arg =>\n\t\t\t\targ === null ? 'null' :\n\t\t\t\targ === undefined ? 'undefined' :\n\t\t\t\ttypeof arg === 'function' ? arg.toString() :\n\t\t\t\targ instanceof Date ? arg.toISOString() :\n\t\t\t\tJSON.stringify(arg)\n\t\t\t)\n\t\t\t.join('|') :\n\t\t\t'_(no-args)_';\n\t\tif (Object.prototype.hasOwnProperty.call(cache, key)) {\n\t\t\treturn cache[key];\n\t\t}\n\t\tlet result = func(...args);\n\t\tcache[key] = result;\n\t\treturn result;\n\t};\n}\n","/* \n * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * \n * http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {parseCases} from './utilities.js';\n\nlet pluralFormatter;\n\nlet keyCounter = 0;\n\n// All the special keywords that can be used in `plural` blocks for the various branches\nconst ONE = 'one';\nconst OTHER = 'other';\n\n/**\n * @private\n * @param {string} caseBody\n * @param {number} value\n * @return {{caseBody: string, numberValues: object}}\n */\nfunction replaceNumberSign(caseBody, value) {\n\tlet i = 0;\n\tlet output = '';\n\tlet numBraces = 0;\n\tconst numberValues = {};\n\n\twhile (i < caseBody.length) {\n\t\tif (caseBody[i] === '#' && !numBraces) {\n\t\t\tlet keyParam = `__hashToken${keyCounter++}`;\n\t\t\toutput += `{${keyParam}, number}`;\n\t\t\tnumberValues[keyParam] = value;\n\t\t}\n\t\telse {\n\t\t\toutput += caseBody[i];\n\t\t}\n\n\t\tif (caseBody[i] === '{') {\n\t\t\tnumBraces++;\n\t\t}\n\t\telse if (caseBody[i] === '}') {\n\t\t\tnumBraces--;\n\t\t}\n\n\t\ti++;\n\t}\n\n\treturn {\n\t\tcaseBody: output,\n\t\tnumberValues\n\t};\n}\n\n/**\n * Handler for `plural` statements within ICU message syntax strings. Returns\n * a formatted string for the branch that closely matches the current value.\n * \n * See https://formatjs.io/docs/core-concepts/icu-syntax#plural-format for more\n * details on how the `plural` statement works.\n *\n * @param {string} value\n * @param {string} matches\n * @param {string} locale\n * @param {Record<string,any>} values\n * @param {(message: string, values?: Record<string,any>) => any[]} process\n * @return {any | any[]}\n */\nexport default function pluralTypeHandler(value, matches = '', locale, values, process) {\n\tconst {args, cases} = parseCases(matches);\n\n\tlet intValue = parseInt(value);\n\n\targs.forEach((arg) => {\n\t\tif (arg.startsWith('offset:')) {\n\t\t\tintValue -= parseInt(arg.slice('offset:'.length));\n\t\t}\n\t});\n\n\tconst keywordPossibilities = [];\n\n\tif ('PluralRules' in Intl) {\n\t\t// Effectively memoize because instantiation of `Int.*` objects is expensive.\n\t\tif (pluralFormatter === undefined || pluralFormatter.resolvedOptions().locale !== locale) {\n\t\t\tpluralFormatter = new Intl.PluralRules(locale);\n\t\t}\n\n\t\tconst pluralKeyword = pluralFormatter.select(intValue);\n\n\t\t// Other is always added last with least priority, so we don't want to add it here.\n\t\tif (pluralKeyword !== OTHER) {\n\t\t\tkeywordPossibilities.push(pluralKeyword);\n\t\t}\n\t}\n\tif (intValue === 1) {\n\t\tkeywordPossibilities.push(ONE);\n\t}\n\tkeywordPossibilities.push(`=${intValue}`, OTHER);\n\n\tfor (let i = 0; i < keywordPossibilities.length; i++) {\n\t\tconst keyword = keywordPossibilities[i];\n\t\tif (keyword in cases) {\n\t\t\tconst {caseBody, numberValues} = replaceNumberSign(cases[keyword], intValue);\n\t\t\treturn process(caseBody, {\n\t\t\t\t...values,\n\t\t\t\t...numberValues\n\t\t\t});\n\t\t}\n\t}\n\n\treturn value;\n}\n","/* \n * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * \n * http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {parseCases} from './utilities.js';\n\nconst OTHER = 'other';\n\n/**\n * Handler for `select` statements within ICU message syntax strings. Returns\n * a formatted string for the branch that closely matches the current value.\n * \n * See https://formatjs.io/docs/core-concepts/icu-syntax#select-format for more\n * details on how the `select` statement works.\n * \n * @param {string} value\n * @param {string} matches\n * @param {string} locale\n * @param {Record<string,any>} values\n * @param {(message: string, values?: Record<string,any>) => any[]} process\n * @return {any | any[]}\n */\nexport default function selectTypeHandler(value, matches = '', locale, values, process) {\n\tconst {cases} = parseCases(matches);\n\n\tif (value in cases) {\n\t\treturn process(cases[value], values);\n\t}\n\telse if (OTHER in cases) {\n\t\treturn process(cases[OTHER], values);\n\t}\n\n\treturn value;\n}\n"],"names":["parseCases","string","isWhitespace","ch","test","args","cases","currTermStart","latestTerm","inTerm","i","length","slice","caseBody","branchEndIndex","findClosingBracket","Error","push","fromIndex","depth","char","charAt","splitFormattedArgument","block","split","separator","limit","accumulator","arguments","undefined","indexOfDelimiter","indexOf","head","substring","trim","tail","MessageFormatter","constructor","locale","typeHandlers","this","format","func","cache","key","map","arg","toString","Date","toISOString","JSON","stringify","join","Object","prototype","hasOwnProperty","call","result","memoize","_this","message","values","process","flat","Infinity","blockStartIndex","blockEndIndex","type","body","typeHandler","bind","pluralFormatter","keyCounter","OTHER","replaceNumberSign","value","output","numBraces","numberValues","keyParam","pluralTypeHandler","matches","intValue","parseInt","forEach","startsWith","keywordPossibilities","Intl","resolvedOptions","PluralRules","pluralKeyword","select","keyword","selectTypeHandler"],"mappings":"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","x_google_ignoreList":[2]}
@@ -0,0 +1,2 @@
1
+ var IcuMessageFormatter=function(t){"use strict";function e(t){const e=t=>/\s/.test(t),r=[],s={};let l=0,i=null,o=!1,u=0;for(;u<t.length;){if(o&&(e(t[u])||"{"===t[u]))o=!1,i=t.slice(l,u),"{"===t[u]&&u--;else if(!o&&!e(t[u])){const e="{"===t[u];if(i&&e){const e=n(t,u);if(-1===e)throw new Error(`Unbalanced curly braces in string: "${t}"`);s[i]=t.slice(u+1,e),u=e,i=null}else i&&(r.push(i),i=null),o=!0,l=u}u++}return o&&(i=t.slice(l)),i&&r.push(i),{args:r,cases:s}}function n(t,e){let n=0;for(let r=e+1;r<t.length;r++){let e=t.charAt(r);if("}"===e){if(0===n)return r;n--}else"{"===e&&n++}return-1}function r(t){return s(t.slice(1,-1),",",3)}function s(t,e,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];if(!t)return r;if(1===n)return r.push(t),r;let l=t.indexOf(e);if(-1===l)return r.push(t),r;let i=t.substring(0,l).trim(),o=t.substring(l+e.length+1).trim();return r.push(i),s(o,e,n-1,r)}let l,i=0;const o="other";function u(t,e){let n=0,r="",s=0;const l={};for(;n<t.length;){if("#"!==t[n]||s)r+=t[n];else{let t="__hashToken"+i++;r+=`{${t}, number}`,l[t]=e}"{"===t[n]?s++:"}"===t[n]&&s--,n++}return{caseBody:r,numberValues:l}}const c="other";return t.MessageFormatter=class{constructor(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};this.locale=t,this.typeHandlers=e}format=function(t){const e={};return function(...n){let r=n.length?n.map((t=>null===t?"null":void 0===t?"undefined":"function"==typeof t?t.toString():t instanceof Date?t.toISOString():JSON.stringify(t))).join("|"):"_(no-args)_";if(Object.prototype.hasOwnProperty.call(e,r))return e[r];let s=t(...n);return e[r]=s,s}}((()=>{var t=this;return function(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return t.process(e,n).flat(1/0).join("")}})());process(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!t)return[];let s=t.indexOf("{");if(-1!==s){let l=n(t,s);if(-1===l)throw new Error(`Unbalanced curly braces in string: "${t}"`);{let n=t.substring(s,l+1);if(n){let i=[],o=t.substring(0,s);o&&i.push(o);let[u,c,a]=r(n),h=e[u];null==h&&(h="");let f=c&&this.typeHandlers[c];i.push(f?f(h,a,this.locale,e,this.process.bind(this)):h);let g=t.substring(l+1);return g&&i.push(this.process(g,e)),i}}}return[t]}},t.findClosingBracket=n,t.parseCases=e,t.pluralTypeHandler=function(t){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",r=arguments.length>2?arguments[2]:void 0,s=arguments.length>3?arguments[3]:void 0,i=arguments.length>4?arguments[4]:void 0;const{args:c,cases:a}=e(n);let h=parseInt(t);c.forEach((t=>{t.startsWith("offset:")&&(h-=parseInt(t.slice(7)))}));const f=[];if("PluralRules"in Intl){void 0!==l&&l.resolvedOptions().locale===r||(l=new Intl.PluralRules(r));const t=l.select(h);t!==o&&f.push(t)}1===h&&f.push("one"),f.push(`=${h}`,o);for(let t=0;t<f.length;t++){const e=f[t];if(e in a){const{caseBody:t,numberValues:n}=u(a[e],h);return i(t,{...s,...n})}}return t},t.selectTypeHandler=function(t){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",r=arguments.length>3?arguments[3]:void 0,s=arguments.length>4?arguments[4]:void 0;const{cases:l}=e(n);return t in l?s(l[t],r):c in l?s(l[c],r):t},t.splitFormattedArgument=r,t}({});
2
+ //# sourceMappingURL=icu-message-formatter.browser.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icu-message-formatter.browser.min.js","sources":["../source/utilities.js","../source/pluralTypeHandler.js","../source/selectTypeHandler.js","../source/MessageFormatter.js","../node_modules/@ultraq/function-utils/function-utils.js"],"sourcesContent":["/* \n * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * \n * http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @typedef ParseCasesResult\n * @property {string[]} args\n * A list of prepended arguments.\n * @property {Record<string,string>} cases\n * A map of all cases.\n */\n\n/**\n * Most branch-based type handlers are based around \"cases\". For example,\n * `select` and `plural` compare compare a value to \"case keys\" to choose a\n * subtranslation.\n * \n * This util splits \"matches\" portions provided to the aforementioned handlers\n * into case strings, and extracts any prepended arguments (for example,\n * `plural` supports an `offset:n` argument used for populating the magic `#`\n * variable).\n * \n * @param {string} string\n * @return {ParseCasesResult}\n */\nexport function parseCases(string) {\n\tconst isWhitespace = ch => /\\s/.test(ch);\n\n\tconst args = [];\n\tconst cases = {};\n\n\tlet currTermStart = 0;\n\tlet latestTerm = null;\n\tlet inTerm = false;\n\n\tlet i = 0;\n\twhile (i < string.length) {\n\t\t// Term ended\n\t\tif (inTerm && (isWhitespace(string[i]) || string[i] === '{')) {\n\t\t\tinTerm = false;\n\t\t\tlatestTerm = string.slice(currTermStart, i);\n\n\t\t\t// We want to process the opening char again so the case will be properly registered.\n\t\t\tif (string[i] === '{') {\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\n\t\t// New term\n\t\telse if (!inTerm && !isWhitespace(string[i])) {\n\t\t\tconst caseBody = string[i] === '{';\n\n\t\t\t// If there's a previous term, we can either handle a whole\n\t\t\t// case, or add that as an argument.\n\t\t\tif (latestTerm && caseBody) {\n\t\t\t\tconst branchEndIndex = findClosingBracket(string, i);\n\n\t\t\t\tif (branchEndIndex === -1) {\n\t\t\t\t\tthrow new Error(`Unbalanced curly braces in string: \"${string}\"`);\n\t\t\t\t}\n\n\t\t\t\tcases[latestTerm] = string.slice(i + 1, branchEndIndex); // Don't include the braces\n\n\t\t\t\ti = branchEndIndex; // Will be moved up where needed at end of loop.\n\t\t\t\tlatestTerm = null;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (latestTerm) {\n\t\t\t\t\targs.push(latestTerm);\n\t\t\t\t\tlatestTerm = null;\n\t\t\t\t}\n\n\t\t\t\tinTerm = true;\n\t\t\t\tcurrTermStart = i;\n\t\t\t}\n\t\t}\n\t\ti++;\n\t}\n\n\tif (inTerm) {\n\t\tlatestTerm = string.slice(currTermStart);\n\t}\n\n\tif (latestTerm) {\n\t\targs.push(latestTerm);\n\t}\n\n\treturn {\n\t\targs,\n\t\tcases\n\t};\n}\n\n/**\n * Finds the index of the matching closing curly bracket, including through\n * strings that could have nested brackets.\n * \n * @param {string} string\n * @param {number} fromIndex\n * @return {number}\n * The index of the matching closing bracket, or -1 if no closing bracket\n * could be found.\n */\nexport function findClosingBracket(string, fromIndex) {\n\tlet depth = 0;\n\tfor (let i = fromIndex + 1; i < string.length; i++) {\n\t\tlet char = string.charAt(i);\n\t\tif (char === '}') {\n\t\t\tif (depth === 0) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t\tdepth--;\n\t\t}\n\t\telse if (char === '{') {\n\t\t\tdepth++;\n\t\t}\n\t}\n\treturn -1;\n}\n\n/**\n * Split a `{key, type, format}` block into those 3 parts, taking into account\n * nested message syntax that can exist in the `format` part.\n * \n * @param {string} block\n * @return {string[]}\n * An array with `key`, `type`, and `format` items in that order, if present\n * in the formatted argument block.\n */\nexport function splitFormattedArgument(block) {\n\treturn split(block.slice(1, -1), ',', 3);\n}\n\n/**\n * Like `String.prototype.split()` but where the limit parameter causes the\n * remainder of the string to be grouped together in a final entry.\n * \n * @private\n * @param {string} string\n * @param {string} separator\n * @param {number} limit\n * @param {string[]} accumulator\n * @return {string[]}\n */\nfunction split(string, separator, limit, accumulator = []) {\n\tif (!string) {\n\t\treturn accumulator;\n\t}\n\tif (limit === 1) {\n\t\taccumulator.push(string);\n\t\treturn accumulator;\n\t}\n\tlet indexOfDelimiter = string.indexOf(separator);\n\tif (indexOfDelimiter === -1) {\n\t\taccumulator.push(string);\n\t\treturn accumulator;\n\t}\n\tlet head = string.substring(0, indexOfDelimiter).trim();\n\tlet tail = string.substring(indexOfDelimiter + separator.length + 1).trim();\n\taccumulator.push(head);\n\treturn split(tail, separator, limit - 1, accumulator);\n}\n","/* \n * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * \n * http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {parseCases} from './utilities.js';\n\nlet pluralFormatter;\n\nlet keyCounter = 0;\n\n// All the special keywords that can be used in `plural` blocks for the various branches\nconst ONE = 'one';\nconst OTHER = 'other';\n\n/**\n * @private\n * @param {string} caseBody\n * @param {number} value\n * @return {{caseBody: string, numberValues: object}}\n */\nfunction replaceNumberSign(caseBody, value) {\n\tlet i = 0;\n\tlet output = '';\n\tlet numBraces = 0;\n\tconst numberValues = {};\n\n\twhile (i < caseBody.length) {\n\t\tif (caseBody[i] === '#' && !numBraces) {\n\t\t\tlet keyParam = `__hashToken${keyCounter++}`;\n\t\t\toutput += `{${keyParam}, number}`;\n\t\t\tnumberValues[keyParam] = value;\n\t\t}\n\t\telse {\n\t\t\toutput += caseBody[i];\n\t\t}\n\n\t\tif (caseBody[i] === '{') {\n\t\t\tnumBraces++;\n\t\t}\n\t\telse if (caseBody[i] === '}') {\n\t\t\tnumBraces--;\n\t\t}\n\n\t\ti++;\n\t}\n\n\treturn {\n\t\tcaseBody: output,\n\t\tnumberValues\n\t};\n}\n\n/**\n * Handler for `plural` statements within ICU message syntax strings. Returns\n * a formatted string for the branch that closely matches the current value.\n * \n * See https://formatjs.io/docs/core-concepts/icu-syntax#plural-format for more\n * details on how the `plural` statement works.\n *\n * @param {string} value\n * @param {string} matches\n * @param {string} locale\n * @param {Record<string,any>} values\n * @param {(message: string, values?: Record<string,any>) => any[]} process\n * @return {any | any[]}\n */\nexport default function pluralTypeHandler(value, matches = '', locale, values, process) {\n\tconst {args, cases} = parseCases(matches);\n\n\tlet intValue = parseInt(value);\n\n\targs.forEach((arg) => {\n\t\tif (arg.startsWith('offset:')) {\n\t\t\tintValue -= parseInt(arg.slice('offset:'.length));\n\t\t}\n\t});\n\n\tconst keywordPossibilities = [];\n\n\tif ('PluralRules' in Intl) {\n\t\t// Effectively memoize because instantiation of `Int.*` objects is expensive.\n\t\tif (pluralFormatter === undefined || pluralFormatter.resolvedOptions().locale !== locale) {\n\t\t\tpluralFormatter = new Intl.PluralRules(locale);\n\t\t}\n\n\t\tconst pluralKeyword = pluralFormatter.select(intValue);\n\n\t\t// Other is always added last with least priority, so we don't want to add it here.\n\t\tif (pluralKeyword !== OTHER) {\n\t\t\tkeywordPossibilities.push(pluralKeyword);\n\t\t}\n\t}\n\tif (intValue === 1) {\n\t\tkeywordPossibilities.push(ONE);\n\t}\n\tkeywordPossibilities.push(`=${intValue}`, OTHER);\n\n\tfor (let i = 0; i < keywordPossibilities.length; i++) {\n\t\tconst keyword = keywordPossibilities[i];\n\t\tif (keyword in cases) {\n\t\t\tconst {caseBody, numberValues} = replaceNumberSign(cases[keyword], intValue);\n\t\t\treturn process(caseBody, {\n\t\t\t\t...values,\n\t\t\t\t...numberValues\n\t\t\t});\n\t\t}\n\t}\n\n\treturn value;\n}\n","/* \n * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * \n * http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {parseCases} from './utilities.js';\n\nconst OTHER = 'other';\n\n/**\n * Handler for `select` statements within ICU message syntax strings. Returns\n * a formatted string for the branch that closely matches the current value.\n * \n * See https://formatjs.io/docs/core-concepts/icu-syntax#select-format for more\n * details on how the `select` statement works.\n * \n * @param {string} value\n * @param {string} matches\n * @param {string} locale\n * @param {Record<string,any>} values\n * @param {(message: string, values?: Record<string,any>) => any[]} process\n * @return {any | any[]}\n */\nexport default function selectTypeHandler(value, matches = '', locale, values, process) {\n\tconst {cases} = parseCases(matches);\n\n\tif (value in cases) {\n\t\treturn process(cases[value], values);\n\t}\n\telse if (OTHER in cases) {\n\t\treturn process(cases[OTHER], values);\n\t}\n\n\treturn value;\n}\n","/*\n * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {findClosingBracket, splitFormattedArgument} from './utilities.js';\n\nimport {memoize} from '@ultraq/function-utils';\n\n/**\n * @typedef {Record<string,any>} FormatValues\n */\n\n/**\n * @callback ProcessFunction\n * @param {string} message\n * @param {FormatValues} [values={}]\n * @return {any[]}\n */\n\n/**\n * @callback TypeHandler\n * @param {any} value\n * The object which matched the key of the block being processed.\n * @param {string} matches\n * Any format options associated with the block being processed.\n * @param {string} locale\n * The locale to use for formatting.\n * @param {FormatValues} values\n * The object of placeholder data given to the original `format`/`process`\n * call.\n * @param {ProcessFunction} process\n * The `process` function itself so that sub-messages can be processed by type\n * handlers.\n * @return {any | any[]}\n */\n\n/**\n * The main class for formatting messages.\n *\n * @author Emanuel Rabina\n */\nexport default class MessageFormatter {\n\n\t/**\n\t * Creates a new formatter that can work using any of the custom type handlers\n\t * you register.\n\t *\n\t * @param {string} locale\n\t * @param {Record<string,TypeHandler>} [typeHandlers]\n\t * Optional object where the keys are the names of the types to register,\n\t * their values being the functions that will return a nicely formatted\n\t * string for the data and locale they are given.\n\t */\n\tconstructor(locale, typeHandlers = {}) {\n\n\t\tthis.locale = locale;\n\t\tthis.typeHandlers = typeHandlers;\n\t}\n\n\t/**\n\t * Formats an ICU message syntax string using `values` for placeholder data\n\t * and any currently-registered type handlers.\n\t *\n\t * @type {(message: string, values?: FormatValues) => string}\n\t */\n\tformat = memoize((message, values = {}) => {\n\n\t\treturn this.process(message, values).flat(Infinity).join('');\n\t});\n\n\t/**\n\t * Process an ICU message syntax string using `values` for placeholder data\n\t * and any currently-registered type handlers. The result of this method is\n\t * an array of the component parts after they have been processed in turn by\n\t * their own type handlers. This raw output is useful for other renderers,\n\t * eg: React where components can be used instead of being forced to return\n\t * raw strings.\n\t *\n\t * This method is used by {@link MessageFormatter#format} where it acts as a\n\t * string renderer.\n\t *\n\t * @param {string} message\n\t * @param {FormatValues} [values]\n\t * @return {any[]}\n\t */\n\tprocess(message, values = {}) {\n\n\t\tif (!message) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet blockStartIndex = message.indexOf('{');\n\t\tif (blockStartIndex !== -1) {\n\t\t\tlet blockEndIndex = findClosingBracket(message, blockStartIndex);\n\t\t\tif (blockEndIndex !== -1) {\n\t\t\t\tlet block = message.substring(blockStartIndex, blockEndIndex + 1);\n\t\t\t\tif (block) {\n\t\t\t\t\tlet result = [];\n\t\t\t\t\tlet head = message.substring(0, blockStartIndex);\n\t\t\t\t\tif (head) {\n\t\t\t\t\t\tresult.push(head);\n\t\t\t\t\t}\n\t\t\t\t\tlet [key, type, format] = splitFormattedArgument(block);\n\t\t\t\t\tlet body = values[key];\n\t\t\t\t\tif (body === null || body === undefined) {\n\t\t\t\t\t\tbody = '';\n\t\t\t\t\t}\n\t\t\t\t\tlet typeHandler = type && this.typeHandlers[type];\n\t\t\t\t\tresult.push(typeHandler ?\n\t\t\t\t\t\ttypeHandler(body, format, this.locale, values, this.process.bind(this)) :\n\t\t\t\t\t\tbody);\n\t\t\t\t\tlet tail = message.substring(blockEndIndex + 1);\n\t\t\t\t\tif (tail) {\n\t\t\t\t\t\tresult.push(this.process(tail, values));\n\t\t\t\t\t}\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthrow new Error(`Unbalanced curly braces in string: \"${message}\"`);\n\t\t\t}\n\t\t}\n\t\treturn [message];\n\t}\n}\n","/**\n * A higher-order function to apply [memoization](https://en.wikipedia.org/wiki/Memoization).\n * \n * If memoizing a recursive function, then memoize and define the function at\n * the same time so you can make a call to the memoized function, eg:\n * \n * ```javascript\n * const myFunction = memoize(() => myFunction());\n * ```\n * \n * @param {Function} func\n * @return {Function} \n */\nexport function memoize(func) {\n\tconst cache = {};\n\treturn function(...args) {\n\t\tlet key = args.length ? args\n\t\t\t.map(arg =>\n\t\t\t\targ === null ? 'null' :\n\t\t\t\targ === undefined ? 'undefined' :\n\t\t\t\ttypeof arg === 'function' ? arg.toString() :\n\t\t\t\targ instanceof Date ? arg.toISOString() :\n\t\t\t\tJSON.stringify(arg)\n\t\t\t)\n\t\t\t.join('|') :\n\t\t\t'_(no-args)_';\n\t\tif (Object.prototype.hasOwnProperty.call(cache, key)) {\n\t\t\treturn cache[key];\n\t\t}\n\t\tlet result = func(...args);\n\t\tcache[key] = result;\n\t\treturn result;\n\t};\n}\n"],"names":["parseCases","string","isWhitespace","ch","test","args","cases","currTermStart","latestTerm","inTerm","i","length","slice","caseBody","branchEndIndex","findClosingBracket","Error","push","fromIndex","depth","char","charAt","splitFormattedArgument","block","split","separator","limit","accumulator","arguments","undefined","indexOfDelimiter","indexOf","head","substring","trim","tail","pluralFormatter","keyCounter","OTHER","replaceNumberSign","value","output","numBraces","numberValues","keyParam","constructor","locale","typeHandlers","this","format","func","cache","key","map","arg","toString","Date","toISOString","JSON","stringify","join","Object","prototype","hasOwnProperty","call","result","memoize","_this","message","values","process","flat","Infinity","blockStartIndex","blockEndIndex","type","body","typeHandler","bind","matches","intValue","parseInt","forEach","startsWith","keywordPossibilities","Intl","resolvedOptions","PluralRules","pluralKeyword","select","keyword"],"mappings":"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","x_google_ignoreList":[4]}
@@ -0,0 +1,473 @@
1
+ 'use strict';
2
+
3
+ var functionUtils = require('@ultraq/function-utils');
4
+
5
+ /*
6
+ * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */
20
+
21
+ /**
22
+ * @typedef ParseCasesResult
23
+ * @property {string[]} args
24
+ * A list of prepended arguments.
25
+ * @property {Record<string,string>} cases
26
+ * A map of all cases.
27
+ */
28
+
29
+ /**
30
+ * Most branch-based type handlers are based around "cases". For example,
31
+ * `select` and `plural` compare compare a value to "case keys" to choose a
32
+ * subtranslation.
33
+ *
34
+ * This util splits "matches" portions provided to the aforementioned handlers
35
+ * into case strings, and extracts any prepended arguments (for example,
36
+ * `plural` supports an `offset:n` argument used for populating the magic `#`
37
+ * variable).
38
+ *
39
+ * @param {string} string
40
+ * @return {ParseCasesResult}
41
+ */
42
+ function parseCases(string) {
43
+ const isWhitespace = ch => /\s/.test(ch);
44
+ const args = [];
45
+ const cases = {};
46
+ let currTermStart = 0;
47
+ let latestTerm = null;
48
+ let inTerm = false;
49
+ let i = 0;
50
+ while (i < string.length) {
51
+ // Term ended
52
+ if (inTerm && (isWhitespace(string[i]) || string[i] === '{')) {
53
+ inTerm = false;
54
+ latestTerm = string.slice(currTermStart, i);
55
+
56
+ // We want to process the opening char again so the case will be properly registered.
57
+ if (string[i] === '{') {
58
+ i--;
59
+ }
60
+ }
61
+
62
+ // New term
63
+ else if (!inTerm && !isWhitespace(string[i])) {
64
+ const caseBody = string[i] === '{';
65
+
66
+ // If there's a previous term, we can either handle a whole
67
+ // case, or add that as an argument.
68
+ if (latestTerm && caseBody) {
69
+ const branchEndIndex = findClosingBracket(string, i);
70
+ if (branchEndIndex === -1) {
71
+ throw new Error(`Unbalanced curly braces in string: "${string}"`);
72
+ }
73
+ cases[latestTerm] = string.slice(i + 1, branchEndIndex); // Don't include the braces
74
+
75
+ i = branchEndIndex; // Will be moved up where needed at end of loop.
76
+ latestTerm = null;
77
+ } else {
78
+ if (latestTerm) {
79
+ args.push(latestTerm);
80
+ latestTerm = null;
81
+ }
82
+ inTerm = true;
83
+ currTermStart = i;
84
+ }
85
+ }
86
+ i++;
87
+ }
88
+ if (inTerm) {
89
+ latestTerm = string.slice(currTermStart);
90
+ }
91
+ if (latestTerm) {
92
+ args.push(latestTerm);
93
+ }
94
+ return {
95
+ args,
96
+ cases
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Finds the index of the matching closing curly bracket, including through
102
+ * strings that could have nested brackets.
103
+ *
104
+ * @param {string} string
105
+ * @param {number} fromIndex
106
+ * @return {number}
107
+ * The index of the matching closing bracket, or -1 if no closing bracket
108
+ * could be found.
109
+ */
110
+ function findClosingBracket(string, fromIndex) {
111
+ let depth = 0;
112
+ for (let i = fromIndex + 1; i < string.length; i++) {
113
+ let char = string.charAt(i);
114
+ if (char === '}') {
115
+ if (depth === 0) {
116
+ return i;
117
+ }
118
+ depth--;
119
+ } else if (char === '{') {
120
+ depth++;
121
+ }
122
+ }
123
+ return -1;
124
+ }
125
+
126
+ /**
127
+ * Split a `{key, type, format}` block into those 3 parts, taking into account
128
+ * nested message syntax that can exist in the `format` part.
129
+ *
130
+ * @param {string} block
131
+ * @return {string[]}
132
+ * An array with `key`, `type`, and `format` items in that order, if present
133
+ * in the formatted argument block.
134
+ */
135
+ function splitFormattedArgument(block) {
136
+ return split(block.slice(1, -1), ',', 3);
137
+ }
138
+
139
+ /**
140
+ * Like `String.prototype.split()` but where the limit parameter causes the
141
+ * remainder of the string to be grouped together in a final entry.
142
+ *
143
+ * @private
144
+ * @param {string} string
145
+ * @param {string} separator
146
+ * @param {number} limit
147
+ * @param {string[]} accumulator
148
+ * @return {string[]}
149
+ */
150
+ function split(string, separator, limit) {
151
+ let accumulator = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
152
+ if (!string) {
153
+ return accumulator;
154
+ }
155
+ if (limit === 1) {
156
+ accumulator.push(string);
157
+ return accumulator;
158
+ }
159
+ let indexOfDelimiter = string.indexOf(separator);
160
+ if (indexOfDelimiter === -1) {
161
+ accumulator.push(string);
162
+ return accumulator;
163
+ }
164
+ let head = string.substring(0, indexOfDelimiter).trim();
165
+ let tail = string.substring(indexOfDelimiter + separator.length + 1).trim();
166
+ accumulator.push(head);
167
+ return split(tail, separator, limit - 1, accumulator);
168
+ }
169
+
170
+ /*
171
+ * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)
172
+ *
173
+ * Licensed under the Apache License, Version 2.0 (the "License");
174
+ * you may not use this file except in compliance with the License.
175
+ * You may obtain a copy of the License at
176
+ *
177
+ * http://www.apache.org/licenses/LICENSE-2.0
178
+ *
179
+ * Unless required by applicable law or agreed to in writing, software
180
+ * distributed under the License is distributed on an "AS IS" BASIS,
181
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
182
+ * See the License for the specific language governing permissions and
183
+ * limitations under the License.
184
+ */
185
+
186
+
187
+ /**
188
+ * @typedef {Record<string,any>} FormatValues
189
+ */
190
+
191
+ /**
192
+ * @callback ProcessFunction
193
+ * @param {string} message
194
+ * @param {FormatValues} [values={}]
195
+ * @return {any[]}
196
+ */
197
+
198
+ /**
199
+ * @callback TypeHandler
200
+ * @param {any} value
201
+ * The object which matched the key of the block being processed.
202
+ * @param {string} matches
203
+ * Any format options associated with the block being processed.
204
+ * @param {string} locale
205
+ * The locale to use for formatting.
206
+ * @param {FormatValues} values
207
+ * The object of placeholder data given to the original `format`/`process`
208
+ * call.
209
+ * @param {ProcessFunction} process
210
+ * The `process` function itself so that sub-messages can be processed by type
211
+ * handlers.
212
+ * @return {any | any[]}
213
+ */
214
+
215
+ /**
216
+ * The main class for formatting messages.
217
+ *
218
+ * @author Emanuel Rabina
219
+ */
220
+ class MessageFormatter {
221
+ /**
222
+ * Creates a new formatter that can work using any of the custom type handlers
223
+ * you register.
224
+ *
225
+ * @param {string} locale
226
+ * @param {Record<string,TypeHandler>} [typeHandlers]
227
+ * Optional object where the keys are the names of the types to register,
228
+ * their values being the functions that will return a nicely formatted
229
+ * string for the data and locale they are given.
230
+ */
231
+ constructor(locale) {
232
+ let typeHandlers = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
233
+ this.locale = locale;
234
+ this.typeHandlers = typeHandlers;
235
+ }
236
+
237
+ /**
238
+ * Formats an ICU message syntax string using `values` for placeholder data
239
+ * and any currently-registered type handlers.
240
+ *
241
+ * @type {(message: string, values?: FormatValues) => string}
242
+ */
243
+ format = functionUtils.memoize((() => {
244
+ var _this = this;
245
+ return function (message) {
246
+ let values = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
247
+ return _this.process(message, values).flat(Infinity).join('');
248
+ };
249
+ })());
250
+
251
+ /**
252
+ * Process an ICU message syntax string using `values` for placeholder data
253
+ * and any currently-registered type handlers. The result of this method is
254
+ * an array of the component parts after they have been processed in turn by
255
+ * their own type handlers. This raw output is useful for other renderers,
256
+ * eg: React where components can be used instead of being forced to return
257
+ * raw strings.
258
+ *
259
+ * This method is used by {@link MessageFormatter#format} where it acts as a
260
+ * string renderer.
261
+ *
262
+ * @param {string} message
263
+ * @param {FormatValues} [values]
264
+ * @return {any[]}
265
+ */
266
+ process(message) {
267
+ let values = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
268
+ if (!message) {
269
+ return [];
270
+ }
271
+ let blockStartIndex = message.indexOf('{');
272
+ if (blockStartIndex !== -1) {
273
+ let blockEndIndex = findClosingBracket(message, blockStartIndex);
274
+ if (blockEndIndex !== -1) {
275
+ let block = message.substring(blockStartIndex, blockEndIndex + 1);
276
+ if (block) {
277
+ let result = [];
278
+ let head = message.substring(0, blockStartIndex);
279
+ if (head) {
280
+ result.push(head);
281
+ }
282
+ let [key, type, format] = splitFormattedArgument(block);
283
+ let body = values[key];
284
+ if (body === null || body === undefined) {
285
+ body = '';
286
+ }
287
+ let typeHandler = type && this.typeHandlers[type];
288
+ result.push(typeHandler ? typeHandler(body, format, this.locale, values, this.process.bind(this)) : body);
289
+ let tail = message.substring(blockEndIndex + 1);
290
+ if (tail) {
291
+ result.push(this.process(tail, values));
292
+ }
293
+ return result;
294
+ }
295
+ } else {
296
+ throw new Error(`Unbalanced curly braces in string: "${message}"`);
297
+ }
298
+ }
299
+ return [message];
300
+ }
301
+ }
302
+
303
+ /*
304
+ * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)
305
+ *
306
+ * Licensed under the Apache License, Version 2.0 (the "License");
307
+ * you may not use this file except in compliance with the License.
308
+ * You may obtain a copy of the License at
309
+ *
310
+ * http://www.apache.org/licenses/LICENSE-2.0
311
+ *
312
+ * Unless required by applicable law or agreed to in writing, software
313
+ * distributed under the License is distributed on an "AS IS" BASIS,
314
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
315
+ * See the License for the specific language governing permissions and
316
+ * limitations under the License.
317
+ */
318
+
319
+ let pluralFormatter;
320
+ let keyCounter = 0;
321
+
322
+ // All the special keywords that can be used in `plural` blocks for the various branches
323
+ const ONE = 'one';
324
+ const OTHER$1 = 'other';
325
+
326
+ /**
327
+ * @private
328
+ * @param {string} caseBody
329
+ * @param {number} value
330
+ * @return {{caseBody: string, numberValues: object}}
331
+ */
332
+ function replaceNumberSign(caseBody, value) {
333
+ let i = 0;
334
+ let output = '';
335
+ let numBraces = 0;
336
+ const numberValues = {};
337
+ while (i < caseBody.length) {
338
+ if (caseBody[i] === '#' && !numBraces) {
339
+ let keyParam = `__hashToken${keyCounter++}`;
340
+ output += `{${keyParam}, number}`;
341
+ numberValues[keyParam] = value;
342
+ } else {
343
+ output += caseBody[i];
344
+ }
345
+ if (caseBody[i] === '{') {
346
+ numBraces++;
347
+ } else if (caseBody[i] === '}') {
348
+ numBraces--;
349
+ }
350
+ i++;
351
+ }
352
+ return {
353
+ caseBody: output,
354
+ numberValues
355
+ };
356
+ }
357
+
358
+ /**
359
+ * Handler for `plural` statements within ICU message syntax strings. Returns
360
+ * a formatted string for the branch that closely matches the current value.
361
+ *
362
+ * See https://formatjs.io/docs/core-concepts/icu-syntax#plural-format for more
363
+ * details on how the `plural` statement works.
364
+ *
365
+ * @param {string} value
366
+ * @param {string} matches
367
+ * @param {string} locale
368
+ * @param {Record<string,any>} values
369
+ * @param {(message: string, values?: Record<string,any>) => any[]} process
370
+ * @return {any | any[]}
371
+ */
372
+ function pluralTypeHandler(value) {
373
+ let matches = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
374
+ let locale = arguments.length > 2 ? arguments[2] : undefined;
375
+ let values = arguments.length > 3 ? arguments[3] : undefined;
376
+ let process = arguments.length > 4 ? arguments[4] : undefined;
377
+ const {
378
+ args,
379
+ cases
380
+ } = parseCases(matches);
381
+ let intValue = parseInt(value);
382
+ args.forEach(arg => {
383
+ if (arg.startsWith('offset:')) {
384
+ intValue -= parseInt(arg.slice('offset:'.length));
385
+ }
386
+ });
387
+ const keywordPossibilities = [];
388
+ if ('PluralRules' in Intl) {
389
+ // Effectively memoize because instantiation of `Int.*` objects is expensive.
390
+ if (pluralFormatter === undefined || pluralFormatter.resolvedOptions().locale !== locale) {
391
+ pluralFormatter = new Intl.PluralRules(locale);
392
+ }
393
+ const pluralKeyword = pluralFormatter.select(intValue);
394
+
395
+ // Other is always added last with least priority, so we don't want to add it here.
396
+ if (pluralKeyword !== OTHER$1) {
397
+ keywordPossibilities.push(pluralKeyword);
398
+ }
399
+ }
400
+ if (intValue === 1) {
401
+ keywordPossibilities.push(ONE);
402
+ }
403
+ keywordPossibilities.push(`=${intValue}`, OTHER$1);
404
+ for (let i = 0; i < keywordPossibilities.length; i++) {
405
+ const keyword = keywordPossibilities[i];
406
+ if (keyword in cases) {
407
+ const {
408
+ caseBody,
409
+ numberValues
410
+ } = replaceNumberSign(cases[keyword], intValue);
411
+ return process(caseBody, {
412
+ ...values,
413
+ ...numberValues
414
+ });
415
+ }
416
+ }
417
+ return value;
418
+ }
419
+
420
+ /*
421
+ * Copyright 2019, Emanuel Rabina (http://www.ultraq.net.nz/)
422
+ *
423
+ * Licensed under the Apache License, Version 2.0 (the "License");
424
+ * you may not use this file except in compliance with the License.
425
+ * You may obtain a copy of the License at
426
+ *
427
+ * http://www.apache.org/licenses/LICENSE-2.0
428
+ *
429
+ * Unless required by applicable law or agreed to in writing, software
430
+ * distributed under the License is distributed on an "AS IS" BASIS,
431
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
432
+ * See the License for the specific language governing permissions and
433
+ * limitations under the License.
434
+ */
435
+
436
+ const OTHER = 'other';
437
+
438
+ /**
439
+ * Handler for `select` statements within ICU message syntax strings. Returns
440
+ * a formatted string for the branch that closely matches the current value.
441
+ *
442
+ * See https://formatjs.io/docs/core-concepts/icu-syntax#select-format for more
443
+ * details on how the `select` statement works.
444
+ *
445
+ * @param {string} value
446
+ * @param {string} matches
447
+ * @param {string} locale
448
+ * @param {Record<string,any>} values
449
+ * @param {(message: string, values?: Record<string,any>) => any[]} process
450
+ * @return {any | any[]}
451
+ */
452
+ function selectTypeHandler(value) {
453
+ let matches = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
454
+ let values = arguments.length > 3 ? arguments[3] : undefined;
455
+ let process = arguments.length > 4 ? arguments[4] : undefined;
456
+ const {
457
+ cases
458
+ } = parseCases(matches);
459
+ if (value in cases) {
460
+ return process(cases[value], values);
461
+ } else if (OTHER in cases) {
462
+ return process(cases[OTHER], values);
463
+ }
464
+ return value;
465
+ }
466
+
467
+ exports.MessageFormatter = MessageFormatter;
468
+ exports.findClosingBracket = findClosingBracket;
469
+ exports.parseCases = parseCases;
470
+ exports.pluralTypeHandler = pluralTypeHandler;
471
+ exports.selectTypeHandler = selectTypeHandler;
472
+ exports.splitFormattedArgument = splitFormattedArgument;
473
+ //# sourceMappingURL=icu-message-formatter.cjs.map