@jarenjs/locales 0.46.4 → 0.49.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -4
- package/dist/types/dates.d.ts +68 -0
- package/dist/types/helpers.d.ts +63 -2
- package/dist/types/index.d.ts +11 -0
- package/dist/types/intl-dates.d.ts +20 -0
- package/package.json +10 -2
- package/src/ar.js +76 -1
- package/src/dates.js +263 -0
- package/src/de.js +49 -0
- package/src/es.js +47 -0
- package/src/fr.js +49 -0
- package/src/helpers.js +116 -2
- package/src/index.js +12 -0
- package/src/intl-dates.js +160 -0
- package/src/ja.js +49 -0
- package/src/ko.js +49 -0
- package/src/nl.js +49 -0
- package/src/pt.js +47 -0
- package/src/ru.js +63 -4
- package/src/tr.js +49 -0
- package/src/zh-tw.js +49 -0
package/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Locale packs (message catalogs) for the error messages of
|
|
4
4
|
[`@jarenjs/validate`](../validate/README.md),
|
|
5
5
|
[`@jarenjs/forms`](../forms/README.md) and
|
|
6
|
-
[`@jarenjs/contract`](../contract/README.md)
|
|
6
|
+
[`@jarenjs/contract`](../contract/README.md), and the calendar language
|
|
7
|
+
`@jarenjs/core/dates` refuses to invent.
|
|
7
8
|
|
|
8
9
|
Jaren validators and form checks never bake prose into the hot path: every
|
|
9
10
|
failure carries a stable message key (`msgid`) plus raw structured
|
|
@@ -48,6 +49,96 @@ way - the package is `sideEffects: false`):
|
|
|
48
49
|
import { nl } from '@jarenjs/locales/nl';
|
|
49
50
|
```
|
|
50
51
|
|
|
52
|
+
## Calendar language
|
|
53
|
+
|
|
54
|
+
`@jarenjs/core/dates` is locale-free by design: its `MMMM`, `EEE` and `a`
|
|
55
|
+
pattern tokens raise rather than fall back to English. Every pack carries
|
|
56
|
+
the data those tokens need, on the same flat msgid machinery as the error
|
|
57
|
+
messages, and `compileDateLocale` turns a pack into the frozen record a
|
|
58
|
+
formatter and a UI read:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
import { compileDateFormat, parseRFC3339Parts } from '@jarenjs/core/dates';
|
|
62
|
+
import { compileDateLocale, nl } from '@jarenjs/locales';
|
|
63
|
+
|
|
64
|
+
const dates = compileDateLocale(nl);
|
|
65
|
+
const long = compileDateFormat('EEEE d MMMM yyyy', dates.names);
|
|
66
|
+
long(parseRFC3339Parts('2026-07-05')); // 'zondag 5 juli 2026'
|
|
67
|
+
dates.relative(-3, 'day'); // '3 dagen geleden'
|
|
68
|
+
dates.relative(-1, 'day', { numeric: 'auto' }); // 'gisteren'
|
|
69
|
+
dates.formatName('date-time'); // 'datum en tijd'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The compiled record has three members: `names` (the five arrays
|
|
73
|
+
`compileDateFormat` takes), `relative(amount, unit, options?)` and
|
|
74
|
+
`formatName(format)`. `relative` takes an **explicit signed amount** -
|
|
75
|
+
negative is past, positive is future - and reads no clock; deciding how
|
|
76
|
+
long ago something was is the caller's job, and phrasing it is this
|
|
77
|
+
package's. `numeric: 'auto'` reaches for `yesterday`/`today`/`tomorrow`
|
|
78
|
+
and `now` only where they are exact (a whole day away, a zero-second
|
|
79
|
+
offset); a fractional amount, an unsupported unit and an unknown mode are
|
|
80
|
+
each refused. Compilation reads the catalog once, so a formatter built
|
|
81
|
+
from `names` costs no message lookup per date.
|
|
82
|
+
|
|
83
|
+
The msgids are closed and mechanical, so a pack cannot half-cover them:
|
|
84
|
+
|
|
85
|
+
| Family | Keys |
|
|
86
|
+
|---|---|
|
|
87
|
+
| months | `date/month/01`…`12` × `/wide`, `/short` |
|
|
88
|
+
| weekdays | `date/weekday/0`…`6` × `/wide`, `/short` (Sunday = 0) |
|
|
89
|
+
| day period | `date/meridiem/am`, `date/meridiem/pm` |
|
|
90
|
+
| relative | `date/relative/{second,minute,hour,day,week,month,year}/{past,future}` |
|
|
91
|
+
| named days | `date/relative/{now,yesterday,today,tomorrow}` |
|
|
92
|
+
| format names | `format/name/{date,time,date-time,iso-date,iso-time,iso-date-time}` |
|
|
93
|
+
|
|
94
|
+
`dateMessagesEn` is the English set and the canonical key list. Name
|
|
95
|
+
entries take no parameters; a relative entry receives the **absolute**
|
|
96
|
+
amount as `{ value }` and owns its language's plural and case grammar -
|
|
97
|
+
the direction is already in the key. A pack authors the forty name keys
|
|
98
|
+
through `dateNameEntries({ months, monthsShort, weekdays, weekdaysShort,
|
|
99
|
+
meridiem })`, which checks the lengths as it expands them.
|
|
100
|
+
|
|
101
|
+
Two consequences worth knowing before writing a pack. The month array
|
|
102
|
+
feeds a *pattern*, so a language that inflects months by position ships
|
|
103
|
+
the **format** forms rather than the standalone ones (Russian's
|
|
104
|
+
`5 июля 2026`, not `июль`). And a language whose abbreviations equal its
|
|
105
|
+
full names ships them equal (Arabic, Japanese, Chinese) rather than
|
|
106
|
+
inventing shorter ones.
|
|
107
|
+
|
|
108
|
+
### Format display names, and why they exist
|
|
109
|
+
|
|
110
|
+
A format's name is its wire name - `date-time`, `iso-time` - which is
|
|
111
|
+
English by construction. Interpolated into a translated sentence it reads
|
|
112
|
+
as gibberish: a Dutch user used to be told *"Moet een geldige date-time
|
|
113
|
+
zijn"*. `@jarenjs/forms` now resolves `format/name/<format>` through the
|
|
114
|
+
catalog it is rendering with, so the same failure reads *"Moet een
|
|
115
|
+
geldige datum en tijd zijn"* while the error's own
|
|
116
|
+
`params.format` stays `'date-time'` - structural, so re-rendering it in a
|
|
117
|
+
second language answers in that language rather than repeating the first
|
|
118
|
+
one's noun. A format the catalog cannot name keeps its own, which is the
|
|
119
|
+
readable answer for names that are already words (`email`, `hostname`)
|
|
120
|
+
and the only possible one for a format this repository has never heard of.
|
|
121
|
+
|
|
122
|
+
### The optional `Intl` provider
|
|
123
|
+
|
|
124
|
+
`createIntlDateLocale(locale, options?)` builds the same shape out of the
|
|
125
|
+
platform's ICU data, for a host that wants a hundred locales more than it
|
|
126
|
+
wants byte stability:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
import { createIntlDateLocale } from '@jarenjs/locales/intl-dates';
|
|
130
|
+
|
|
131
|
+
const dates = createIntlDateLocale('hu-HU'); // same three members
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
It is a separate module and an explicit call because it cannot be the
|
|
135
|
+
default. ICU output moves between Node versions and between a browser and
|
|
136
|
+
a server, and this repository's own site is server-rendered under
|
|
137
|
+
byte-comparison tests. Nothing on the default path constructs an `Intl`
|
|
138
|
+
object, and a bundler drops the provider from a bundle that never calls
|
|
139
|
+
it. ICU has no date-format display names, so that member carries the
|
|
140
|
+
repository's English ones unless `options.formatNames` overrides them.
|
|
141
|
+
|
|
51
142
|
## Authoring a pack
|
|
52
143
|
|
|
53
144
|
A catalog is a plain flat object; each entry is either a **template
|
|
@@ -68,9 +159,11 @@ Rules of the road:
|
|
|
68
159
|
|
|
69
160
|
1. **Key parity.** Cover every key of validate's `messagesEn`, every
|
|
70
161
|
`form/*` key of forms' `formsMessagesEn`, every `contract/*` wire-error
|
|
71
|
-
key of contract's `contractMessagesEn`,
|
|
72
|
-
`
|
|
73
|
-
|
|
162
|
+
key of contract's `contractMessagesEn`, every `date/*` and
|
|
163
|
+
`format/name/*` key of this package's own `dateMessagesEn`, plus
|
|
164
|
+
`x-form/assert` and the `JQ2xxx` query runtime codes. The repo enforces
|
|
165
|
+
this with tests (`test/locales/`); missing keys silently fall back to
|
|
166
|
+
English.
|
|
74
167
|
2. **Never depend on a consumer.** A pack must stay importable without
|
|
75
168
|
dragging in `@jarenjs/validate` or `@jarenjs/forms`, so that either one
|
|
76
169
|
can serve any pack. The platform and `@jarenjs/core` are the only things
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { RELATIVE_UNITS } from './helpers.js';
|
|
2
|
+
export { RELATIVE_UNITS };
|
|
3
|
+
/**
|
|
4
|
+
* The English date catalog: the canonical key set every pack covers,
|
|
5
|
+
* and the entries `compileDateLocale` falls back to for a partial
|
|
6
|
+
* caller-supplied catalog.
|
|
7
|
+
* @type {Record<string, string | ((params: any, error?: object) => string)>}
|
|
8
|
+
*/
|
|
9
|
+
export declare const dateMessagesEn: Record<string, string | ((params: any, error?: object) => string)>;
|
|
10
|
+
export type DateLocaleNames = {
|
|
11
|
+
/**
|
|
12
|
+
* - 12 wide month names, January first
|
|
13
|
+
*/
|
|
14
|
+
months: string[];
|
|
15
|
+
/**
|
|
16
|
+
* - 12 abbreviated month names
|
|
17
|
+
*/
|
|
18
|
+
monthsShort: string[];
|
|
19
|
+
/**
|
|
20
|
+
* - 7 wide weekday names, Sunday first
|
|
21
|
+
*/
|
|
22
|
+
weekdays: string[];
|
|
23
|
+
/**
|
|
24
|
+
* - 7 abbreviated weekday names
|
|
25
|
+
*/
|
|
26
|
+
weekdaysShort: string[];
|
|
27
|
+
/**
|
|
28
|
+
* - the AM and PM markers
|
|
29
|
+
*/
|
|
30
|
+
meridiem: [string, string];
|
|
31
|
+
};
|
|
32
|
+
export type DateLocale = {
|
|
33
|
+
/**
|
|
34
|
+
* - the arrays `compileDateFormat` reads
|
|
35
|
+
*/
|
|
36
|
+
names: DateLocaleNames;
|
|
37
|
+
/**
|
|
38
|
+
* - a signed relative-time phrase
|
|
39
|
+
*/
|
|
40
|
+
relative: (amount: number, unit: string, options?: {
|
|
41
|
+
numeric?: string;
|
|
42
|
+
}) => string;
|
|
43
|
+
/**
|
|
44
|
+
* - a date-format display name, or undefined when the catalog has none
|
|
45
|
+
*/
|
|
46
|
+
formatName: (format: string) => string | undefined;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Compile a catalog into the calendar language a formatter and a UI
|
|
50
|
+
* need. Names are read once and frozen, so a formatter compiled against
|
|
51
|
+
* them and a label rendered from them cost no lookup per date.
|
|
52
|
+
*
|
|
53
|
+
* The argument is a catalog-like - a locale pack, an already-compiled
|
|
54
|
+
* catalog, or a partial object of overrides; every key it leaves out
|
|
55
|
+
* falls back to English. The eleven shipped packs cover the whole key
|
|
56
|
+
* set, so that fallback exists for a caller's own catalog, not for them.
|
|
57
|
+
*
|
|
58
|
+
* @param {Record<string, any>} [catalogLike] - A catalog to read, or undefined for English
|
|
59
|
+
* @returns {Readonly<DateLocale>} The frozen calendar language
|
|
60
|
+
* @throws {TypeError} when an entry does not render a non-empty string
|
|
61
|
+
* @example
|
|
62
|
+
* import { compileDateFormat } from '@jarenjs/core/dates';
|
|
63
|
+
* const dates = compileDateLocale(nl);
|
|
64
|
+
* const long = compileDateFormat('EEEE d MMMM yyyy', dates.names);
|
|
65
|
+
* dates.relative(-3, 'day'); // '3 dagen geleden'
|
|
66
|
+
* dates.relative(-1, 'day', { numeric: 'auto' }); // 'gisteren'
|
|
67
|
+
*/
|
|
68
|
+
export declare function compileDateLocale(catalogLike?: Record<string, any>): Readonly<DateLocale>;
|
package/dist/types/helpers.d.ts
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
* Each factory takes a pack's own `Intl` singleton or translated table
|
|
7
7
|
* and returns the render closure the catalog entries call. Building the
|
|
8
8
|
* closure once at module load keeps the packs' allocation discipline:
|
|
9
|
-
* nothing is constructed per message.
|
|
10
|
-
*
|
|
9
|
+
* nothing is constructed per message. The calendar half is here for the
|
|
10
|
+
* same reason: the date msgids are mechanical, and expanding them from
|
|
11
|
+
* arrays is what keeps forty keys per pack from being forty chances to
|
|
12
|
+
* mistype one. This module is internal to `@jarenjs/locales` - packs and
|
|
13
|
+
* the date adapters import it, consumers never see it.
|
|
11
14
|
*/
|
|
12
15
|
export { formatMessageValue } from '@jarenjs/core/message';
|
|
13
16
|
/**
|
|
@@ -39,3 +42,61 @@ export declare function makePluralPicker(pluralRules: Intl.PluralRules): (count:
|
|
|
39
42
|
* @returns {(type: string) => string} The type-name renderer
|
|
40
43
|
*/
|
|
41
44
|
export declare function makeTypeNamer(typeNames: Record<string, string>): (type: string) => string;
|
|
45
|
+
/**
|
|
46
|
+
* Build a multi-form noun picker over a pack's plural rules: the
|
|
47
|
+
* count's CLDR category selects a member of the `forms` record, and
|
|
48
|
+
* `other` covers every category the record leaves out. For languages
|
|
49
|
+
* whose counted messages need more than the two forms
|
|
50
|
+
* {@link makePluralPicker} covers - Russian's one/few/many, Arabic's
|
|
51
|
+
* six.
|
|
52
|
+
*
|
|
53
|
+
* @param {Intl.PluralRules} pluralRules - The pack's plural rules
|
|
54
|
+
* @returns {(count: number, forms: Record<string, string>) => string} The form picker
|
|
55
|
+
* @example
|
|
56
|
+
* plural(21, { one: 'секунду', few: 'секунды', other: 'секунд' }); // 'секунду'
|
|
57
|
+
*/
|
|
58
|
+
export declare function makePluralForms(pluralRules: Intl.PluralRules): (count: number, forms: Record<string, string>) => string;
|
|
59
|
+
/**
|
|
60
|
+
* The relative-time units, in the order a duration shrinks. A unit
|
|
61
|
+
* outside this list is refused rather than approximated.
|
|
62
|
+
*/
|
|
63
|
+
export declare const RELATIVE_UNITS: readonly string[];
|
|
64
|
+
/**
|
|
65
|
+
* Check a relative-time call and resolve its numeric mode. Both date
|
|
66
|
+
* locale providers - the repository one and the `Intl` one - run this,
|
|
67
|
+
* so the opt-in provider refuses exactly what the default provider
|
|
68
|
+
* refuses instead of quietly answering where the other raises.
|
|
69
|
+
*
|
|
70
|
+
* @param {number} amount - Whole units, signed: negative past, positive future
|
|
71
|
+
* @param {string} unit - One of {@link RELATIVE_UNITS}
|
|
72
|
+
* @param {{numeric?: string}} [options] - The caller's options
|
|
73
|
+
* @returns {string} The resolved numeric mode, `'always'` or `'auto'`
|
|
74
|
+
* @throws {TypeError} on a fractional amount, an unsupported unit or an unknown mode
|
|
75
|
+
*/
|
|
76
|
+
export declare function checkRelativeArguments(amount: number, unit: string, options?: {
|
|
77
|
+
numeric?: string;
|
|
78
|
+
}): string;
|
|
79
|
+
/**
|
|
80
|
+
* Expand a pack's five calendar-name arrays into the flat `date/...`
|
|
81
|
+
* msgid entries every catalog carries. The arrays are what a translator
|
|
82
|
+
* wants to read and the flat keys are what the catalog contract needs,
|
|
83
|
+
* so the expansion - and the length/order check that goes with it -
|
|
84
|
+
* happens once here rather than as forty hand-typed keys per pack.
|
|
85
|
+
*
|
|
86
|
+
* Weekdays start at Sunday, matching the index
|
|
87
|
+
* `@jarenjs/core/dates`' `EEEE`/`EEE` tokens look names up by.
|
|
88
|
+
*
|
|
89
|
+
* @param {{months: string[], monthsShort: string[], weekdays: string[], weekdaysShort: string[], meridiem: string[]}} names - The pack's calendar names
|
|
90
|
+
* @returns {Record<string, string>} The `date/month|weekday|meridiem/...` entries
|
|
91
|
+
* @throws {TypeError} when an array has the wrong length or a non-string member
|
|
92
|
+
* @example
|
|
93
|
+
* dateNameEntries({ months, monthsShort, weekdays, weekdaysShort, meridiem })
|
|
94
|
+
* // { 'date/month/01/wide': 'januari', ..., 'date/meridiem/pm': 'p.m.' }
|
|
95
|
+
*/
|
|
96
|
+
export declare function dateNameEntries({ months, monthsShort, weekdays, weekdaysShort, meridiem }: {
|
|
97
|
+
months: string[];
|
|
98
|
+
monthsShort: string[];
|
|
99
|
+
weekdays: string[];
|
|
100
|
+
weekdaysShort: string[];
|
|
101
|
+
meridiem: string[];
|
|
102
|
+
}): Record<string, string>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -9,7 +9,18 @@
|
|
|
9
9
|
* own translations and `Intl` singletons, and imports nothing but the
|
|
10
10
|
* rendering helpers of `./helpers.js` - never a consumer package, so
|
|
11
11
|
* either consumer can serve any pack.
|
|
12
|
+
*
|
|
13
|
+
* Beside the error messages, every pack carries the calendar language
|
|
14
|
+
* `@jarenjs/core/dates` refuses to invent: month, weekday and meridiem
|
|
15
|
+
* names, relative-time phrases and date-format display names.
|
|
16
|
+
* `compileDateLocale` (`./dates`) turns a pack into the frozen record a
|
|
17
|
+
* formatter and a UI read; importing that subpath directly costs no
|
|
18
|
+
* `Intl` construction at all, which is what keeps server-rendered output
|
|
19
|
+
* byte-stable. `createIntlDateLocale` (`./intl-dates`) is the opt-in
|
|
20
|
+
* provider for hosts that want the platform's locales instead.
|
|
12
21
|
*/
|
|
22
|
+
export { dateMessagesEn, compileDateLocale, RELATIVE_UNITS } from './dates.js';
|
|
23
|
+
export { createIntlDateLocale } from './intl-dates.js';
|
|
13
24
|
export { ar } from './ar.js';
|
|
14
25
|
export { de } from './de.js';
|
|
15
26
|
export { es } from './es.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a calendar language from the host's ICU data.
|
|
3
|
+
*
|
|
4
|
+
* The returned shape is the one `compileDateLocale` returns, so a
|
|
5
|
+
* consumer takes either without knowing which: `names` plugs into
|
|
6
|
+
* `compileDateFormat`, and `relative` takes the same signed amount and
|
|
7
|
+
* refuses the same arguments. What differs is only where the text comes
|
|
8
|
+
* from, and therefore whether it is stable across host versions.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} locale - A BCP 47 locale tag (`'nl-NL'`)
|
|
11
|
+
* @param {{formatNames?: Record<string, string>}} [options] - Date-format display names, merged over the English ones
|
|
12
|
+
* @returns {Readonly<import('./dates.js').DateLocale>} The frozen calendar language
|
|
13
|
+
* @example
|
|
14
|
+
* const dates = createIntlDateLocale('hu-HU');
|
|
15
|
+
* compileDateFormat('yyyy MMMM d.', dates.names);
|
|
16
|
+
* dates.relative(-3, 'day'); // '3 napja'
|
|
17
|
+
*/
|
|
18
|
+
export declare function createIntlDateLocale(locale: string, options?: {
|
|
19
|
+
formatNames?: Record<string, string>;
|
|
20
|
+
}): Readonly<import('./dates.js').DateLocale>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jarenjs/locales",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.49.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./dist/types/index.d.ts",
|
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
"types": "./dist/types/index.d.ts",
|
|
12
12
|
"default": "./src/index.js"
|
|
13
13
|
},
|
|
14
|
+
"./dates": {
|
|
15
|
+
"types": "./dist/types/dates.d.ts",
|
|
16
|
+
"default": "./src/dates.js"
|
|
17
|
+
},
|
|
18
|
+
"./intl-dates": {
|
|
19
|
+
"types": "./dist/types/intl-dates.d.ts",
|
|
20
|
+
"default": "./src/intl-dates.js"
|
|
21
|
+
},
|
|
14
22
|
"./ar": {
|
|
15
23
|
"types": "./dist/types/ar.d.ts",
|
|
16
24
|
"default": "./src/ar.js"
|
|
@@ -90,6 +98,6 @@
|
|
|
90
98
|
"prepack": "npm run build:types"
|
|
91
99
|
},
|
|
92
100
|
"dependencies": {
|
|
93
|
-
"@jarenjs/core": "^0.
|
|
101
|
+
"@jarenjs/core": "^0.49.2"
|
|
94
102
|
}
|
|
95
103
|
}
|
package/src/ar.js
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
* - Arabic pluralizes across six categories, so counted messages phrase
|
|
16
16
|
* around a fixed "عدد" (count-of) noun ("يجب ألا يقل عدد الأحرف عن 2")
|
|
17
17
|
* instead of agreeing the noun with the number - the standard
|
|
18
|
-
* software-string convention
|
|
18
|
+
* software-string convention. The relative-time phrases are the
|
|
19
|
+
* exception, because there the counted noun IS the message, so they
|
|
20
|
+
* agree through `Intl.PluralRules`,
|
|
19
21
|
* - `Intl.NumberFormat` is pinned to Latin digits (`numberingSystem:
|
|
20
22
|
* 'latn'`): the limits describe JSON documents, which are written in
|
|
21
23
|
* Latin digits regardless of UI language,
|
|
@@ -26,11 +28,14 @@
|
|
|
26
28
|
import {
|
|
27
29
|
formatMessageValue,
|
|
28
30
|
makeNumberRenderer,
|
|
31
|
+
makePluralForms,
|
|
29
32
|
makeTypeNamer,
|
|
33
|
+
dateNameEntries,
|
|
30
34
|
} from './helpers.js';
|
|
31
35
|
|
|
32
36
|
//#region Intl singletons
|
|
33
37
|
|
|
38
|
+
const pluralRules = new Intl.PluralRules('ar');
|
|
34
39
|
const numberFormat = new Intl.NumberFormat('ar', { numberingSystem: 'latn' });
|
|
35
40
|
const listFormat = new Intl.ListFormat('ar', { style: 'long', type: 'disjunction' });
|
|
36
41
|
|
|
@@ -54,6 +59,28 @@ const TYPE_NAMES = {
|
|
|
54
59
|
/** Type keyword values under their Arabic display name. */
|
|
55
60
|
const typeName = makeTypeNamer(TYPE_NAMES);
|
|
56
61
|
|
|
62
|
+
/** Pick the form a counted noun takes across Arabic's six categories. */
|
|
63
|
+
const plural = makePluralForms(pluralRules);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Count a noun the Arabic way: one and two are carried by the noun's own
|
|
67
|
+
* forms and take no numeral at all ("ثانية واحدة", "ثانيتين"), three to
|
|
68
|
+
* ten take the broken plural after the numeral, eleven to ninety-nine
|
|
69
|
+
* take the accusative singular ("21 يومًا") and a hundred or more the
|
|
70
|
+
* bare singular. This is the one place the pack
|
|
71
|
+
* cannot phrase around a fixed noun - a relative-time phrase IS the
|
|
72
|
+
* counted noun.
|
|
73
|
+
* @param {number} value - The absolute amount
|
|
74
|
+
* @param {Record<string, string>} forms - The noun's forms by CLDR category
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
function counted(value, forms) {
|
|
78
|
+
const category = pluralRules.select(value);
|
|
79
|
+
return (category === 'one' || category === 'two')
|
|
80
|
+
? plural(value, forms)
|
|
81
|
+
: `${num(value)} ${plural(value, forms)}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
57
84
|
/**
|
|
58
85
|
* The limit comparisons in words: an ASCII operator between RTL text
|
|
59
86
|
* and a number falls to the bidi algorithm's neutral reordering and
|
|
@@ -198,4 +225,52 @@ export const ar = {
|
|
|
198
225
|
'contract/stream-error': 'انتهى دفق العملية {op} بخطأ من الخادم ({code})',
|
|
199
226
|
'contract/heartbeat-missed': 'ظل دفق العملية {op} صامتًا لمدة {ms} مللي ثانية',
|
|
200
227
|
//#endregion
|
|
228
|
+
|
|
229
|
+
//#region calendar language (the date names, relative phrasing and
|
|
230
|
+
// format display names of @jarenjs/locales' date adapter)
|
|
231
|
+
// Arabic abbreviates neither month nor weekday names, so the wide and
|
|
232
|
+
// short arrays hold the same strings - as CLDR has them.
|
|
233
|
+
...dateNameEntries({
|
|
234
|
+
months: [
|
|
235
|
+
'يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو',
|
|
236
|
+
'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر',
|
|
237
|
+
],
|
|
238
|
+
monthsShort: [
|
|
239
|
+
'يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو',
|
|
240
|
+
'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر',
|
|
241
|
+
],
|
|
242
|
+
weekdays: [
|
|
243
|
+
'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء',
|
|
244
|
+
'الخميس', 'الجمعة', 'السبت',
|
|
245
|
+
],
|
|
246
|
+
weekdaysShort: [
|
|
247
|
+
'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت',
|
|
248
|
+
],
|
|
249
|
+
meridiem: ['ص', 'م'],
|
|
250
|
+
}),
|
|
251
|
+
'date/relative/second/past': (p) => `قبل ${counted(p.value, { one: 'ثانية واحدة', two: 'ثانيتين', few: 'ثوانٍ', many: 'ثانيةً', other: 'ثانية' })}`,
|
|
252
|
+
'date/relative/second/future': (p) => `خلال ${counted(p.value, { one: 'ثانية واحدة', two: 'ثانيتين', few: 'ثوانٍ', many: 'ثانيةً', other: 'ثانية' })}`,
|
|
253
|
+
'date/relative/minute/past': (p) => `قبل ${counted(p.value, { one: 'دقيقة واحدة', two: 'دقيقتين', few: 'دقائق', many: 'دقيقةً', other: 'دقيقة' })}`,
|
|
254
|
+
'date/relative/minute/future': (p) => `خلال ${counted(p.value, { one: 'دقيقة واحدة', two: 'دقيقتين', few: 'دقائق', many: 'دقيقةً', other: 'دقيقة' })}`,
|
|
255
|
+
'date/relative/hour/past': (p) => `قبل ${counted(p.value, { one: 'ساعة واحدة', two: 'ساعتين', few: 'ساعات', many: 'ساعةً', other: 'ساعة' })}`,
|
|
256
|
+
'date/relative/hour/future': (p) => `خلال ${counted(p.value, { one: 'ساعة واحدة', two: 'ساعتين', few: 'ساعات', many: 'ساعةً', other: 'ساعة' })}`,
|
|
257
|
+
'date/relative/day/past': (p) => `قبل ${counted(p.value, { one: 'يوم واحد', two: 'يومين', few: 'أيام', many: 'يومًا', other: 'يوم' })}`,
|
|
258
|
+
'date/relative/day/future': (p) => `خلال ${counted(p.value, { one: 'يوم واحد', two: 'يومين', few: 'أيام', many: 'يومًا', other: 'يوم' })}`,
|
|
259
|
+
'date/relative/week/past': (p) => `قبل ${counted(p.value, { one: 'أسبوع واحد', two: 'أسبوعين', few: 'أسابيع', many: 'أسبوعًا', other: 'أسبوع' })}`,
|
|
260
|
+
'date/relative/week/future': (p) => `خلال ${counted(p.value, { one: 'أسبوع واحد', two: 'أسبوعين', few: 'أسابيع', many: 'أسبوعًا', other: 'أسبوع' })}`,
|
|
261
|
+
'date/relative/month/past': (p) => `قبل ${counted(p.value, { one: 'شهر واحد', two: 'شهرين', few: 'أشهر', many: 'شهرًا', other: 'شهر' })}`,
|
|
262
|
+
'date/relative/month/future': (p) => `خلال ${counted(p.value, { one: 'شهر واحد', two: 'شهرين', few: 'أشهر', many: 'شهرًا', other: 'شهر' })}`,
|
|
263
|
+
'date/relative/year/past': (p) => `قبل ${counted(p.value, { one: 'سنة واحدة', two: 'سنتين', few: 'سنوات', many: 'سنةً', other: 'سنة' })}`,
|
|
264
|
+
'date/relative/year/future': (p) => `خلال ${counted(p.value, { one: 'سنة واحدة', two: 'سنتين', few: 'سنوات', many: 'سنةً', other: 'سنة' })}`,
|
|
265
|
+
'date/relative/now': 'الآن',
|
|
266
|
+
'date/relative/yesterday': 'أمس',
|
|
267
|
+
'date/relative/today': 'اليوم',
|
|
268
|
+
'date/relative/tomorrow': 'غدًا',
|
|
269
|
+
'format/name/date': 'تاريخ',
|
|
270
|
+
'format/name/time': 'وقت',
|
|
271
|
+
'format/name/date-time': 'تاريخ ووقت',
|
|
272
|
+
'format/name/iso-date': 'تاريخ ISO',
|
|
273
|
+
'format/name/iso-time': 'وقت ISO',
|
|
274
|
+
'format/name/iso-date-time': 'تاريخ ووقت ISO',
|
|
275
|
+
//#endregion
|
|
201
276
|
};
|