@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 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`, plus `x-form/assert` and the
72
- `JQ2xxx` query runtime codes. The repo enforces this with tests
73
- (`test/locales/`); missing keys silently fall back to English.
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>;
@@ -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. This module is internal to
10
- * `@jarenjs/locales` - packs import it, consumers never see it.
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>;
@@ -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.46.4",
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.46.4"
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; no plural helper is needed,
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
  };