├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── documentation └── asset │ ├── logo.png │ └── logo.svg ├── package.json ├── prettier.config.js ├── rollup.config.js ├── scaffold.config.js ├── scripts └── build-data │ ├── build-data.ts │ └── ts │ └── create-program-from-sources.ts ├── src ├── assert │ ├── is-list.ts │ └── is-record.ts ├── create-parts-from-list │ └── create-parts-from-list.ts ├── deconstruct-pattern │ └── deconstruct-pattern.ts ├── default-locale │ └── get-default-locale.ts ├── format-list-to-parts │ └── format-list-to-parts.ts ├── format-list │ └── format-list.ts ├── index.ts ├── internal-slot │ ├── internal-slot.ts │ ├── list-format-instance-internals.ts │ └── list-format-static-internals.ts ├── list-format-options │ └── list-format-options.ts ├── list-format │ └── list-format.ts ├── list-partition │ └── list-partition.ts ├── list │ └── list.ts ├── locale-matcher │ └── locale-matcher.ts ├── locale │ ├── locale-data.ts │ ├── locale.ts │ └── locales.ts ├── matcher │ ├── best-available-locale │ │ └── best-available-locale.ts │ ├── best-fit-matcher │ │ └── best-fit-matcher.ts │ ├── lookup-matcher │ │ └── lookup-matcher.ts │ ├── matcher-options.ts │ └── matcher-result.ts ├── patch │ └── patch.ts ├── placeables │ └── placeables.ts ├── relevant-extension-key │ └── relevant-extension-key.ts ├── resolve-locale │ ├── resolve-locale-options.ts │ ├── resolve-locale-result.ts │ └── resolve-locale.ts ├── resolved-list-format-options │ └── resolved-list-format-options.ts ├── string-list-from-iterable.ts ├── style │ └── style.ts ├── support │ └── supports-intl-list-format.ts ├── supported-locales-options │ └── supported-locales-options.ts ├── supported-locales │ ├── best-fit-supported-locales.ts │ ├── lookup-supported-locales.ts │ ├── supported-locales-options.ts │ └── supported-locales.ts ├── test262.ts ├── type │ └── type.ts ├── typings.d.ts ├── unicode-extension │ └── unicode-extension.ts └── util │ ├── element-of.ts │ ├── get-option.ts │ ├── get.ts │ ├── is-property-key.ts │ ├── to-boolean.ts │ ├── to-number.ts │ ├── to-object.ts │ └── to-string.ts ├── test ├── format-to-parts.test.ts ├── format.test.ts ├── resolved-options.test.ts └── supported-locales-of.test.ts ├── test262-runner.js ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /compiled/ 3 | /locale-data/ 4 | /test262-polyfill.js* 5 | scripts/build-data/compiled/ 6 | /dist/ 7 | /typings/ 8 | package-lock.json 9 | /.idea/ 10 | /.cache/ 11 | /.vscode/ 12 | *.log 13 | /logs/ 14 | npm-debug.log* 15 | /lib-cov/ 16 | /coverage/ 17 | /.nyc_output/ 18 | /.grunt/ 19 | *.7z 20 | *.dmg 21 | *.gz 22 | *.iso 23 | *.jar 24 | *.rar 25 | *.tar 26 | *.zip 27 | .tgz 28 | .env 29 | .DS_Store 30 | .DS_Store? 31 | ._* 32 | .Spotlight-V100 33 | .Trashes 34 | ehthumbs.db 35 | Thumbs.db 36 | *.pem 37 | *.p12 38 | *.crt 39 | *.csr -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test262"] 2 | path = test262 3 | url = https://github.com/tc39/test262.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.3](https://github.com/wessberg/intl-list-format/compare/v1.0.2...v1.0.3) (2019-07-09) 2 | 3 | ### Bug Fixes 4 | 5 | - **conformance:** Makes the polyfill pass all 134 test262 conformance tests ([5368709](https://github.com/wessberg/intl-list-format/commit/5368709)) 6 | 7 | ## [1.0.2](https://github.com/wessberg/intl-list-format/compare/v1.0.1...v1.0.2) (2019-02-09) 8 | 9 | ## 1.0.1 (2019-02-07) 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Contributor Covenant Code of Conduct 2 | 3 | Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting any of the code of conduct enforcers: [Frederik Wessberg](mailto:frederikwessberg@hotmail.com) ([@FredWessberg](https://twitter.com/FredWessberg)) ([Website](https://github.com/wessberg)). 59 | All complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | Attribution 69 | 70 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 71 | available at http://contributor-covenant.org/version/1/4/ 72 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | You are more than welcome to contribute to `intl-list-format` in any way you please, including: 2 | 3 | - Updating documentation. 4 | - Fixing spelling and grammar 5 | - Adding tests 6 | - Fixing issues and suggesting new features 7 | - Blogging, tweeting, and creating tutorials about `intl-list-format` 8 | - Reaching out to [@FredWessberg](https://twitter.com/FredWessberg) on Twitter 9 | - Submit an issue or a Pull Request 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2019 [Frederik Wessberg](mailto:frederikwessberg@hotmail.com) ([@FredWessberg](https://twitter.com/FredWessberg)) ([Website](https://github.com/wessberg)) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Logo
4 | 5 | 6 | 7 | 8 | 9 | > A fully spec-compliant polyfill for 'Intl.ListFormat' 10 | 11 | 12 | 13 | 14 | 15 | Downloads per month 16 | NPM version 17 | Dependencies 18 | Contributors 19 | code style: prettier 20 | License: MIT 21 | Support on Patreon 22 | 23 | 24 | 25 | 26 | 27 | ## Description 28 | 29 | 30 | 31 | This is a 1:1 implementation of the [`Intl.ListFormat`](https://github.com/tc39/proposal-intl-list-format) draft spec proposal ECMA-402, or the ECMAScript® Internationalization API Specification. 32 | 33 | The `Intl.ListFormat` object is a constructor for objects that enable language-sensitive list formatting. 34 | It is a really useful low-level primitive to build on top of which avoids the need to parse lots of CLDR raw data at the expense of your users and their internet connections. 35 | 36 | It builds upon another member of the `Intl` family: `Intl.getCanonicalLocales`, so this must be polyfilled. [See this section for an overview](#dependencies--browser-support). 37 | 38 | This implementation passes all 134 [Test262 Conformance tests](https://github.com/tc39/test262) from the Official ECMAScript Conformance Test Suite. 39 | 40 | 41 | 42 | ### Features 43 | 44 | 45 | 46 | Some highlights of this polyfill include: 47 | 48 | - A very precise implementation of the spec, with cross-references inlined in the source code 49 | - Conditional loading of Locale data for all CLDR locales 50 | - Well-tested and well-documented. 51 | - Passes all Official ECMAScript Conformance Tests 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ## Table of Contents 60 | 61 | - [Description](#description) 62 | - [Features](#features) 63 | - [Table of Contents](#table-of-contents) 64 | - [Install](#install) 65 | - [NPM](#npm) 66 | - [Yarn](#yarn) 67 | - [Applying the polyfill](#applying-the-polyfill) 68 | - [Loading locale data](#loading-locale-data) 69 | - [Usage](#usage) 70 | - [Intl.ListFormat.prototype.format](#intllistformatprototypeformat) 71 | - [Intl.ListFormat.prototype.formatToParts](#intllistformatprototypeformattoparts) 72 | - [Intl.ListFormat.prototype.resolvedOptions](#intllistformatprototyperesolvedoptions) 73 | - [Intl.ListFormat.supportedLocalesOf](#intllistformatsupportedlocalesof) 74 | - [Dependencies & Browser support](#dependencies--browser-support) 75 | - [Contributing](#contributing) 76 | - [Maintainers](#maintainers) 77 | - [Backers](#backers) 78 | - [Patreon](#patreon) 79 | - [FAQ](#faq) 80 | - [What is the default locale?](#what-is-the-default-locale) 81 | - [Are there any known quirks?](#are-there-any-known-quirks) 82 | - [License](#license) 83 | 84 | 85 | 86 | 87 | 88 | ## Install 89 | 90 | ### NPM 91 | 92 | ``` 93 | $ npm install intl-list-format 94 | ``` 95 | 96 | ### Yarn 97 | 98 | ``` 99 | $ yarn add intl-list-format 100 | ``` 101 | 102 | 103 | 104 | ## Applying the polyfill 105 | 106 | The polyfill will check for the existence of `Intl.ListFormat` and will _only_ be applied if the runtime doesn't already support it. 107 | 108 | To include it, add this somewhere: 109 | 110 | ```typescript 111 | import "intl-list-format"; 112 | 113 | // Or with commonjs: 114 | require("intl-list-format"); 115 | ``` 116 | 117 | However, it is strongly suggested that you only include the polyfill for runtimes that don't already support `Intl.ListFormat`. 118 | One way to do so is with an async import: 119 | 120 | ```typescript 121 | if (!("ListFormat" in Intl)) { 122 | await import("intl-list-format"); 123 | 124 | // or with commonjs: 125 | require("intl-list-format"); 126 | } 127 | ``` 128 | 129 | Alternatively, you can use [Polyfill.app](https://github.com/wessberg/Polyfiller) which uses this polyfill and takes care of only loading the polyfill if needed as well as adding the language features that the polyfill depends on (See [dependencies](#dependencies--browser-support)). 130 | 131 | ## Loading locale data 132 | 133 | By default, no CLDR locale data is loaded. Instead, _you_ decide what data you want. 134 | To load data, you can import it via the `/locale-data` subfolder that comes with the NPM package: 135 | 136 | With ES modules: 137 | 138 | ```typescript 139 | // Load the polyfill 140 | import "intl-list-format"; 141 | 142 | // Load data for the 'en' locale 143 | import "intl-list-format/locale-data/en"; 144 | ``` 145 | 146 | And naturally, it also works with commonjs: 147 | 148 | ```typescript 149 | // Load the polyfill 150 | require("intl-list-format"); 151 | 152 | // Load data for the 'en' locale 153 | require("intl-list-format/locale-data/en"); 154 | ``` 155 | 156 | Remember, if you're also depending on a polyfilled version of `Intl.getCanonicalLocales`, you will need to import that polyfill beforehand. 157 | 158 | 159 | 160 | ## Usage 161 | 162 | 163 | 164 | The following examples are taken [directly from the original proposal](https://github.com/tc39/proposal-intl-list-format) 165 | 166 | ### Intl.ListFormat.prototype.format 167 | 168 | ```typescript 169 | // Create a list formatter in your locale 170 | // with default values explicitly passed in. 171 | const lf = new Intl.ListFormat("en", { 172 | localeMatcher: "best fit", // other values: "lookup" 173 | type: "conjunction", // "conjunction", "disjunction" or "unit" 174 | style: "long" // other values: "short" or "narrow" 175 | }); 176 | 177 | lf.format(["Motorcycle", "Truck", "Car"]); 178 | // > "Motorcycle, Truck, and Car" 179 | ``` 180 | 181 | ### Intl.ListFormat.prototype.formatToParts 182 | 183 | ```typescript 184 | const lf = new Intl.ListFormat("en"); 185 | lf.formatToParts(["Foo", "Bar", "Baz"]); 186 | // > [ 187 | // > {type: "element", value: "Foo"}, 188 | // > {type: "literal", value: ", "}, 189 | // > {type: "element", value: "Bar"}, 190 | // > {type: "literal", value: ", and "}, 191 | // > {type: "element", value: "Baz"} 192 | // > ] 193 | ``` 194 | 195 | ### Intl.ListFormat.prototype.resolvedOptions 196 | 197 | ```typescript 198 | const lf = new Intl.ListFormat("en", {type: "unit", style: "narrow"}); 199 | 200 | lf.resolvedOptions(); 201 | // > {locale: "en", style: "narrow", type: "unit"} 202 | ``` 203 | 204 | ### Intl.ListFormat.supportedLocalesOf 205 | 206 | ```typescript 207 | Intl.ListFormat.supportedLocalesOf(["foo", "bar", "en-US"]); 208 | // > ["en-US"] 209 | ``` 210 | 211 | ## Dependencies & Browser support 212 | 213 | This polyfill is distributed in ES3-compatible syntax, but is using some additional APIs and language features which must be available: 214 | 215 | - `Array.prototype.includes` 216 | - `Object.create` 217 | - `String.prototype.replace` 218 | - `Symbol.toStringTag`, 219 | - `WeakMap` 220 | - `Intl.getCanonicalLocales` 221 | 222 | For by far the most browsers, these features will already be natively available. 223 | Generally, I would highly recommend using something like [Polyfill.app](https://github.com/wessberg/Polyfiller) which takes care of this stuff automatically. 224 | 225 | 226 | 227 | ## Contributing 228 | 229 | Do you want to contribute? Awesome! Please follow [these recommendations](./CONTRIBUTING.md). 230 | 231 | 232 | 233 | 234 | 235 | ## Maintainers 236 | 237 | | Frederik Wessberg | 238 | | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | 239 | | [Frederik Wessberg](mailto:frederikwessberg@hotmail.com)
Twitter: [@FredWessberg](https://twitter.com/FredWessberg)
_Lead Developer_ | 240 | 241 | 242 | 243 | 244 | 245 | ## Backers 246 | 247 | ### Patreon 248 | 249 | [Become a backer](https://www.patreon.com/bePatron?u=11315442) and get your name, avatar, and Twitter handle listed here. 250 | 251 | Backers on Patreon 252 | 253 | 254 | 255 | 256 | 257 | ## FAQ 258 | 259 | 260 | 261 | ### What is the default locale? 262 | 263 | The default locale will be equal to the locale file you load first. 264 | 265 | ### Are there any known quirks? 266 | 267 | Nope! 268 | 269 | 270 | 271 | ## License 272 | 273 | MIT © [Frederik Wessberg](mailto:frederikwessberg@hotmail.com) ([@FredWessberg](https://twitter.com/FredWessberg)) ([Website](https://github.com/wessberg)) 274 | 275 | 276 | -------------------------------------------------------------------------------- /documentation/asset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wessberg/intl-list-format/dd1b4cbdd77947445e108ed2cb83dadabe51a0d1/documentation/asset/logo.png -------------------------------------------------------------------------------- /documentation/asset/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intl-list-format", 3 | "version": "1.0.3", 4 | "description": "A fully spec-compliant polyfill for 'Intl.ListFormat'", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/wessberg/intl-list-format.git" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/wessberg/intl-list-format/issues" 11 | }, 12 | "scripts": { 13 | "generate:readme": "scaffold readme --yes", 14 | "generate:license": "scaffold license --yes", 15 | "generate:contributing": "scaffold contributing --yes", 16 | "generate:coc": "scaffold coc --yes", 17 | "generate:changelog": "standard-changelog --first-release", 18 | "generate:all": "npm run generate:license & npm run generate:contributing & npm run generate:coc & npm run generate:readme && npm run generate:changelog", 19 | "clean:dist": "rm -rf dist", 20 | "clean": "npm run clean:dist", 21 | "lint": "tsc --noEmit && tslint -c tslint.json --project tsconfig.json", 22 | "prettier": "prettier --write '{src,test,documentation}/**/*.{js,ts,json,html,xml,css,md}'", 23 | "test": "ava", 24 | "test262": "node test262-runner.js", 25 | "prebuild": "npm run clean:dist", 26 | "build": "npm run rollup", 27 | "watch": "npm run rollup -- --watch", 28 | "build_data": "ts-node scripts/build-data/build-data.ts", 29 | "rollup": "rollup -c rollup.config.js", 30 | "preversion": "npm run lint && npm run build_data && NODE_ENV=production npm run build", 31 | "version": "npm run generate:all && git add .", 32 | "release": "np --no-cleanup --no-yarn" 33 | }, 34 | "files": [ 35 | "dist/**/*.*", 36 | "locale-data/**/*.*" 37 | ], 38 | "keywords": [ 39 | "intl", 40 | "ListFormat", 41 | "locale", 42 | "ecma-402", 43 | "internationalization", 44 | "i18n", 45 | "formatting", 46 | "polyfill", 47 | "list", 48 | "ECMAScript internationalization API" 49 | ], 50 | "contributors": [ 51 | { 52 | "name": "Frederik Wessberg", 53 | "email": "frederikwessberg@hotmail.com", 54 | "url": "https://github.com/wessberg", 55 | "imageUrl": "https://avatars2.githubusercontent.com/u/20454213?s=460&v=4", 56 | "role": "Lead Developer", 57 | "twitter": "FredWessberg" 58 | } 59 | ], 60 | "license": "MIT", 61 | "devDependencies": { 62 | "@types/find-up": "^2.1.1", 63 | "@wessberg/rollup-plugin-ts": "1.1.60", 64 | "@wessberg/scaffold": "1.0.19", 65 | "@wessberg/ts-config": "^0.0.41", 66 | "@wessberg/browserslist-generator": "^1.0.23", 67 | "babel-preset-minify": "0.5.0", 68 | "test262-harness": "^6.3.2", 69 | "ava": "^2.2.0", 70 | "cldr": "^5.3.0", 71 | "find-up": "^4.1.0", 72 | "rollup": "^1.16.7", 73 | "rollup-plugin-node-resolve": "^5.2.0", 74 | "standard-changelog": "^2.0.11", 75 | "javascript-stringify": "^2.0.0", 76 | "tslib": "^1.10.0", 77 | "tslint": "^5.18.0", 78 | "typescript": "^3.5.3", 79 | "prettier": "^1.18.2", 80 | "pretty-quick": "^1.11.1", 81 | "husky": "^3.0.0", 82 | "np": "^5.0.3", 83 | "ts-node": "8.3.0", 84 | "rollup-plugin-multi-entry": "2.1.0", 85 | "full-icu": "1.3.0" 86 | }, 87 | "dependencies": {}, 88 | "test262": "./test262-polyfill.js", 89 | "minified": "./dist/index.min.js", 90 | "main": "./dist/index.js", 91 | "module": "./dist/index.js", 92 | "browser": "./dist/index.js", 93 | "types": "./dist/index.d.ts", 94 | "typings": "./dist/index.d.ts", 95 | "es2015": "./dist/index.js", 96 | "engines": { 97 | "node": ">=4.0.0" 98 | }, 99 | "husky": { 100 | "hooks": { 101 | "pre-commit": "pretty-quick --staged" 102 | } 103 | }, 104 | "ava": { 105 | "files": [ 106 | "test/*.test.ts" 107 | ], 108 | "compileEnhancements": false, 109 | "extensions": [ 110 | "ts" 111 | ], 112 | "require": [ 113 | "ts-node/register" 114 | ] 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@wessberg/ts-config/prettier.config"); 2 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import {browsersWithSupportForEcmaVersion} from "@wessberg/browserslist-generator"; 2 | import ts from "@wessberg/rollup-plugin-ts"; 3 | import resolve from "rollup-plugin-node-resolve"; 4 | import multiEntry from "rollup-plugin-multi-entry"; 5 | import packageJson from "./package.json"; 6 | 7 | const SHARED_OPTIONS = { 8 | treeshake: true 9 | }; 10 | 11 | export default [ 12 | { 13 | ...SHARED_OPTIONS, 14 | input: "src/index.ts", 15 | output: [ 16 | { 17 | file: packageJson.module, 18 | format: "esm", 19 | sourcemap: true 20 | } 21 | ], 22 | plugins: [ 23 | ts({ 24 | transpiler: "babel", 25 | browserslist: browsersWithSupportForEcmaVersion("es2020") 26 | }), 27 | resolve() 28 | ] 29 | }, 30 | { 31 | ...SHARED_OPTIONS, 32 | input: "src/index.ts", 33 | output: [ 34 | { 35 | file: packageJson.main, 36 | format: "iife", 37 | sourcemap: true 38 | } 39 | ], 40 | plugins: [ 41 | ts({ 42 | transpiler: "babel", 43 | browserslist: browsersWithSupportForEcmaVersion("es5") 44 | }), 45 | resolve() 46 | ] 47 | }, 48 | { 49 | ...SHARED_OPTIONS, 50 | input: "src/index.ts", 51 | output: [ 52 | { 53 | file: packageJson.minified, 54 | format: "iife", 55 | sourcemap: true 56 | } 57 | ], 58 | plugins: [ 59 | ts({ 60 | transpiler: "babel", 61 | browserslist: browsersWithSupportForEcmaVersion("es5"), 62 | babelConfig: { 63 | comments: false, 64 | minified: true, 65 | compact: true, 66 | presets: [["minify", {builtIns: false}]] 67 | } 68 | }), 69 | resolve() 70 | ] 71 | }, 72 | { 73 | ...SHARED_OPTIONS, 74 | input: [ 75 | "src/test262.ts", 76 | "locale-data/en.js", 77 | "locale-data/en-GB.js", 78 | "locale-data/en-US.js", 79 | "locale-data/es.js", 80 | "locale-data/es-ES.js", 81 | "locale-data/de.js" 82 | ], 83 | output: [ 84 | { 85 | file: packageJson.test262, 86 | format: "iife", 87 | sourcemap: true 88 | } 89 | ], 90 | plugins: [ 91 | multiEntry(), 92 | ts({ 93 | tsconfig: resolvedOptions => ({...resolvedOptions, declaration: false}) 94 | }), 95 | resolve() 96 | ] 97 | } 98 | ]; 99 | -------------------------------------------------------------------------------- /scaffold.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require("@wessberg/ts-config/scaffold.config"), 3 | logo: { 4 | url: 5 | "https://raw.githubusercontent.com/wessberg/intl-list-format/master/documentation/asset/logo.png", 6 | height: 60 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /scripts/build-data/build-data.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import {extractListPatterns, localeIds} from "cldr"; 3 | import {sync} from "find-up"; 4 | import {dirname, join} from "path"; 5 | import {existsSync, mkdirSync} from "fs"; 6 | import {createProgramFromSources, SourceFileInput} from "./ts/create-program-from-sources"; 7 | import {LocaleDataEntry, LocaleDataEntryValueValueValue} from "../../src/locale/locale-data"; 8 | import {stringify} from "javascript-stringify"; 9 | 10 | // The directory on disk to write locale files to 11 | const OUTPUT_DIRECTORY = join(dirname(sync("package.json")!), "locale-data"); 12 | 13 | // Ensure that the output directory exists 14 | if (!existsSync(OUTPUT_DIRECTORY)) { 15 | mkdirSync(OUTPUT_DIRECTORY); 16 | } 17 | // Prepare sources 18 | const sources: SourceFileInput[] = []; 19 | 20 | /** 21 | * Formats a LocaleDataEntryValueValueValue based on the given input 22 | * @param {object} value 23 | * @returns{LocaleDataEntryValueValueValue} 24 | */ 25 | function formatLocaleDataEntryValueValueValue(value: {"2": string; start: string; middle: string; end: string}): LocaleDataEntryValueValueValue { 26 | return { 27 | Pair: value["2"], 28 | Start: value.start, 29 | Middle: value.middle, 30 | End: value.end 31 | }; 32 | } 33 | 34 | // Loop through all locales 35 | for (const localeId of localeIds) { 36 | // @ts-ignore 37 | const locale = Intl.getCanonicalLocales(localeId.replace(/_/g, "-"))[0]; 38 | console.log(`Building data for locale: ${locale} (localeId: ${localeId})`); 39 | 40 | // Extract List patterns 41 | const patterns = extractListPatterns(localeId); 42 | 43 | const localeDataEntry: LocaleDataEntry = { 44 | formats: { 45 | conjunction: { 46 | long: formatLocaleDataEntryValueValueValue(patterns.standard || patterns.standardLong || patterns.default), 47 | short: formatLocaleDataEntryValueValueValue(patterns.standardShort || patterns.standardNarrow || patterns.standard || patterns.default), 48 | narrow: formatLocaleDataEntryValueValueValue(patterns.standardNarrow || patterns.standardShort || patterns.standard || patterns.default) 49 | }, 50 | disjunction: { 51 | long: formatLocaleDataEntryValueValueValue(patterns.or), 52 | short: formatLocaleDataEntryValueValueValue(patterns.or), 53 | narrow: formatLocaleDataEntryValueValueValue(patterns.or) 54 | }, 55 | unit: { 56 | long: formatLocaleDataEntryValueValueValue(patterns.unit || patterns.default), 57 | short: formatLocaleDataEntryValueValueValue(patterns.unitShort || patterns.unitNarrow || patterns.unit || patterns.default), 58 | narrow: formatLocaleDataEntryValueValueValue(patterns.unitNarrow || patterns.unitShort || patterns.unit || patterns.default) 59 | } 60 | } 61 | }; 62 | 63 | // Add the source to the sources 64 | sources.push({ 65 | fileName: join(OUTPUT_DIRECTORY, `${locale}.ts`), 66 | text: `\ 67 | if ("__addLocaleData" in Intl.ListFormat) { 68 | Intl.ListFormat.__addLocaleData({ 69 | locale: "${locale}", 70 | data: ${stringify(localeDataEntry, undefined, " ")} 71 | }); 72 | }` 73 | }); 74 | } 75 | 76 | console.log(`Emitting locale data...`); 77 | 78 | // Create a Program from the SourceFiles 79 | const program = createProgramFromSources(sources); 80 | 81 | // Emit all of them! 82 | program.emit(); 83 | 84 | console.log(`Successfully built data!`); 85 | -------------------------------------------------------------------------------- /scripts/build-data/ts/create-program-from-sources.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CompilerOptions, 3 | createProgram, 4 | createSourceFile, 5 | getDefaultCompilerOptions, 6 | getDefaultLibFileName, 7 | Program, 8 | ScriptKind, 9 | ScriptTarget, 10 | SourceFile, 11 | sys 12 | } from "typescript"; 13 | 14 | export interface SourceFileInput { 15 | fileName: string; 16 | text: string; 17 | } 18 | 19 | /** 20 | * Generates a Program based on the given sources 21 | * @returns {Program} 22 | */ 23 | export function createProgramFromSources(sources: SourceFileInput[]): Program { 24 | return createProgram({ 25 | rootNames: sources.map(source => source.fileName), 26 | host: { 27 | readFile(fileName: string): string | undefined { 28 | const matchedFile = sources.find(file => file.fileName === fileName); 29 | return matchedFile == null ? undefined : matchedFile.text; 30 | }, 31 | 32 | fileExists(fileName: string): boolean { 33 | return this.readFile(fileName) != null; 34 | }, 35 | 36 | getSourceFile( 37 | fileName: string, 38 | languageVersion: ScriptTarget 39 | ): SourceFile | undefined { 40 | const sourceText = this.readFile(fileName); 41 | if (sourceText == null) return undefined; 42 | 43 | return createSourceFile( 44 | fileName, 45 | sourceText, 46 | languageVersion, 47 | true, 48 | ScriptKind.TS 49 | ); 50 | }, 51 | 52 | getCurrentDirectory() { 53 | return "."; 54 | }, 55 | 56 | getDirectories(directoryName: string) { 57 | return sys.getDirectories(directoryName); 58 | }, 59 | 60 | getDefaultLibFileName(options: CompilerOptions): string { 61 | return getDefaultLibFileName(options); 62 | }, 63 | 64 | getCanonicalFileName(fileName: string): string { 65 | return this.useCaseSensitiveFileNames() 66 | ? fileName 67 | : fileName.toLowerCase(); 68 | }, 69 | 70 | getNewLine(): string { 71 | return sys.newLine; 72 | }, 73 | 74 | useCaseSensitiveFileNames() { 75 | return sys.useCaseSensitiveFileNames; 76 | }, 77 | 78 | writeFile( 79 | fileName: string, 80 | data: string, 81 | writeByteOrderMark: boolean = false 82 | ) { 83 | console.log("write file:", fileName); 84 | sys.writeFile(fileName, data, writeByteOrderMark); 85 | } 86 | }, 87 | options: { 88 | ...getDefaultCompilerOptions(), 89 | declaration: true, 90 | declarationMap: true 91 | } 92 | }); 93 | } 94 | -------------------------------------------------------------------------------- /src/assert/is-list.ts: -------------------------------------------------------------------------------- 1 | import {isRecord} from "./is-record"; 2 | import {List} from "../list/list"; 3 | 4 | /** 5 | * Returns true if the given item is a List 6 | * @param {T} item 7 | * @return {item is T} 8 | */ 9 | export function isList(item: unknown): item is List { 10 | return Array.isArray(item) || isRecord(item); 11 | } 12 | -------------------------------------------------------------------------------- /src/assert/is-record.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if the given item is a record 3 | * @param {T} item 4 | * @return {item is T} 5 | */ 6 | export function isRecord(item: T): item is Exclude { 7 | return Object.prototype.toString.call(item) === "[object Object]"; 8 | } 9 | -------------------------------------------------------------------------------- /src/create-parts-from-list/create-parts-from-list.ts: -------------------------------------------------------------------------------- 1 | import {ListFormat} from "../list-format/list-format"; 2 | import {ElementPartition, ListPartition, ListPartitions} from "../list-partition/list-partition"; 3 | import {getInternalSlot} from "../internal-slot/internal-slot"; 4 | import {deconstructPattern} from "../deconstruct-pattern/deconstruct-pattern"; 5 | import {Placeables} from "../placeables/placeables"; 6 | 7 | /** 8 | * The CreatePartsFromList abstract operation is called with arguments listFormat 9 | * (which must be an object initialized as a ListFormat) and list (which must be a List of String values), 10 | * and creates the corresponding list of parts according to the effective locale and the formatting options of listFormat. 11 | * Each part is a Record with two fields: [[Type]], which must be a string with values "element" or "literal", 12 | * and [[Value]] which must be a string or a number. 13 | * @param {ListFormat} listFormat 14 | * @param {string[]} list 15 | * @return {ListPartitions} 16 | */ 17 | export function createPartsFromList(listFormat: ListFormat, list: string[]): ListPartitions { 18 | let pattern: string; 19 | 20 | // Let size be the number of elements of list. 21 | const size = list.length; 22 | 23 | // If size is 0, then 24 | if (size === 0) { 25 | // Return a new empty List. 26 | return []; 27 | } 28 | 29 | // If size is 2, then 30 | if (size === 2) { 31 | // Let pattern be listFormat.[[TemplatePair]]. 32 | pattern = getInternalSlot(listFormat, "templatePair"); 33 | 34 | // Let first be a new Record { [[Type]]: "element", [[Value]]: list[0] }. 35 | const first: ElementPartition = { 36 | type: "element", 37 | value: list[0] 38 | }; 39 | 40 | // Let second be a new Record { [[Type]]: "element", [[Value]]: list[1] }. 41 | const second: ElementPartition = { 42 | type: "element", 43 | value: list[1] 44 | }; 45 | 46 | // Let placeables be a new Record { [[0]]: first, [[1]]: second }. 47 | const placeables: Placeables = { 48 | 0: first, 49 | 1: second 50 | }; 51 | 52 | // Return DeconstructPattern(pattern, placeables). 53 | return deconstructPattern(pattern, placeables); 54 | } 55 | 56 | // Let last be a new Record { [[Type]]: "element", [[Value]]: list[size - 1] }. 57 | const last: ElementPartition = { 58 | type: "element", 59 | value: list[size - 1] 60 | }; 61 | 62 | // Let parts be « last ». 63 | let parts: ListPartition[] = [last]; 64 | 65 | // Let i be size - 2. 66 | let i = size - 2; 67 | 68 | // Repeat, while i ≥ 0 69 | while (i >= 0) { 70 | // If i is 0, then 71 | if (i === 0) { 72 | // Let pattern be listFormat.[[TemplateStart]]. 73 | pattern = getInternalSlot(listFormat, "templateStart"); 74 | } 75 | 76 | // Else, if i is less than size - 2, then 77 | else if (i < size - 2) { 78 | // Let pattern be listFormat.[[TemplateMiddle]]. 79 | pattern = getInternalSlot(listFormat, "templateMiddle"); 80 | } 81 | 82 | // Else, 83 | else { 84 | // Let pattern be listFormat.[[TemplateEnd]]. 85 | pattern = getInternalSlot(listFormat, "templateEnd"); 86 | } 87 | 88 | // Let head be a new Record { [[Type]]: "element", [[Value]]: list[i] }. 89 | const head: ElementPartition = { 90 | type: "element", 91 | value: list[i] 92 | }; 93 | 94 | // Let tail be a new Record { [[Type]]: "element", [[Value]]: parts }. 95 | const tail: ElementPartition = { 96 | type: "element", 97 | value: parts 98 | }; 99 | 100 | // Let placeables be a new Record { [[0]]: head, [[1]]: tail }. 101 | const placeables: Placeables = { 102 | 0: head, 103 | 1: tail 104 | }; 105 | 106 | // Set parts to DeconstructPattern(pattern, placeables). 107 | parts = deconstructPattern(pattern, placeables); 108 | 109 | // Decrement i by 1. 110 | i--; 111 | } 112 | 113 | // Return parts. 114 | return parts; 115 | } 116 | -------------------------------------------------------------------------------- /src/deconstruct-pattern/deconstruct-pattern.ts: -------------------------------------------------------------------------------- 1 | import {Placeables} from "../placeables/placeables"; 2 | import {ListPartition} from "../list-partition/list-partition"; 3 | import {isList} from "../assert/is-list"; 4 | 5 | /** 6 | * The DeconstructPattern abstract operation is called with arguments pattern 7 | * (which must be a String) and placeables (which must be a Record), 8 | * and deconstructs the pattern string into a list of parts. 9 | * The placeables record is a record whose keys are placeables tokens used in the pattern string, 10 | * and values are parts records which will be used in the result List to represent the token part. 11 | * 12 | * http://tc39.github.io/proposal-intl-list-format/#sec-deconstructpattern 13 | * @param {string} pattern 14 | * @param {Placeables} placeables 15 | * @return {ListPartition[]} 16 | */ 17 | export function deconstructPattern(pattern: string, placeables: Placeables): ListPartition[] { 18 | // Let result be a new empty List. 19 | const result: ListPartition[] = []; 20 | 21 | // Let beginIndex be ! Call(%StringProto_indexOf%, pattern, « "{", 0 »). 22 | let beginIndex = String.prototype.indexOf.call(pattern, "{", 0); 23 | 24 | // Let nextIndex be 0. 25 | let nextIndex = 0; 26 | 27 | // Let length be the number of code units in pattern. 28 | const length = pattern.length; 29 | 30 | // Repeat, while beginIndex is an integer index into pattern 31 | while (pattern[beginIndex] !== undefined) { 32 | // Let endIndex to ! Call(%StringProto_indexOf%, pattern, « "}", beginIndex »). 33 | const endIndex = String.prototype.indexOf.call(pattern, "}", beginIndex); 34 | 35 | // Assert: endIndex is greater than beginIndex. 36 | if (endIndex <= beginIndex) { 37 | throw new TypeError(`Expected endIndex: ${endIndex} to be greater than beginIndex: ${beginIndex}`); 38 | } 39 | 40 | // If beginIndex is greater than nextIndex, then 41 | if (beginIndex > nextIndex) { 42 | // Let literal be a substring of pattern from position nextIndex, inclusive, to position beginIndex, exclusive. 43 | const literal = pattern.slice(nextIndex, beginIndex); 44 | 45 | // Append a new Record { [[Type]]: "literal", [[Value]]: literal } as the last element of result 46 | result.push({ 47 | type: "literal", 48 | value: literal 49 | }); 50 | } 51 | 52 | // Let part be the substring of pattern from position beginIndex, exclusive, to position endIndex, exclusive. 53 | const part = pattern.slice(beginIndex + 1, endIndex); 54 | 55 | // Assert: placeables has a field [[]]. 56 | if (placeables[Number(part) as 0 | 1] == null) { 57 | throw new TypeError(`Expected placeables to have a part for PropertyKey: ${part}`); 58 | } 59 | 60 | // Let subst be placeables.[[]]. 61 | const subst = placeables[Number(part) as 0 | 1]; 62 | 63 | // If Type(subst) is List, then 64 | if (isList(subst.value)) { 65 | // For each element s of subst in List order, do 66 | for (const s of subst.value) { 67 | // Append s as the last element of result. 68 | result.push(s); 69 | } 70 | } 71 | 72 | // Else, 73 | else { 74 | // Append subst as the last element of result. 75 | result.push(subst); 76 | } 77 | 78 | // Set nextIndex to endIndex + 1. 79 | nextIndex = endIndex + 1; 80 | 81 | // Set beginIndex to ! Call(%StringProto_indexOf%, pattern, « "{", nextIndex »). 82 | beginIndex = String.prototype.indexOf.call(pattern, "{", nextIndex); 83 | } 84 | 85 | // If nextIndex is less than length, then 86 | if (nextIndex < length) { 87 | // Let literal be the substring of pattern from position nextIndex, inclusive, to position length, exclusive. 88 | const literal = pattern.slice(nextIndex, length); 89 | 90 | // Append a new Record { [[Type]]: "literal", [[Value]]: literal } as the last element of result. 91 | result.push({ 92 | type: "literal", 93 | value: literal 94 | }); 95 | } 96 | 97 | // Return result 98 | return result; 99 | } 100 | -------------------------------------------------------------------------------- /src/default-locale/get-default-locale.ts: -------------------------------------------------------------------------------- 1 | import {Locale} from "../locale/locale"; 2 | 3 | /** 4 | * Must represent the structurally valid (6.2.2) and canonicalized (6.2.3) BCP 47 language tag for the host environment's current locale. 5 | * 6 | * https://tc39.github.io/ecma402/#sec-defaultlocale 7 | * @type {Locale?} 8 | */ 9 | let _defaultLocale: Locale | undefined; 10 | 11 | /** 12 | * Sets the default locale 13 | * @param {Locale} locale 14 | */ 15 | export function setDefaultLocale(locale: Locale): void { 16 | _defaultLocale = locale; 17 | } 18 | 19 | /** 20 | * The DefaultLocale abstract operation returns a String value representing the structurally valid (6.2.2) and canonicalized (6.2.3) BCP 47 language tag for the host environment's current locale. 21 | * https://tc39.github.io/ecma402/#sec-defaultlocale 22 | * @returns{Locale | undefined} 23 | */ 24 | export function getDefaultLocale(): Locale | undefined { 25 | return _defaultLocale; 26 | } 27 | 28 | /** 29 | * Retrieves the default locale if it is set, and throws otherwise 30 | * @returns{Locale} 31 | */ 32 | export function ensureDefaultLocale(): Locale { 33 | if (_defaultLocale == null) { 34 | throw new ReferenceError(`Could not determine locale: No default locale has been configured`); 35 | } 36 | return _defaultLocale; 37 | } 38 | -------------------------------------------------------------------------------- /src/format-list-to-parts/format-list-to-parts.ts: -------------------------------------------------------------------------------- 1 | import {ListFormat} from "../list-format/list-format"; 2 | import {ListPartitions} from "../list-partition/list-partition"; 3 | import {createPartsFromList} from "../create-parts-from-list/create-parts-from-list"; 4 | 5 | /** 6 | * The FormatListToParts abstract operation is called with arguments listFormat 7 | * (which must be an object initialized as a ListFormat) and list (which must be a List of String values) 8 | * 9 | * http://tc39.github.io/proposal-intl-list-format/#sec-formatlisttoparts 10 | * @param {ListFormat} listFormat 11 | * @param {string[]} list 12 | * @returns {ListPartitions} 13 | */ 14 | export function formatListToParts(listFormat: ListFormat, list: string[]): ListPartitions { 15 | return createPartsFromList(listFormat, list); 16 | } 17 | -------------------------------------------------------------------------------- /src/format-list/format-list.ts: -------------------------------------------------------------------------------- 1 | import {ListFormat} from "../list-format/list-format"; 2 | import {createPartsFromList} from "../create-parts-from-list/create-parts-from-list"; 3 | 4 | /** 5 | * The FormatList abstract operation is called with arguments listFormat 6 | * (which must be an object initialized as a ListFormat) and list (which must be a List of String values) 7 | * 8 | * http://tc39.github.io/proposal-intl-list-format/#sec-formatlist 9 | * @param {ListFormat} listFormat 10 | * @param {string[]} list 11 | * @returns {string} 12 | */ 13 | export function formatList(listFormat: ListFormat, list: string[]): string { 14 | // Let parts be CreatePartsFromList(listFormat, list). 15 | const parts = createPartsFromList(listFormat, list); 16 | 17 | // Let result be an empty String. 18 | let result = ""; 19 | 20 | // For each part in parts, do 21 | for (const part of parts) { 22 | // Set result to a String value produced by concatenating result and part.[[Value]]. 23 | result += part.value; 24 | } 25 | 26 | return result; 27 | } 28 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {SUPPORTS_LIST_FORMAT} from "./support/supports-intl-list-format"; 2 | import {patch} from "./patch/patch"; 3 | 4 | if (!SUPPORTS_LIST_FORMAT) { 5 | patch(); 6 | } 7 | -------------------------------------------------------------------------------- /src/internal-slot/internal-slot.ts: -------------------------------------------------------------------------------- 1 | import {ListFormatInstanceInternals} from "./list-format-instance-internals"; 2 | import {ListFormatStaticInternals} from "./list-format-static-internals"; 3 | import {ListFormat} from "../list-format/list-format"; 4 | 5 | /** 6 | * A WeakMap between ListFormat instances and their internal slot members 7 | * @type {WeakMap} 8 | */ 9 | export const LIST_FORMAT_INSTANCE_INTERNAL_MAP: WeakMap = new WeakMap(); 10 | 11 | /** 12 | * Contains the internal static for ListFormat 13 | * @type {ListFormatStaticInternals} 14 | */ 15 | export const LIST_FORMAT_STATIC_INTERNALS: ListFormatStaticInternals = { 16 | /** 17 | * The value of the [[RelevantExtensionKeys]] internal slot is « ». 18 | * http://tc39.github.io/proposal-intl-list-format/#sec-Intl.ListFormat-internal-slots 19 | */ 20 | relevantExtensionKeys: [], 21 | 22 | /** 23 | * The value of the [[LocaleData]] internal slot is implementation defined within the constraints described in 9.1 24 | * http://tc39.github.io/proposal-intl-list-format/#sec-Intl.ListFormat-internal-slots 25 | */ 26 | localeData: {}, 27 | 28 | /** 29 | * The value of the [[AvailableLocales]] internal slot is implementation defined within the constraints described in 9.1. 30 | * http://tc39.github.io/proposal-intl-list-format/#sec-Intl.ListFormat-internal-slots 31 | */ 32 | availableLocales: [] 33 | }; 34 | 35 | /** 36 | * Sets the value for a property in an internal slot for an instance of ListFormat 37 | * @param {ListFormat} instance 38 | * @param {T} property 39 | * @param {ListFormatInstanceInternals[T]} value 40 | */ 41 | export function setInternalSlot( 42 | instance: ListFormat, 43 | property: T, 44 | value: ListFormatInstanceInternals[T] 45 | ): void { 46 | let record = LIST_FORMAT_INSTANCE_INTERNAL_MAP.get(instance); 47 | if (record == null) { 48 | record = Object.create(null) as ListFormatInstanceInternals; 49 | LIST_FORMAT_INSTANCE_INTERNAL_MAP.set(instance, record); 50 | } 51 | 52 | // Update the property with the given value 53 | record[property] = value; 54 | } 55 | 56 | /** 57 | * Gets the value associated with the given property on the internal slots of the given instance of ListFormat 58 | * @param {ListFormat} instance 59 | * @param {T} property 60 | * @returns{ListFormatInstanceInternals[T]} 61 | */ 62 | export function getInternalSlot(instance: ListFormat, property: T): ListFormatInstanceInternals[T] { 63 | const record = LIST_FORMAT_INSTANCE_INTERNAL_MAP.get(instance); 64 | if (record == null) { 65 | throw new ReferenceError(`No internal slots has been allocated for the given instance of ListFormat`); 66 | } 67 | 68 | return record[property]; 69 | } 70 | 71 | /** 72 | * Returns true if the given property on the internal slots of the given instance of ListFormat exists 73 | * @param {ListFormat} instance 74 | * @param {T} property 75 | * @returns{ListFormatInstanceInternals[T]} 76 | */ 77 | export function hasInternalSlot(instance: ListFormat, property: T): boolean { 78 | const record = LIST_FORMAT_INSTANCE_INTERNAL_MAP.get(instance); 79 | return record != null && property in record; 80 | } 81 | -------------------------------------------------------------------------------- /src/internal-slot/list-format-instance-internals.ts: -------------------------------------------------------------------------------- 1 | import {Locale} from "../locale/locale"; 2 | import {Type} from "../type/type"; 3 | import {Style} from "../style/style"; 4 | import {ListFormat} from "../list-format/list-format"; 5 | 6 | export interface ListFormatInstanceInternals { 7 | initializedListFormat: ListFormat; 8 | locale: Locale; 9 | type: Type; 10 | style: Style; 11 | templatePair: string; 12 | templateStart: string; 13 | templateMiddle: string; 14 | templateEnd: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/internal-slot/list-format-static-internals.ts: -------------------------------------------------------------------------------- 1 | import {RelevantExtensionKey} from "../relevant-extension-key/relevant-extension-key"; 2 | import {LocaleData} from "../locale/locale-data"; 3 | import {Locales} from "../locale/locales"; 4 | 5 | export interface ListFormatStaticInternals { 6 | relevantExtensionKeys: RelevantExtensionKey[]; 7 | localeData: LocaleData; 8 | availableLocales: Locales; 9 | } 10 | -------------------------------------------------------------------------------- /src/list-format-options/list-format-options.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "../type/type"; 2 | import {Style} from "../style/style"; 3 | import {LocaleMatcher} from "../locale-matcher/locale-matcher"; 4 | 5 | export interface ListFormatOptions { 6 | type: Type; 7 | style: Style; 8 | localeMatcher: LocaleMatcher; 9 | } 10 | -------------------------------------------------------------------------------- /src/list-format/list-format.ts: -------------------------------------------------------------------------------- 1 | import {Locale} from "../locale/locale"; 2 | import {Locales} from "../locale/locales"; 3 | import {ListFormatOptions} from "../list-format-options/list-format-options"; 4 | import {SupportedLocalesOptions} from "../supported-locales-options/supported-locales-options"; 5 | import {ListPartitions} from "../list-partition/list-partition"; 6 | import {ResolvedListFormatOptions} from "../resolved-list-format-options/resolved-list-format-options"; 7 | import {toObject} from "../util/to-object"; 8 | import {InputLocaleDataEntry} from "../locale/locale-data"; 9 | import {getDefaultLocale, setDefaultLocale} from "../default-locale/get-default-locale"; 10 | import {getInternalSlot, hasInternalSlot, LIST_FORMAT_STATIC_INTERNALS, setInternalSlot} from "../internal-slot/internal-slot"; 11 | import {supportedLocales} from "../supported-locales/supported-locales"; 12 | import {resolveLocale} from "../resolve-locale/resolve-locale"; 13 | import {stringListFromIterable} from "../string-list-from-iterable"; 14 | import {formatList} from "../format-list/format-list"; 15 | import {formatListToParts} from "../format-list-to-parts/format-list-to-parts"; 16 | import {getOption} from "../util/get-option"; 17 | import {TYPE} from "../type/type"; 18 | import {STYLE} from "../style/style"; 19 | import {LOCALE_MATCHER} from "../locale-matcher/locale-matcher"; 20 | 21 | /** 22 | * The ListFormat constructor is the %ListFormat% intrinsic object and a standard built-in property of the Intl object. 23 | * Behaviour common to all service constructor properties of the Intl object is specified in 9.1. 24 | * 25 | * http://tc39.github.io/proposal-intl-list-format/#sec-intl-listformat-constructor 26 | */ 27 | export class ListFormat { 28 | // The spec states that the constructor must have a length of 0 and therefore be parameter-less 29 | constructor() { 30 | const locales = arguments[0] as Locale | Locales | undefined; 31 | let options = arguments[1] as Partial; 32 | 33 | // If NewTarget is undefined, throw a TypeError exception. 34 | if (new.target === undefined) { 35 | throw new TypeError(`Constructor Intl.ListFormat requires 'new'`); 36 | } 37 | 38 | // Let requestedLocales be ? CanonicalizeLocaleList(locales). 39 | const requestedLocales = Intl.getCanonicalLocales(locales); 40 | 41 | // If options is undefined, then (a) Let options be ObjectCreate(null). 42 | // Else (b) Let options be ? ToObject(options). 43 | options = options === undefined ? (Object.create(null) as Partial) : toObject(options); 44 | 45 | // Let opt be a new Record. 46 | const opt = Object.create(null) as ListFormatOptions; 47 | 48 | // Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit"). 49 | const matcher = getOption(options, "localeMatcher", "string", LOCALE_MATCHER, "best fit"); 50 | 51 | // Set opt.[[localeMatcher]] to matcher. 52 | opt.localeMatcher = matcher; 53 | 54 | // Let type be GetOption(options, "type", "string", « "conjunction", "disjunction", "unit" », "conjunction"). 55 | const type = getOption(options, "type", "string", TYPE, "conjunction"); 56 | 57 | // Set listFormat.[[Type]] to type. 58 | setInternalSlot(this, "type", type); 59 | 60 | // Let style be GetOption(options, "style", "string", « "long", "short", "narrow" », "long"). 61 | const style = getOption(options, "style", "string", STYLE, "long"); 62 | 63 | // Set listFormat.[[Style]] to style. 64 | setInternalSlot(this, "style", style); 65 | 66 | // Let localeData be %ListFormat%.[[LocaleData]]. 67 | const localeData = LIST_FORMAT_STATIC_INTERNALS.localeData; 68 | 69 | // Let r be ResolveLocale(%ListFormat%.[[AvailableLocales]], requestedLocales, opt, %ListFormat%.[[RelevantExtensionKeys]], localeData). 70 | const r = resolveLocale( 71 | LIST_FORMAT_STATIC_INTERNALS.availableLocales, 72 | requestedLocales, 73 | opt, 74 | LIST_FORMAT_STATIC_INTERNALS.relevantExtensionKeys, 75 | localeData 76 | ); 77 | 78 | // Let dataLocale be r.[[dataLocale]]. 79 | const dataLocale = r.dataLocale; 80 | 81 | // Let dataLocaleData be localeData.[[]]. 82 | const dataLocaleData = localeData[dataLocale]!; 83 | 84 | // Let dataLocaleTypes be dataLocaleData.[[]]. 85 | const dataLocaleTypes = dataLocaleData.formats[type]; 86 | 87 | // Let templates be dataLocaleTypes.[[