@object-ui/i18n 17.7.0 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * The first day of the week for a BCP-47 locale tag (objectui#11675).
10
+ *
11
+ * The first day of the week follows the user's locale, as dates, times and
12
+ * numbers do (ruling B on objectui#11675: there is no separate week-start
13
+ * setting). So a week grid, a weekday header row and a "this week" bound all
14
+ * read it from the tag they already format with, and never from a hard-coded
15
+ * Sunday or Monday: `en-US` starts on Sunday, `en-GB` and `zh-CN` on Monday.
16
+ *
17
+ * ## The engine's own week info comes first
18
+ *
19
+ * `Intl.Locale` carries CLDR's week data for the tag's region, including a
20
+ * `-u-fw-` keyword on the tag. It has two spellings: the `getWeekInfo()` method
21
+ * of the current Intl Locale Info proposal, and the `weekInfo` accessor engines
22
+ * shipped first. Both are read, the method first. Measured while writing this:
23
+ * Chromium 141 has both, Node 22 (the test and CI runtime) has the accessor
24
+ * only. Both count ISO weekdays, 1 (Monday) to 7 (Sunday).
25
+ *
26
+ * ## Where the engine has none, CLDR's table answers
27
+ *
28
+ * An engine with neither spelling gets {@link NON_MONDAY_REGIONS}: CLDR's
29
+ * `weekData` `firstDay` rows, read by region. The region is the tag's own, or
30
+ * the one CLDR's likely subtags give it (`Intl.Locale.prototype.maximize`), so
31
+ * `en` reads `US` and `zh` reads `CN`. A region the table does not list, and a
32
+ * tag with no region even after `maximize()`, reads Monday, CLDR's world
33
+ * default (`001`). This path reads the region only: a `-u-fw-` or `-u-rg-`
34
+ * keyword is the engine path's alone.
35
+ *
36
+ * The table transcribes ONE CLDR release, the one {@link WEEK_DATA_CLDR} names:
37
+ * the newest release the repository's CI runtime ships. Engines carry their own
38
+ * week data, and it differs: CLDR moves a region now and then, and a browser
39
+ * may carry data of its own. That is why the engine's answer wins wherever
40
+ * there is one. What re-derives the table against the running engine is
41
+ * `__tests__/firstDayOfWeek-11675.test.ts`, region by region. On a runtime
42
+ * whose CLDR release is not the table's, it reads that release's recorded
43
+ * differences from the table instead of reporting them as drift.
44
+ *
45
+ * ## A tag `Intl` refuses is refused here too
46
+ *
47
+ * A malformed tag throws `Intl.Locale`'s `RangeError`, as `Intl.DateTimeFormat`
48
+ * and `toLocaleDateString` do for the same tag. Every caller formats its dates
49
+ * with the tag it hands this function, so a tag that reached here and is
50
+ * malformed fails the caller's own formatting the same way. ⛔ No Sunday or
51
+ * Monday is guessed for it.
52
+ */
53
+ /** A weekday as `Date.prototype.getDay` numbers it: 0 is Sunday, 6 is Saturday. */
54
+ export type WeekdayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6;
55
+ /**
56
+ * The CLDR release {@link NON_MONDAY_REGIONS} transcribes, as the major part of
57
+ * `process.versions.cldr`. Change it only together with the table, from a
58
+ * runtime that ships that release.
59
+ */
60
+ export declare const WEEK_DATA_CLDR = "48";
61
+ /**
62
+ * The first day of the week in `locale`, numbered as `Date.prototype.getDay`
63
+ * numbers weekdays (0 is Sunday), which is also what react-day-picker's
64
+ * `weekStartsOn` takes. Read the module header for where the answer comes from.
65
+ *
66
+ * @example
67
+ * firstDayOfWeek('en-US'); // 0, Sunday
68
+ * firstDayOfWeek('en-GB'); // 1, Monday
69
+ * firstDayOfWeek('zh-CN'); // 1, Monday
70
+ */
71
+ export declare function firstDayOfWeek(locale: string): WeekdayIndex;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /** ISO weekday 1 (Monday) to 7 (Sunday), as a {@link WeekdayIndex}. */
9
+ const BY_ISO_WEEKDAY = [1, 2, 3, 4, 5, 6, 0];
10
+ function fromIsoWeekday(day) {
11
+ return Number.isInteger(day) && day >= 1 && day <= 7 ? BY_ISO_WEEKDAY[day - 1] : undefined;
12
+ }
13
+ /**
14
+ * The CLDR release {@link NON_MONDAY_REGIONS} transcribes, as the major part of
15
+ * `process.versions.cldr`. Change it only together with the table, from a
16
+ * runtime that ships that release.
17
+ */
18
+ export const WEEK_DATA_CLDR = '48';
19
+ /**
20
+ * CLDR's `weekData` `firstDay` rows other than Monday, by region, as of the
21
+ * release {@link WEEK_DATA_CLDR} names. Regions are the canonical codes
22
+ * `Intl.Locale` reports (`BU` reads `MM`), so the deprecated aliases CLDR also
23
+ * lists are left out.
24
+ */
25
+ const NON_MONDAY_REGIONS = [
26
+ [
27
+ 0,
28
+ 'AG AS BD BR BS BT BW BZ CA CO DM DO ET GT GU HK HN ID IL IN IS JM JP KE KH KR LA MH MM MO MT MX ' +
29
+ 'MZ NI NP PA PE PH PK PR PT PY SA SG SV TH TT TW UM US VE VI WS YE ZA ZW',
30
+ ],
31
+ [5, 'MV'],
32
+ [6, 'AF BH DJ DZ EG IQ IR JO KW LY OM QA SD SY'],
33
+ ];
34
+ /** CLDR's world default (`001`): the week starts on Monday. */
35
+ const WORLD_FIRST_DAY = 1;
36
+ const FIRST_DAY_BY_REGION = new Map(NON_MONDAY_REGIONS.flatMap(([day, regions]) => regions.split(' ').map((region) => [region, day])));
37
+ function engineFirstDay(locale) {
38
+ const faces = locale;
39
+ if (typeof faces.getWeekInfo === 'function')
40
+ return fromIsoWeekday(faces.getWeekInfo().firstDay);
41
+ const info = faces.weekInfo;
42
+ return info ? fromIsoWeekday(info.firstDay) : undefined;
43
+ }
44
+ function tableFirstDay(locale) {
45
+ const region = locale.region ?? locale.maximize().region;
46
+ return (region !== undefined ? FIRST_DAY_BY_REGION.get(region) : undefined) ?? WORLD_FIRST_DAY;
47
+ }
48
+ /**
49
+ * The first day of the week in `locale`, numbered as `Date.prototype.getDay`
50
+ * numbers weekdays (0 is Sunday), which is also what react-day-picker's
51
+ * `weekStartsOn` takes. Read the module header for where the answer comes from.
52
+ *
53
+ * @example
54
+ * firstDayOfWeek('en-US'); // 0, Sunday
55
+ * firstDayOfWeek('en-GB'); // 1, Monday
56
+ * firstDayOfWeek('zh-CN'); // 1, Monday
57
+ */
58
+ export function firstDayOfWeek(locale) {
59
+ const parsed = new Intl.Locale(locale);
60
+ return engineFirstDay(parsed) ?? tableFirstDay(parsed);
61
+ }
@@ -49,8 +49,14 @@ export declare function formatDateTime(date: Date | string | number, options?: D
49
49
  * midnight), the same distance in every zone at the same wall-clock time.
50
50
  * An unparsable value comes back as its string, as it does from
51
51
  * {@link formatDate}; `Intl.RelativeTimeFormat` would throw on it.
52
+ *
53
+ * `options` are `Intl.RelativeTimeFormat`'s own `style` and `numeric`, passed
54
+ * through unchanged; omitted, the result is what it always was (`long`,
55
+ * `auto`: "yesterday", "2 days ago"). A surface with little room asks for a
56
+ * compact form, e.g. `{ style: 'short', numeric: 'always' }` ("1 day ago",
57
+ * "3 hr. ago").
52
58
  */
53
- export declare function formatRelativeTime(date: Date | string | number, locale?: string): string;
59
+ export declare function formatRelativeTime(date: Date | string | number, locale?: string, options?: Pick<Intl.RelativeTimeFormatOptions, 'style' | 'numeric'>): string;
54
60
  /**
55
61
  * Format a currency value according to locale conventions. When `currency`
56
62
  * is omitted, falls back to a plain locale-formatted number (no symbol)
@@ -59,8 +59,14 @@ export function formatDateTime(date, options = {}) {
59
59
  * midnight), the same distance in every zone at the same wall-clock time.
60
60
  * An unparsable value comes back as its string, as it does from
61
61
  * {@link formatDate}; `Intl.RelativeTimeFormat` would throw on it.
62
+ *
63
+ * `options` are `Intl.RelativeTimeFormat`'s own `style` and `numeric`, passed
64
+ * through unchanged; omitted, the result is what it always was (`long`,
65
+ * `auto`: "yesterday", "2 days ago"). A surface with little room asks for a
66
+ * compact form, e.g. `{ style: 'short', numeric: 'always' }` ("1 day ago",
67
+ * "3 hr. ago").
62
68
  */
63
- export function formatRelativeTime(date, locale = 'en') {
69
+ export function formatRelativeTime(date, locale = 'en', options = {}) {
64
70
  const d = toDisplayDate(date);
65
71
  if (isNaN(d.getTime())) {
66
72
  return String(date);
@@ -71,7 +77,10 @@ export function formatRelativeTime(date, locale = 'en') {
71
77
  const diffMin = Math.round(diffSec / 60);
72
78
  const diffHour = Math.round(diffMin / 60);
73
79
  const diffDay = Math.round(diffHour / 24);
74
- const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
80
+ const rtf = new Intl.RelativeTimeFormat(locale, {
81
+ numeric: options.numeric ?? 'auto',
82
+ style: options.style ?? 'long',
83
+ });
75
84
  if (Math.abs(diffSec) < 60)
76
85
  return rtf.format(diffSec, 'second');
77
86
  if (Math.abs(diffMin) < 60)
@@ -2,3 +2,4 @@ export { formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatN
2
2
  export { resolvePlural, formatDateSpec, formatNumberSpec, applyLocaleConfig, type SpecPluralRule, type SpecDateFormat, type SpecNumberFormat, type SpecLocaleConfig, } from './spec-formatters.js';
3
3
  export { isSpecTranslationData, transformSpecTranslations, type SpecTranslationData, } from './spec-translations.js';
4
4
  export { formatDisplayNumber, shouldGroupDisplayNumber, type DisplayNumberFormatOptions, } from './number-display.js';
5
+ export { firstDayOfWeek, type WeekdayIndex } from './first-day-of-week.js';
@@ -2,3 +2,4 @@ export { formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatN
2
2
  export { resolvePlural, formatDateSpec, formatNumberSpec, applyLocaleConfig, } from './spec-formatters.js';
3
3
  export { isSpecTranslationData, transformSpecTranslations, } from './spec-translations.js';
4
4
  export { formatDisplayNumber, shouldGroupDisplayNumber, } from './number-display.js';
5
+ export { firstDayOfWeek } from './first-day-of-week.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/i18n",
3
- "version": "17.7.0",
3
+ "version": "18.0.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "i18next": "^26.4.0",
39
39
  "react-i18next": "^17.0.11",
40
- "@object-ui/core": "17.7.0"
40
+ "@object-ui/core": "18.0.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "react": "^18.0.0 || ^19.0.0"