@kanso-protocol/i18n 0.1.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,166 @@
|
|
|
1
|
+
import { InjectionToken, inject } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BCP-47 locale tag used by every Kanso component that calls into Intl
|
|
5
|
+
* APIs (date / time / number formatting, weekday names, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Default factory pulls from `navigator.language` if available, else
|
|
8
|
+
* falls back to `'en-US'`. Override at the app root:
|
|
9
|
+
*
|
|
10
|
+
* providers: [{ provide: KP_LOCALE, useValue: 'fr-FR' }]
|
|
11
|
+
*
|
|
12
|
+
* Or per-feature via a child injector. Components inject this lazily —
|
|
13
|
+
* changing it at runtime requires re-rendering the affected components.
|
|
14
|
+
*/
|
|
15
|
+
const KP_LOCALE = new InjectionToken('KP_LOCALE', {
|
|
16
|
+
factory: () => typeof navigator !== 'undefined' && navigator.language
|
|
17
|
+
? navigator.language
|
|
18
|
+
: 'en-US',
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Built-in English strings. Components that read these go through
|
|
22
|
+
* `injectKpStrings()` so consumers can override per-app or per-locale.
|
|
23
|
+
*/
|
|
24
|
+
const KP_DEFAULT_STRINGS_EN = {
|
|
25
|
+
close: 'Close',
|
|
26
|
+
clear: 'Clear',
|
|
27
|
+
cancel: 'Cancel',
|
|
28
|
+
confirm: 'Confirm',
|
|
29
|
+
loading: 'Loading…',
|
|
30
|
+
noResults: 'No results found',
|
|
31
|
+
today: 'Today',
|
|
32
|
+
yesterday: 'Yesterday',
|
|
33
|
+
thisWeek: 'This week',
|
|
34
|
+
lastWeek: 'Last week',
|
|
35
|
+
thisMonth: 'This month',
|
|
36
|
+
lastMonth: 'Last month',
|
|
37
|
+
selectDate: 'Select date',
|
|
38
|
+
selectTime: 'Select time',
|
|
39
|
+
previousMonth: 'Previous month',
|
|
40
|
+
nextMonth: 'Next month',
|
|
41
|
+
dropzoneTitle: 'Drop files here or click to browse',
|
|
42
|
+
dropzoneRemove: (filename) => `Remove ${filename}`,
|
|
43
|
+
dropzoneUploaded: 'Uploaded',
|
|
44
|
+
searchPlaceholder: 'Search…',
|
|
45
|
+
commandPaletteHint: '⌘K',
|
|
46
|
+
commandPalettePlaceholder: 'Type a command or search…',
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* App-level override token. Provide a `Partial<KpLocaleStrings>` —
|
|
50
|
+
* unspecified keys fall through to the English defaults.
|
|
51
|
+
*
|
|
52
|
+
* providers: [{
|
|
53
|
+
* provide: KP_STRINGS,
|
|
54
|
+
* useValue: {
|
|
55
|
+
* close: 'Закрыть',
|
|
56
|
+
* today: 'Сегодня',
|
|
57
|
+
* commandPalettePlaceholder: 'Команда или поиск…',
|
|
58
|
+
* },
|
|
59
|
+
* }]
|
|
60
|
+
*/
|
|
61
|
+
const KP_STRINGS = new InjectionToken('KP_STRINGS', { factory: () => ({}) });
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the merged strings dictionary inside a component / directive
|
|
64
|
+
* factory. Reads both the override token and the defaults; consumers
|
|
65
|
+
* never have to remember to merge.
|
|
66
|
+
*/
|
|
67
|
+
function injectKpStrings() {
|
|
68
|
+
const overrides = inject(KP_STRINGS);
|
|
69
|
+
return { ...KP_DEFAULT_STRINGS_EN, ...overrides };
|
|
70
|
+
}
|
|
71
|
+
/** Convenience accessor with the same lifecycle constraints as `inject(KP_LOCALE)`. */
|
|
72
|
+
function injectKpLocale() {
|
|
73
|
+
return inject(KP_LOCALE);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Thin wrappers over `Intl.DateTimeFormat` so components that take
|
|
78
|
+
* `KP_LOCALE` don't have to repeat the `new Intl.DateTimeFormat(...)`
|
|
79
|
+
* dance. Each helper caches the formatter per (locale, options) — the
|
|
80
|
+
* cache is keyed on a JSON-stringified options object since `Intl`
|
|
81
|
+
* options are small and structurally compared.
|
|
82
|
+
*/
|
|
83
|
+
const CACHE = new Map();
|
|
84
|
+
function getFormatter(locale, options) {
|
|
85
|
+
const key = `${locale}::${JSON.stringify(options)}`;
|
|
86
|
+
let f = CACHE.get(key);
|
|
87
|
+
if (!f) {
|
|
88
|
+
f = new Intl.DateTimeFormat(locale, options);
|
|
89
|
+
CACHE.set(key, f);
|
|
90
|
+
}
|
|
91
|
+
return f;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Format a date in the consumer's locale.
|
|
95
|
+
*
|
|
96
|
+
* kpFormatDate(d, 'fr-FR', { dateStyle: 'medium' }) // "5 mai 2026"
|
|
97
|
+
*/
|
|
98
|
+
function kpFormatDate(date, locale, options = { dateStyle: 'medium' }) {
|
|
99
|
+
return getFormatter(locale, options).format(date);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Format the time portion of a date.
|
|
103
|
+
*
|
|
104
|
+
* kpFormatTime(d, 'en-US', { hour: 'numeric', minute: '2-digit' }) // "3:45 PM"
|
|
105
|
+
* kpFormatTime(d, 'de-DE') // "15:45"
|
|
106
|
+
*/
|
|
107
|
+
function kpFormatTime(date, locale, options = { timeStyle: 'short' }) {
|
|
108
|
+
return getFormatter(locale, options).format(date);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Localized month names. Reuses a single formatter; the calendar
|
|
112
|
+
* components walk all 12 months once on render.
|
|
113
|
+
*
|
|
114
|
+
* kpMonthNames('en-US', 'long') // ['January', 'February', ...]
|
|
115
|
+
* kpMonthNames('ru-RU', 'short') // ['янв.', 'февр.', ...]
|
|
116
|
+
*/
|
|
117
|
+
function kpMonthNames(locale, format = 'long') {
|
|
118
|
+
const fmt = getFormatter(locale, { month: format });
|
|
119
|
+
const out = [];
|
|
120
|
+
// Use day=1 of each month at year=2000 (safe — no month has fewer than 1 day,
|
|
121
|
+
// year is well after epoch + DST offsets don't shift the month).
|
|
122
|
+
for (let m = 0; m < 12; m++)
|
|
123
|
+
out.push(fmt.format(new Date(2000, m, 1)));
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Localized weekday short labels in display order, starting from
|
|
128
|
+
* `firstDayOfWeek` (0 = Sunday, 1 = Monday).
|
|
129
|
+
*
|
|
130
|
+
* kpWeekdayNames('en-US', 1, 'narrow') // ['M', 'T', 'W', 'T', 'F', 'S', 'S']
|
|
131
|
+
* kpWeekdayNames('en-US', 1, 'short') // ['Mon', 'Tue', ...]
|
|
132
|
+
*/
|
|
133
|
+
function kpWeekdayNames(locale, firstDayOfWeek = 1, format = 'short') {
|
|
134
|
+
const fmt = getFormatter(locale, { weekday: format });
|
|
135
|
+
// Pick a reference week starting on Sunday 2017-01-01 (Sunday = 0).
|
|
136
|
+
const out = [];
|
|
137
|
+
for (let i = 0; i < 7; i++) {
|
|
138
|
+
const d = new Date(2017, 0, 1 + i);
|
|
139
|
+
out.push(fmt.format(d));
|
|
140
|
+
}
|
|
141
|
+
if (firstDayOfWeek === 1) {
|
|
142
|
+
return [...out.slice(1), out[0]];
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* AM/PM marker for a given hour in the consumer's locale, or empty
|
|
148
|
+
* string if the locale uses 24h format (most of the world).
|
|
149
|
+
*
|
|
150
|
+
* kpDayPeriod(15, 'en-US') // 'PM'
|
|
151
|
+
* kpDayPeriod(15, 'de-DE') // ''
|
|
152
|
+
*/
|
|
153
|
+
function kpDayPeriod(hour, locale) {
|
|
154
|
+
const fmt = getFormatter(locale, { hour: 'numeric', hour12: true });
|
|
155
|
+
// Format "3 PM" → match trailing letters
|
|
156
|
+
const formatted = fmt.format(new Date(2000, 0, 1, hour));
|
|
157
|
+
const m = formatted.match(/[A-Za-zА-Яа-я]+\.?$/);
|
|
158
|
+
return m ? m[0] : '';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Generated bundle index. Do not edit.
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
export { KP_DEFAULT_STRINGS_EN, KP_LOCALE, KP_STRINGS, injectKpLocale, injectKpStrings, kpDayPeriod, kpFormatDate, kpFormatTime, kpMonthNames, kpWeekdayNames };
|
|
166
|
+
//# sourceMappingURL=kanso-protocol-i18n.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kanso-protocol-i18n.mjs","sources":["../../../../packages/i18n/src/tokens.ts","../../../../packages/i18n/src/intl.ts","../../../../packages/i18n/src/kanso-protocol-i18n.ts"],"sourcesContent":["import { InjectionToken, inject } from '@angular/core';\n\n/**\n * BCP-47 locale tag used by every Kanso component that calls into Intl\n * APIs (date / time / number formatting, weekday names, etc.).\n *\n * Default factory pulls from `navigator.language` if available, else\n * falls back to `'en-US'`. Override at the app root:\n *\n * providers: [{ provide: KP_LOCALE, useValue: 'fr-FR' }]\n *\n * Or per-feature via a child injector. Components inject this lazily —\n * changing it at runtime requires re-rendering the affected components.\n */\nexport const KP_LOCALE = new InjectionToken<string>('KP_LOCALE', {\n factory: () =>\n typeof navigator !== 'undefined' && navigator.language\n ? navigator.language\n : 'en-US',\n});\n\n/**\n * Static UI strings that don't have an Intl equivalent — labels, ARIA\n * fallbacks, button text. Anything that DOES have an Intl form (month\n * names, day-period markers, time formats) is derived from `KP_LOCALE`\n * via the helpers in `intl.ts` instead of being listed here.\n *\n * Add a key here only after you've confirmed it's not derivable from\n * Intl. The goal is a small registry — large registries rot fast.\n */\nexport interface KpLocaleStrings {\n // Generic chrome\n close: string;\n clear: string;\n cancel: string;\n confirm: string;\n loading: string;\n noResults: string;\n\n // Date / time pickers — buttons + presets\n today: string;\n yesterday: string;\n thisWeek: string;\n lastWeek: string;\n thisMonth: string;\n lastMonth: string;\n selectDate: string;\n selectTime: string;\n previousMonth: string;\n nextMonth: string;\n\n // File upload\n dropzoneTitle: string;\n dropzoneRemove: (filename: string) => string;\n dropzoneUploaded: string;\n\n // Search / command palette\n searchPlaceholder: string;\n commandPaletteHint: string;\n commandPalettePlaceholder: string;\n}\n\n/**\n * Built-in English strings. Components that read these go through\n * `injectKpStrings()` so consumers can override per-app or per-locale.\n */\nexport const KP_DEFAULT_STRINGS_EN: KpLocaleStrings = {\n close: 'Close',\n clear: 'Clear',\n cancel: 'Cancel',\n confirm: 'Confirm',\n loading: 'Loading…',\n noResults: 'No results found',\n\n today: 'Today',\n yesterday: 'Yesterday',\n thisWeek: 'This week',\n lastWeek: 'Last week',\n thisMonth: 'This month',\n lastMonth: 'Last month',\n selectDate: 'Select date',\n selectTime: 'Select time',\n previousMonth: 'Previous month',\n nextMonth: 'Next month',\n\n dropzoneTitle: 'Drop files here or click to browse',\n dropzoneRemove: (filename) => `Remove ${filename}`,\n dropzoneUploaded: 'Uploaded',\n\n searchPlaceholder: 'Search…',\n commandPaletteHint: '⌘K',\n commandPalettePlaceholder: 'Type a command or search…',\n};\n\n/**\n * App-level override token. Provide a `Partial<KpLocaleStrings>` —\n * unspecified keys fall through to the English defaults.\n *\n * providers: [{\n * provide: KP_STRINGS,\n * useValue: {\n * close: 'Закрыть',\n * today: 'Сегодня',\n * commandPalettePlaceholder: 'Команда или поиск…',\n * },\n * }]\n */\nexport const KP_STRINGS = new InjectionToken<Partial<KpLocaleStrings>>(\n 'KP_STRINGS',\n { factory: () => ({}) },\n);\n\n/**\n * Resolve the merged strings dictionary inside a component / directive\n * factory. Reads both the override token and the defaults; consumers\n * never have to remember to merge.\n */\nexport function injectKpStrings(): KpLocaleStrings {\n const overrides = inject(KP_STRINGS);\n return { ...KP_DEFAULT_STRINGS_EN, ...overrides };\n}\n\n/** Convenience accessor with the same lifecycle constraints as `inject(KP_LOCALE)`. */\nexport function injectKpLocale(): string {\n return inject(KP_LOCALE);\n}\n","/**\n * Thin wrappers over `Intl.DateTimeFormat` so components that take\n * `KP_LOCALE` don't have to repeat the `new Intl.DateTimeFormat(...)`\n * dance. Each helper caches the formatter per (locale, options) — the\n * cache is keyed on a JSON-stringified options object since `Intl`\n * options are small and structurally compared.\n */\n\nconst CACHE = new Map<string, Intl.DateTimeFormat>();\n\nfunction getFormatter(locale: string, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = `${locale}::${JSON.stringify(options)}`;\n let f = CACHE.get(key);\n if (!f) {\n f = new Intl.DateTimeFormat(locale, options);\n CACHE.set(key, f);\n }\n return f;\n}\n\n/**\n * Format a date in the consumer's locale.\n *\n * kpFormatDate(d, 'fr-FR', { dateStyle: 'medium' }) // \"5 mai 2026\"\n */\nexport function kpFormatDate(\n date: Date,\n locale: string,\n options: Intl.DateTimeFormatOptions = { dateStyle: 'medium' },\n): string {\n return getFormatter(locale, options).format(date);\n}\n\n/**\n * Format the time portion of a date.\n *\n * kpFormatTime(d, 'en-US', { hour: 'numeric', minute: '2-digit' }) // \"3:45 PM\"\n * kpFormatTime(d, 'de-DE') // \"15:45\"\n */\nexport function kpFormatTime(\n date: Date,\n locale: string,\n options: Intl.DateTimeFormatOptions = { timeStyle: 'short' },\n): string {\n return getFormatter(locale, options).format(date);\n}\n\n/**\n * Localized month names. Reuses a single formatter; the calendar\n * components walk all 12 months once on render.\n *\n * kpMonthNames('en-US', 'long') // ['January', 'February', ...]\n * kpMonthNames('ru-RU', 'short') // ['янв.', 'февр.', ...]\n */\nexport function kpMonthNames(\n locale: string,\n format: 'long' | 'short' | 'narrow' = 'long',\n): string[] {\n const fmt = getFormatter(locale, { month: format });\n const out: string[] = [];\n // Use day=1 of each month at year=2000 (safe — no month has fewer than 1 day,\n // year is well after epoch + DST offsets don't shift the month).\n for (let m = 0; m < 12; m++) out.push(fmt.format(new Date(2000, m, 1)));\n return out;\n}\n\n/**\n * Localized weekday short labels in display order, starting from\n * `firstDayOfWeek` (0 = Sunday, 1 = Monday).\n *\n * kpWeekdayNames('en-US', 1, 'narrow') // ['M', 'T', 'W', 'T', 'F', 'S', 'S']\n * kpWeekdayNames('en-US', 1, 'short') // ['Mon', 'Tue', ...]\n */\nexport function kpWeekdayNames(\n locale: string,\n firstDayOfWeek: 0 | 1 = 1,\n format: 'long' | 'short' | 'narrow' = 'short',\n): string[] {\n const fmt = getFormatter(locale, { weekday: format });\n // Pick a reference week starting on Sunday 2017-01-01 (Sunday = 0).\n const out: string[] = [];\n for (let i = 0; i < 7; i++) {\n const d = new Date(2017, 0, 1 + i);\n out.push(fmt.format(d));\n }\n if (firstDayOfWeek === 1) {\n return [...out.slice(1), out[0]];\n }\n return out;\n}\n\n/**\n * AM/PM marker for a given hour in the consumer's locale, or empty\n * string if the locale uses 24h format (most of the world).\n *\n * kpDayPeriod(15, 'en-US') // 'PM'\n * kpDayPeriod(15, 'de-DE') // ''\n */\nexport function kpDayPeriod(hour: number, locale: string): string {\n const fmt = getFormatter(locale, { hour: 'numeric', hour12: true });\n // Format \"3 PM\" → match trailing letters\n const formatted = fmt.format(new Date(2000, 0, 1, hour));\n const m = formatted.match(/[A-Za-zА-Яа-я]+\\.?$/);\n return m ? m[0] : '';\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kanso-protocol/i18n",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"peerDependencies": {
|
|
6
|
+
"@angular/core": "^18.0.0"
|
|
7
|
+
},
|
|
8
|
+
"description": "Kanso Protocol — i18n primitives (locale token + string registry + Intl helpers).",
|
|
9
|
+
"author": "GregNBlack",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/GregNBlack/kanso-protocol.git",
|
|
13
|
+
"directory": "packages/i18n"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://gregnblack.github.io/kanso-protocol",
|
|
16
|
+
"bugs": "https://github.com/GregNBlack/kanso-protocol/issues",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"design-system",
|
|
19
|
+
"angular",
|
|
20
|
+
"kanso",
|
|
21
|
+
"i18n",
|
|
22
|
+
"localization"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"module": "fesm2022/kanso-protocol-i18n.mjs",
|
|
26
|
+
"typings": "types/kanso-protocol-i18n.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
"./package.json": {
|
|
29
|
+
"default": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./types/kanso-protocol-i18n.d.ts",
|
|
33
|
+
"default": "./fesm2022/kanso-protocol-i18n.mjs"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"type": "module",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"tslib": "^2.3.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BCP-47 locale tag used by every Kanso component that calls into Intl
|
|
5
|
+
* APIs (date / time / number formatting, weekday names, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Default factory pulls from `navigator.language` if available, else
|
|
8
|
+
* falls back to `'en-US'`. Override at the app root:
|
|
9
|
+
*
|
|
10
|
+
* providers: [{ provide: KP_LOCALE, useValue: 'fr-FR' }]
|
|
11
|
+
*
|
|
12
|
+
* Or per-feature via a child injector. Components inject this lazily —
|
|
13
|
+
* changing it at runtime requires re-rendering the affected components.
|
|
14
|
+
*/
|
|
15
|
+
declare const KP_LOCALE: InjectionToken<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Static UI strings that don't have an Intl equivalent — labels, ARIA
|
|
18
|
+
* fallbacks, button text. Anything that DOES have an Intl form (month
|
|
19
|
+
* names, day-period markers, time formats) is derived from `KP_LOCALE`
|
|
20
|
+
* via the helpers in `intl.ts` instead of being listed here.
|
|
21
|
+
*
|
|
22
|
+
* Add a key here only after you've confirmed it's not derivable from
|
|
23
|
+
* Intl. The goal is a small registry — large registries rot fast.
|
|
24
|
+
*/
|
|
25
|
+
interface KpLocaleStrings {
|
|
26
|
+
close: string;
|
|
27
|
+
clear: string;
|
|
28
|
+
cancel: string;
|
|
29
|
+
confirm: string;
|
|
30
|
+
loading: string;
|
|
31
|
+
noResults: string;
|
|
32
|
+
today: string;
|
|
33
|
+
yesterday: string;
|
|
34
|
+
thisWeek: string;
|
|
35
|
+
lastWeek: string;
|
|
36
|
+
thisMonth: string;
|
|
37
|
+
lastMonth: string;
|
|
38
|
+
selectDate: string;
|
|
39
|
+
selectTime: string;
|
|
40
|
+
previousMonth: string;
|
|
41
|
+
nextMonth: string;
|
|
42
|
+
dropzoneTitle: string;
|
|
43
|
+
dropzoneRemove: (filename: string) => string;
|
|
44
|
+
dropzoneUploaded: string;
|
|
45
|
+
searchPlaceholder: string;
|
|
46
|
+
commandPaletteHint: string;
|
|
47
|
+
commandPalettePlaceholder: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Built-in English strings. Components that read these go through
|
|
51
|
+
* `injectKpStrings()` so consumers can override per-app or per-locale.
|
|
52
|
+
*/
|
|
53
|
+
declare const KP_DEFAULT_STRINGS_EN: KpLocaleStrings;
|
|
54
|
+
/**
|
|
55
|
+
* App-level override token. Provide a `Partial<KpLocaleStrings>` —
|
|
56
|
+
* unspecified keys fall through to the English defaults.
|
|
57
|
+
*
|
|
58
|
+
* providers: [{
|
|
59
|
+
* provide: KP_STRINGS,
|
|
60
|
+
* useValue: {
|
|
61
|
+
* close: 'Закрыть',
|
|
62
|
+
* today: 'Сегодня',
|
|
63
|
+
* commandPalettePlaceholder: 'Команда или поиск…',
|
|
64
|
+
* },
|
|
65
|
+
* }]
|
|
66
|
+
*/
|
|
67
|
+
declare const KP_STRINGS: InjectionToken<Partial<KpLocaleStrings>>;
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the merged strings dictionary inside a component / directive
|
|
70
|
+
* factory. Reads both the override token and the defaults; consumers
|
|
71
|
+
* never have to remember to merge.
|
|
72
|
+
*/
|
|
73
|
+
declare function injectKpStrings(): KpLocaleStrings;
|
|
74
|
+
/** Convenience accessor with the same lifecycle constraints as `inject(KP_LOCALE)`. */
|
|
75
|
+
declare function injectKpLocale(): string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Thin wrappers over `Intl.DateTimeFormat` so components that take
|
|
79
|
+
* `KP_LOCALE` don't have to repeat the `new Intl.DateTimeFormat(...)`
|
|
80
|
+
* dance. Each helper caches the formatter per (locale, options) — the
|
|
81
|
+
* cache is keyed on a JSON-stringified options object since `Intl`
|
|
82
|
+
* options are small and structurally compared.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* Format a date in the consumer's locale.
|
|
86
|
+
*
|
|
87
|
+
* kpFormatDate(d, 'fr-FR', { dateStyle: 'medium' }) // "5 mai 2026"
|
|
88
|
+
*/
|
|
89
|
+
declare function kpFormatDate(date: Date, locale: string, options?: Intl.DateTimeFormatOptions): string;
|
|
90
|
+
/**
|
|
91
|
+
* Format the time portion of a date.
|
|
92
|
+
*
|
|
93
|
+
* kpFormatTime(d, 'en-US', { hour: 'numeric', minute: '2-digit' }) // "3:45 PM"
|
|
94
|
+
* kpFormatTime(d, 'de-DE') // "15:45"
|
|
95
|
+
*/
|
|
96
|
+
declare function kpFormatTime(date: Date, locale: string, options?: Intl.DateTimeFormatOptions): string;
|
|
97
|
+
/**
|
|
98
|
+
* Localized month names. Reuses a single formatter; the calendar
|
|
99
|
+
* components walk all 12 months once on render.
|
|
100
|
+
*
|
|
101
|
+
* kpMonthNames('en-US', 'long') // ['January', 'February', ...]
|
|
102
|
+
* kpMonthNames('ru-RU', 'short') // ['янв.', 'февр.', ...]
|
|
103
|
+
*/
|
|
104
|
+
declare function kpMonthNames(locale: string, format?: 'long' | 'short' | 'narrow'): string[];
|
|
105
|
+
/**
|
|
106
|
+
* Localized weekday short labels in display order, starting from
|
|
107
|
+
* `firstDayOfWeek` (0 = Sunday, 1 = Monday).
|
|
108
|
+
*
|
|
109
|
+
* kpWeekdayNames('en-US', 1, 'narrow') // ['M', 'T', 'W', 'T', 'F', 'S', 'S']
|
|
110
|
+
* kpWeekdayNames('en-US', 1, 'short') // ['Mon', 'Tue', ...]
|
|
111
|
+
*/
|
|
112
|
+
declare function kpWeekdayNames(locale: string, firstDayOfWeek?: 0 | 1, format?: 'long' | 'short' | 'narrow'): string[];
|
|
113
|
+
/**
|
|
114
|
+
* AM/PM marker for a given hour in the consumer's locale, or empty
|
|
115
|
+
* string if the locale uses 24h format (most of the world).
|
|
116
|
+
*
|
|
117
|
+
* kpDayPeriod(15, 'en-US') // 'PM'
|
|
118
|
+
* kpDayPeriod(15, 'de-DE') // ''
|
|
119
|
+
*/
|
|
120
|
+
declare function kpDayPeriod(hour: number, locale: string): string;
|
|
121
|
+
|
|
122
|
+
export { KP_DEFAULT_STRINGS_EN, KP_LOCALE, KP_STRINGS, injectKpLocale, injectKpStrings, kpDayPeriod, kpFormatDate, kpFormatTime, kpMonthNames, kpWeekdayNames };
|
|
123
|
+
export type { KpLocaleStrings };
|