├── .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 |

4 |
5 |
6 |
7 |
8 |
9 | > A fully spec-compliant polyfill for 'Intl.ListFormat'
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 | |
|
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 |
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 |
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.[[