@tuwaio/docs-ui 0.5.2 → 0.5.4

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,131 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var locale_exports = {};
20
+ __export(locale_exports, {
21
+ DEFAULT_LOCALE: () => DEFAULT_LOCALE,
22
+ LOCALE_STORAGE_KEY: () => LOCALE_STORAGE_KEY,
23
+ TUWA_LOCALES: () => TUWA_LOCALES,
24
+ detectLocale: () => detectLocale,
25
+ formatLabel: () => formatLabel,
26
+ normalizeLocale: () => normalizeLocale,
27
+ policyLanguageOf: () => policyLanguageOf,
28
+ resolveClientLocale: () => resolveClientLocale
29
+ });
30
+ module.exports = __toCommonJS(locale_exports);
31
+ const TUWA_LOCALES = ["en", "uk", "zh-CN"];
32
+ const DEFAULT_LOCALE = "en";
33
+ const LOCALE_STORAGE_KEY = "tuwa-locale";
34
+ const UKRAINE_TIMEZONES = /* @__PURE__ */ new Set([
35
+ "europe/kyiv",
36
+ "europe/kiev",
37
+ "europe/uzhgorod",
38
+ "europe/zaporozhye",
39
+ "europe/simferopol"
40
+ ]);
41
+ const CHINA_TIMEZONES = /* @__PURE__ */ new Set([
42
+ "asia/shanghai",
43
+ "asia/chongqing",
44
+ "asia/harbin",
45
+ "asia/urumqi",
46
+ "asia/beijing",
47
+ "prc"
48
+ ]);
49
+ const MASKED_TIMEZONES = /* @__PURE__ */ new Set(["", "utc", "gmt", "etc/utc", "etc/gmt"]);
50
+ function hasUkraineRegion(tag) {
51
+ return /(?:^|[-_])ua(?:$|[-_])/.test(tag);
52
+ }
53
+ function isSimplifiedChinese(tag) {
54
+ if (tag === "zh" || tag === "zh-cn" || tag === "zh_cn" || tag.startsWith("zh-hans") || tag.startsWith("zh_hans")) {
55
+ return true;
56
+ }
57
+ return tag.startsWith("zh-") && !/hant|tw|hk|mo/.test(tag);
58
+ }
59
+ function normalizeLocale(tag) {
60
+ if (!tag) return null;
61
+ const lower = tag.trim().toLowerCase();
62
+ if (!lower) return null;
63
+ if (lower === "uk" || lower === "ua" || lower.startsWith("uk-") || lower.startsWith("uk_")) return "uk";
64
+ if (isSimplifiedChinese(lower)) return "zh-CN";
65
+ if (lower === "en" || lower.startsWith("en-") || lower.startsWith("en_")) return "en";
66
+ return null;
67
+ }
68
+ function detectLocale(signals) {
69
+ const stored = normalizeLocale(signals.stored);
70
+ if (stored) return stored;
71
+ const languages = (signals.languages ?? []).map((tag) => tag.trim().toLowerCase()).filter(Boolean);
72
+ for (const tag of languages) {
73
+ const locale = normalizeLocale(tag);
74
+ if (locale === "uk" || locale === "zh-CN") return locale;
75
+ if (locale === "en" && !hasUkraineRegion(tag)) break;
76
+ }
77
+ const timeZone = (signals.timeZone ?? "").trim().toLowerCase();
78
+ const systemLocale = (signals.systemLocale ?? "").trim().toLowerCase();
79
+ const explicitEnglish = languages.some((tag) => normalizeLocale(tag) === "en" && !hasUkraineRegion(tag));
80
+ if (UKRAINE_TIMEZONES.has(timeZone) || hasUkraineRegion(systemLocale) || languages.some(hasUkraineRegion)) {
81
+ return "uk";
82
+ }
83
+ if (CHINA_TIMEZONES.has(timeZone) || systemLocale && isSimplifiedChinese(systemLocale)) {
84
+ return "zh-CN";
85
+ }
86
+ if (!explicitEnglish && MASKED_TIMEZONES.has(timeZone) && languages.some((tag) => tag === "ru" || tag.startsWith("ru-")) && signals.januaryOffset === -120 && signals.julyOffset === -180) {
87
+ return "uk";
88
+ }
89
+ return DEFAULT_LOCALE;
90
+ }
91
+ function resolveClientLocale(storageKey = LOCALE_STORAGE_KEY) {
92
+ if (typeof window === "undefined") return DEFAULT_LOCALE;
93
+ const signals = {};
94
+ try {
95
+ signals.stored = window.localStorage.getItem(storageKey);
96
+ } catch {
97
+ }
98
+ try {
99
+ if (typeof navigator !== "undefined") {
100
+ signals.languages = navigator.languages?.length ? navigator.languages : [navigator.language];
101
+ }
102
+ } catch {
103
+ }
104
+ try {
105
+ const options = Intl.DateTimeFormat().resolvedOptions();
106
+ signals.timeZone = options.timeZone;
107
+ signals.systemLocale = options.locale;
108
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
109
+ signals.januaryOffset = new Date(year, 0, 1).getTimezoneOffset();
110
+ signals.julyOffset = new Date(year, 6, 1).getTimezoneOffset();
111
+ } catch {
112
+ }
113
+ return detectLocale(signals);
114
+ }
115
+ function formatLabel(template, values) {
116
+ return template.replace(/\{(\w+)\}/g, (match, key) => key in values ? String(values[key]) : match);
117
+ }
118
+ function policyLanguageOf(locale) {
119
+ return locale === "uk" ? "uk" : "en";
120
+ }
121
+ // Annotate the CommonJS export names for ESM import in node:
122
+ 0 && (module.exports = {
123
+ DEFAULT_LOCALE,
124
+ LOCALE_STORAGE_KEY,
125
+ TUWA_LOCALES,
126
+ detectLocale,
127
+ formatLabel,
128
+ normalizeLocale,
129
+ policyLanguageOf,
130
+ resolveClientLocale
131
+ });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Locale primitives shared by the TUWA sites (docs hub, website, Quasar dashboard). Pure functions without React and
3
+ * without a `'use client'` directive, so server code (metadata, sitemaps) can call them too.
4
+ *
5
+ * Privacy: the locale is resolved from signals the browser already has (Local Storage, `navigator.languages`, the
6
+ * time zone). No GeoIP lookup, no cookie and no request is ever made.
7
+ */
8
+ /** A locale of the TUWA sites. */
9
+ type TuwaLocale = 'en' | 'uk' | 'zh-CN';
10
+ /** Every supported locale, the default first. */
11
+ declare const TUWA_LOCALES: readonly TuwaLocale[];
12
+ /** The locale used on the server and when nothing else matches. */
13
+ declare const DEFAULT_LOCALE: TuwaLocale;
14
+ /**
15
+ * Local Storage key of the locale the visitor picked. Shared by every TUWA site, so a choice on one origin of the same
16
+ * site is kept; list it in `PreferenceExpiry` to forget it after 30 days (Cookie Policy).
17
+ */
18
+ declare const LOCALE_STORAGE_KEY = "tuwa-locale";
19
+ /**
20
+ * Maps a language tag or a stored value to a supported locale.
21
+ *
22
+ * @param tag - A BCP 47 tag (`uk-UA`, `zh-Hans`, `en-US`), a region alias (`ua`) or a stored value.
23
+ * @returns The matching {@link TuwaLocale}, or `null` when the tag names none.
24
+ */
25
+ declare function normalizeLocale(tag: string | null | undefined): TuwaLocale | null;
26
+ /**
27
+ * Browser signals read by {@link detectLocale}. Each is optional, so tests and server code can pass what they have.
28
+ */
29
+ interface LocaleSignals {
30
+ /** The locale saved under {@link LOCALE_STORAGE_KEY}. */
31
+ stored?: string | null;
32
+ /** `navigator.languages`, or `[navigator.language]`. */
33
+ languages?: readonly string[];
34
+ /** IANA time zone, `Intl.DateTimeFormat().resolvedOptions().timeZone`. */
35
+ timeZone?: string;
36
+ /** Locale of the system, `Intl.DateTimeFormat().resolvedOptions().locale`. */
37
+ systemLocale?: string;
38
+ /** Offset of January 1 in minutes, `new Date(year, 0, 1).getTimezoneOffset()`. */
39
+ januaryOffset?: number;
40
+ /** Offset of July 1 in minutes, `new Date(year, 6, 1).getTimezoneOffset()`. */
41
+ julyOffset?: number;
42
+ }
43
+ /**
44
+ * Picks the locale from browser signals, in this order:
45
+ * 1. The locale the visitor picked (`stored`).
46
+ * 2. Ukrainian or Simplified Chinese in the browser languages, in the order the visitor ranked them.
47
+ * 3. A Ukrainian time zone, or a language or system locale with the region Ukraine (`ru-UA`, `en-UA`).
48
+ * 4. A mainland China time zone or system locale.
49
+ * 5. Russian with a masked time zone (UTC) and the Eastern European offsets (UTC+2 in winter, UTC+3 in summer).
50
+ * 6. English.
51
+ *
52
+ * @param signals - What the browser exposes.
53
+ * @returns The resolved {@link TuwaLocale}.
54
+ */
55
+ declare function detectLocale(signals: LocaleSignals): TuwaLocale;
56
+ /**
57
+ * Reads the browser signals and resolves the locale with {@link detectLocale}. Returns {@link DEFAULT_LOCALE} on the
58
+ * server. Never throws: blocked storage or a missing `Intl` only drop that signal.
59
+ *
60
+ * @param storageKey - Local Storage key of the picked locale (default: {@link LOCALE_STORAGE_KEY}).
61
+ * @returns The resolved {@link TuwaLocale}.
62
+ */
63
+ declare function resolveClientLocale(storageKey?: string): TuwaLocale;
64
+ /**
65
+ * Replaces `{name}` tokens of a template with values, as the labels of Nova UI Kit do. Unknown tokens stay.
66
+ *
67
+ * @param template - Text with `{name}` tokens.
68
+ * @param values - Values by token name.
69
+ * @returns The text with the values in place.
70
+ */
71
+ declare function formatLabel(template: string, values: Record<string, string | number>): string;
72
+ /**
73
+ * The language of the TUWA policies for a site locale: policies exist in English and Ukrainian only.
74
+ *
75
+ * @param locale - The site locale.
76
+ * @returns `uk` for Ukrainian, `en` otherwise.
77
+ */
78
+ declare function policyLanguageOf(locale: TuwaLocale): 'en' | 'uk';
79
+
80
+ export { DEFAULT_LOCALE, LOCALE_STORAGE_KEY, TUWA_LOCALES, detectLocale, formatLabel, normalizeLocale, policyLanguageOf, resolveClientLocale };
81
+ export type { LocaleSignals, TuwaLocale };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Locale primitives shared by the TUWA sites (docs hub, website, Quasar dashboard). Pure functions without React and
3
+ * without a `'use client'` directive, so server code (metadata, sitemaps) can call them too.
4
+ *
5
+ * Privacy: the locale is resolved from signals the browser already has (Local Storage, `navigator.languages`, the
6
+ * time zone). No GeoIP lookup, no cookie and no request is ever made.
7
+ */
8
+ /** A locale of the TUWA sites. */
9
+ type TuwaLocale = 'en' | 'uk' | 'zh-CN';
10
+ /** Every supported locale, the default first. */
11
+ declare const TUWA_LOCALES: readonly TuwaLocale[];
12
+ /** The locale used on the server and when nothing else matches. */
13
+ declare const DEFAULT_LOCALE: TuwaLocale;
14
+ /**
15
+ * Local Storage key of the locale the visitor picked. Shared by every TUWA site, so a choice on one origin of the same
16
+ * site is kept; list it in `PreferenceExpiry` to forget it after 30 days (Cookie Policy).
17
+ */
18
+ declare const LOCALE_STORAGE_KEY = "tuwa-locale";
19
+ /**
20
+ * Maps a language tag or a stored value to a supported locale.
21
+ *
22
+ * @param tag - A BCP 47 tag (`uk-UA`, `zh-Hans`, `en-US`), a region alias (`ua`) or a stored value.
23
+ * @returns The matching {@link TuwaLocale}, or `null` when the tag names none.
24
+ */
25
+ declare function normalizeLocale(tag: string | null | undefined): TuwaLocale | null;
26
+ /**
27
+ * Browser signals read by {@link detectLocale}. Each is optional, so tests and server code can pass what they have.
28
+ */
29
+ interface LocaleSignals {
30
+ /** The locale saved under {@link LOCALE_STORAGE_KEY}. */
31
+ stored?: string | null;
32
+ /** `navigator.languages`, or `[navigator.language]`. */
33
+ languages?: readonly string[];
34
+ /** IANA time zone, `Intl.DateTimeFormat().resolvedOptions().timeZone`. */
35
+ timeZone?: string;
36
+ /** Locale of the system, `Intl.DateTimeFormat().resolvedOptions().locale`. */
37
+ systemLocale?: string;
38
+ /** Offset of January 1 in minutes, `new Date(year, 0, 1).getTimezoneOffset()`. */
39
+ januaryOffset?: number;
40
+ /** Offset of July 1 in minutes, `new Date(year, 6, 1).getTimezoneOffset()`. */
41
+ julyOffset?: number;
42
+ }
43
+ /**
44
+ * Picks the locale from browser signals, in this order:
45
+ * 1. The locale the visitor picked (`stored`).
46
+ * 2. Ukrainian or Simplified Chinese in the browser languages, in the order the visitor ranked them.
47
+ * 3. A Ukrainian time zone, or a language or system locale with the region Ukraine (`ru-UA`, `en-UA`).
48
+ * 4. A mainland China time zone or system locale.
49
+ * 5. Russian with a masked time zone (UTC) and the Eastern European offsets (UTC+2 in winter, UTC+3 in summer).
50
+ * 6. English.
51
+ *
52
+ * @param signals - What the browser exposes.
53
+ * @returns The resolved {@link TuwaLocale}.
54
+ */
55
+ declare function detectLocale(signals: LocaleSignals): TuwaLocale;
56
+ /**
57
+ * Reads the browser signals and resolves the locale with {@link detectLocale}. Returns {@link DEFAULT_LOCALE} on the
58
+ * server. Never throws: blocked storage or a missing `Intl` only drop that signal.
59
+ *
60
+ * @param storageKey - Local Storage key of the picked locale (default: {@link LOCALE_STORAGE_KEY}).
61
+ * @returns The resolved {@link TuwaLocale}.
62
+ */
63
+ declare function resolveClientLocale(storageKey?: string): TuwaLocale;
64
+ /**
65
+ * Replaces `{name}` tokens of a template with values, as the labels of Nova UI Kit do. Unknown tokens stay.
66
+ *
67
+ * @param template - Text with `{name}` tokens.
68
+ * @param values - Values by token name.
69
+ * @returns The text with the values in place.
70
+ */
71
+ declare function formatLabel(template: string, values: Record<string, string | number>): string;
72
+ /**
73
+ * The language of the TUWA policies for a site locale: policies exist in English and Ukrainian only.
74
+ *
75
+ * @param locale - The site locale.
76
+ * @returns `uk` for Ukrainian, `en` otherwise.
77
+ */
78
+ declare function policyLanguageOf(locale: TuwaLocale): 'en' | 'uk';
79
+
80
+ export { DEFAULT_LOCALE, LOCALE_STORAGE_KEY, TUWA_LOCALES, detectLocale, formatLabel, normalizeLocale, policyLanguageOf, resolveClientLocale };
81
+ export type { LocaleSignals, TuwaLocale };
package/dist/locale.js ADDED
@@ -0,0 +1,100 @@
1
+ const TUWA_LOCALES = ["en", "uk", "zh-CN"];
2
+ const DEFAULT_LOCALE = "en";
3
+ const LOCALE_STORAGE_KEY = "tuwa-locale";
4
+ const UKRAINE_TIMEZONES = /* @__PURE__ */ new Set([
5
+ "europe/kyiv",
6
+ "europe/kiev",
7
+ "europe/uzhgorod",
8
+ "europe/zaporozhye",
9
+ "europe/simferopol"
10
+ ]);
11
+ const CHINA_TIMEZONES = /* @__PURE__ */ new Set([
12
+ "asia/shanghai",
13
+ "asia/chongqing",
14
+ "asia/harbin",
15
+ "asia/urumqi",
16
+ "asia/beijing",
17
+ "prc"
18
+ ]);
19
+ const MASKED_TIMEZONES = /* @__PURE__ */ new Set(["", "utc", "gmt", "etc/utc", "etc/gmt"]);
20
+ function hasUkraineRegion(tag) {
21
+ return /(?:^|[-_])ua(?:$|[-_])/.test(tag);
22
+ }
23
+ function isSimplifiedChinese(tag) {
24
+ if (tag === "zh" || tag === "zh-cn" || tag === "zh_cn" || tag.startsWith("zh-hans") || tag.startsWith("zh_hans")) {
25
+ return true;
26
+ }
27
+ return tag.startsWith("zh-") && !/hant|tw|hk|mo/.test(tag);
28
+ }
29
+ function normalizeLocale(tag) {
30
+ if (!tag) return null;
31
+ const lower = tag.trim().toLowerCase();
32
+ if (!lower) return null;
33
+ if (lower === "uk" || lower === "ua" || lower.startsWith("uk-") || lower.startsWith("uk_")) return "uk";
34
+ if (isSimplifiedChinese(lower)) return "zh-CN";
35
+ if (lower === "en" || lower.startsWith("en-") || lower.startsWith("en_")) return "en";
36
+ return null;
37
+ }
38
+ function detectLocale(signals) {
39
+ const stored = normalizeLocale(signals.stored);
40
+ if (stored) return stored;
41
+ const languages = (signals.languages ?? []).map((tag) => tag.trim().toLowerCase()).filter(Boolean);
42
+ for (const tag of languages) {
43
+ const locale = normalizeLocale(tag);
44
+ if (locale === "uk" || locale === "zh-CN") return locale;
45
+ if (locale === "en" && !hasUkraineRegion(tag)) break;
46
+ }
47
+ const timeZone = (signals.timeZone ?? "").trim().toLowerCase();
48
+ const systemLocale = (signals.systemLocale ?? "").trim().toLowerCase();
49
+ const explicitEnglish = languages.some((tag) => normalizeLocale(tag) === "en" && !hasUkraineRegion(tag));
50
+ if (UKRAINE_TIMEZONES.has(timeZone) || hasUkraineRegion(systemLocale) || languages.some(hasUkraineRegion)) {
51
+ return "uk";
52
+ }
53
+ if (CHINA_TIMEZONES.has(timeZone) || systemLocale && isSimplifiedChinese(systemLocale)) {
54
+ return "zh-CN";
55
+ }
56
+ if (!explicitEnglish && MASKED_TIMEZONES.has(timeZone) && languages.some((tag) => tag === "ru" || tag.startsWith("ru-")) && signals.januaryOffset === -120 && signals.julyOffset === -180) {
57
+ return "uk";
58
+ }
59
+ return DEFAULT_LOCALE;
60
+ }
61
+ function resolveClientLocale(storageKey = LOCALE_STORAGE_KEY) {
62
+ if (typeof window === "undefined") return DEFAULT_LOCALE;
63
+ const signals = {};
64
+ try {
65
+ signals.stored = window.localStorage.getItem(storageKey);
66
+ } catch {
67
+ }
68
+ try {
69
+ if (typeof navigator !== "undefined") {
70
+ signals.languages = navigator.languages?.length ? navigator.languages : [navigator.language];
71
+ }
72
+ } catch {
73
+ }
74
+ try {
75
+ const options = Intl.DateTimeFormat().resolvedOptions();
76
+ signals.timeZone = options.timeZone;
77
+ signals.systemLocale = options.locale;
78
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
79
+ signals.januaryOffset = new Date(year, 0, 1).getTimezoneOffset();
80
+ signals.julyOffset = new Date(year, 6, 1).getTimezoneOffset();
81
+ } catch {
82
+ }
83
+ return detectLocale(signals);
84
+ }
85
+ function formatLabel(template, values) {
86
+ return template.replace(/\{(\w+)\}/g, (match, key) => key in values ? String(values[key]) : match);
87
+ }
88
+ function policyLanguageOf(locale) {
89
+ return locale === "uk" ? "uk" : "en";
90
+ }
91
+ export {
92
+ DEFAULT_LOCALE,
93
+ LOCALE_STORAGE_KEY,
94
+ TUWA_LOCALES,
95
+ detectLocale,
96
+ formatLabel,
97
+ normalizeLocale,
98
+ policyLanguageOf,
99
+ resolveClientLocale
100
+ };
package/dist/policies.cjs CHANGED
@@ -25,6 +25,7 @@ __export(policies_exports, {
25
25
  POLICY_SITE_URL: () => POLICY_SITE_URL,
26
26
  POLICY_SOURCE: () => POLICY_SOURCE,
27
27
  footerPolicies: () => footerPolicies,
28
+ formatPolicyDate: () => formatPolicyDate,
28
29
  getPolicyFileUrl: () => getPolicyFileUrl,
29
30
  getPolicyPageUrl: () => getPolicyPageUrl,
30
31
  loadPolicy: () => loadPolicy,
@@ -134,6 +135,41 @@ async function loadPolicy(slug, options = {}) {
134
135
  languages: languages.length > 0 ? languages : ["en"]
135
136
  };
136
137
  }
138
+ const MONTH_MAP = {
139
+ january: 0,
140
+ february: 1,
141
+ march: 2,
142
+ april: 3,
143
+ may: 4,
144
+ june: 5,
145
+ july: 6,
146
+ august: 7,
147
+ september: 8,
148
+ october: 9,
149
+ november: 10,
150
+ december: 11
151
+ };
152
+ function formatPolicyDate(dateStr, locale) {
153
+ let parsed;
154
+ const match = dateStr.trim().match(/^([A-Za-z]+)\s+(\d{1,2}),?\s+(\d{4})/);
155
+ if (match) {
156
+ const monthIndex = MONTH_MAP[match[1].toLowerCase()];
157
+ if (monthIndex !== void 0) {
158
+ parsed = new Date(Date.UTC(Number(match[3]), monthIndex, Number(match[2])));
159
+ }
160
+ }
161
+ if (!parsed || Number.isNaN(parsed.getTime())) {
162
+ parsed = new Date(dateStr.includes("UTC") ? dateStr : `${dateStr} UTC`);
163
+ }
164
+ if (Number.isNaN(parsed.getTime())) return dateStr;
165
+ const bcp47 = locale === "uk" ? "uk-UA" : locale === "zh-CN" ? "zh-CN" : "en-US";
166
+ return parsed.toLocaleDateString(bcp47, {
167
+ year: "numeric",
168
+ month: "long",
169
+ day: "numeric",
170
+ timeZone: "UTC"
171
+ });
172
+ }
137
173
  // Annotate the CommonJS export names for ESM import in node:
138
174
  0 && (module.exports = {
139
175
  POLICIES_INDEX_URL,
@@ -143,6 +179,7 @@ async function loadPolicy(slug, options = {}) {
143
179
  POLICY_SITE_URL,
144
180
  POLICY_SOURCE,
145
181
  footerPolicies,
182
+ formatPolicyDate,
146
183
  getPolicyFileUrl,
147
184
  getPolicyPageUrl,
148
185
  loadPolicy,
@@ -124,6 +124,14 @@ declare function loadPolicy(slug: PolicySlug, options?: {
124
124
  revalidate?: number | false;
125
125
  language?: PolicyLanguage;
126
126
  }): Promise<PolicyContent>;
127
+ /**
128
+ * Formats a policy date string (e.g. `'October 14, 2026'`) according to the active locale or policy language.
129
+ *
130
+ * @param dateStr - The date string from the policy document.
131
+ * @param locale - The target locale (`'uk'`, `'zh-CN'`, `'en'`, or BCP 47 tag).
132
+ * @returns The formatted date, or the original string if parsing fails.
133
+ */
134
+ declare function formatPolicyDate(dateStr: string, locale?: string): string;
127
135
 
128
- export { POLICIES_INDEX_URL, POLICY_DOCUMENT_LABELS, POLICY_LANGUAGES, POLICY_LANGUAGE_NAMES, POLICY_SITE_URL, POLICY_SOURCE, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, policyLanguageFrom, publishedPolicies };
136
+ export { POLICIES_INDEX_URL, POLICY_DOCUMENT_LABELS, POLICY_LANGUAGES, POLICY_LANGUAGE_NAMES, POLICY_SITE_URL, POLICY_SOURCE, footerPolicies, formatPolicyDate, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, policyLanguageFrom, publishedPolicies };
129
137
  export type { Policy, PolicyContent, PolicyDocumentLabels, PolicyLanguage, PolicySection, PolicySlug };
@@ -124,6 +124,14 @@ declare function loadPolicy(slug: PolicySlug, options?: {
124
124
  revalidate?: number | false;
125
125
  language?: PolicyLanguage;
126
126
  }): Promise<PolicyContent>;
127
+ /**
128
+ * Formats a policy date string (e.g. `'October 14, 2026'`) according to the active locale or policy language.
129
+ *
130
+ * @param dateStr - The date string from the policy document.
131
+ * @param locale - The target locale (`'uk'`, `'zh-CN'`, `'en'`, or BCP 47 tag).
132
+ * @returns The formatted date, or the original string if parsing fails.
133
+ */
134
+ declare function formatPolicyDate(dateStr: string, locale?: string): string;
127
135
 
128
- export { POLICIES_INDEX_URL, POLICY_DOCUMENT_LABELS, POLICY_LANGUAGES, POLICY_LANGUAGE_NAMES, POLICY_SITE_URL, POLICY_SOURCE, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, policyLanguageFrom, publishedPolicies };
136
+ export { POLICIES_INDEX_URL, POLICY_DOCUMENT_LABELS, POLICY_LANGUAGES, POLICY_LANGUAGE_NAMES, POLICY_SITE_URL, POLICY_SOURCE, footerPolicies, formatPolicyDate, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, policyLanguageFrom, publishedPolicies };
129
137
  export type { Policy, PolicyContent, PolicyDocumentLabels, PolicyLanguage, PolicySection, PolicySlug };
package/dist/policies.js CHANGED
@@ -99,6 +99,41 @@ async function loadPolicy(slug, options = {}) {
99
99
  languages: languages.length > 0 ? languages : ["en"]
100
100
  };
101
101
  }
102
+ const MONTH_MAP = {
103
+ january: 0,
104
+ february: 1,
105
+ march: 2,
106
+ april: 3,
107
+ may: 4,
108
+ june: 5,
109
+ july: 6,
110
+ august: 7,
111
+ september: 8,
112
+ october: 9,
113
+ november: 10,
114
+ december: 11
115
+ };
116
+ function formatPolicyDate(dateStr, locale) {
117
+ let parsed;
118
+ const match = dateStr.trim().match(/^([A-Za-z]+)\s+(\d{1,2}),?\s+(\d{4})/);
119
+ if (match) {
120
+ const monthIndex = MONTH_MAP[match[1].toLowerCase()];
121
+ if (monthIndex !== void 0) {
122
+ parsed = new Date(Date.UTC(Number(match[3]), monthIndex, Number(match[2])));
123
+ }
124
+ }
125
+ if (!parsed || Number.isNaN(parsed.getTime())) {
126
+ parsed = new Date(dateStr.includes("UTC") ? dateStr : `${dateStr} UTC`);
127
+ }
128
+ if (Number.isNaN(parsed.getTime())) return dateStr;
129
+ const bcp47 = locale === "uk" ? "uk-UA" : locale === "zh-CN" ? "zh-CN" : "en-US";
130
+ return parsed.toLocaleDateString(bcp47, {
131
+ year: "numeric",
132
+ month: "long",
133
+ day: "numeric",
134
+ timeZone: "UTC"
135
+ });
136
+ }
102
137
  export {
103
138
  POLICIES_INDEX_URL,
104
139
  POLICY_DOCUMENT_LABELS,
@@ -107,6 +142,7 @@ export {
107
142
  POLICY_SITE_URL,
108
143
  POLICY_SOURCE,
109
144
  footerPolicies,
145
+ formatPolicyDate,
110
146
  getPolicyFileUrl,
111
147
  getPolicyPageUrl,
112
148
  loadPolicy,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuwaio/docs-ui",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "private": false,
5
5
  "author": "Oleksandr Tkach",
6
6
  "license": "Apache-2.0",
@@ -23,6 +23,16 @@
23
23
  "import": "./dist/policies.js",
24
24
  "require": "./dist/policies.cjs"
25
25
  },
26
+ "./locale": {
27
+ "types": "./dist/locale.d.ts",
28
+ "import": "./dist/locale.js",
29
+ "require": "./dist/locale.cjs"
30
+ },
31
+ "./i18n": {
32
+ "types": "./dist/i18n.d.ts",
33
+ "import": "./dist/i18n.js",
34
+ "require": "./dist/i18n.cjs"
35
+ },
26
36
  "./dist/index.css": "./dist/index.css",
27
37
  "./dist/*": "./dist/*"
28
38
  },
@@ -61,7 +71,7 @@
61
71
  }
62
72
  ],
63
73
  "dependencies": {
64
- "@tuwaio/nova-core": "^0.10.2",
74
+ "@tuwaio/nova-core": "^0.10.3",
65
75
  "clsx": "^2.1.1",
66
76
  "html-react-parser": "^6.1.9",
67
77
  "tailwind-merge": "^3.7.0"