@peax/consent-banner-locales 0.7.0 → 0.8.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.
package/README.md CHANGED
@@ -31,6 +31,23 @@ The default export is the complete map. Named root imports support normal ESM tr
31
31
 
32
32
  Supported codes: `bg`, `cs`, `da`, `de`, `el`, `en`, `es`, `et`, `fi`, `fr`, `ga`, `hr`, `hu`, `it`, `lt`, `lv`, `mt`, `nl`, `pl`, `pt`, `ro`, `ru`, `sk`, `sl`, `sv`. Portuguese uses European wording.
33
33
 
34
+ ## Your own copy over a language
35
+
36
+ Keep only the lines you change, for example in a `locales.json`, and lay them over the dictionaries you import. Every field you leave out stays the dictionary's, so the banner never falls back to English mid-sentence:
37
+
38
+ ```tsx
39
+ import { de, fr, mergeWithDefaultLocales } from '@peax/consent-banner-locales'
40
+ import copy from './locales.json' // { "de": { "banner": { "title": "Kekse" } } }
41
+
42
+ const locales = mergeWithDefaultLocales({ de, fr }, copy)
43
+
44
+ <Banner locale="de" locales={locales} />
45
+ ```
46
+
47
+ Merge once, as above, or memoize the call: every merge returns a new map, and the banner resolves its copy again whenever `locales` is a new object.
48
+
49
+ The merge is field by field: an omitted, `null`, or `undefined` field keeps the dictionary's line, and an empty string is kept (`text: ''` hides a section description). Sections your copy adds, such as `custom-1`, are kept. Tags match as the banner matches them, trimmed and case-insensitive. Copy for a regional tag without its own dictionary, such as `de-AT`, is laid over its base language (`de`). Copy for a language you pass no dictionary for is kept as it is and falls back to English. A namespace or section that is not an object, such as `"banner": "Kekse"`, changes nothing. Every dictionary you pass is kept, one whose copy changes no line is returned as it is, and neither input is changed. Import only the languages you use: the helper adds no dictionaries of its own.
50
+
34
51
  ## Copy and fallback
35
52
 
36
53
  Banner fields: `title`, `collapsedText`, `acceptAllLabel`, `rejectAllLabel`, `customizePreferencesLabel`, `preferencesTitle`, `closePreferencesLabel`, `applyPreferencesLabel`, and `cookiePolicyLabel`. Optional `preferencesText` has no bundled default. `collapsedText` includes a `<cookiePolicy>` placeholder; the application supplies its URL through `cookiePolicyUrl`.
@@ -43,7 +60,7 @@ The shared resolver matches exact tags, then their base language, ignoring case
43
60
 
44
61
  | Entry | Exports |
45
62
  | --- | --- |
46
- | `@peax/consent-banner-locales` | Default complete map and named `en`, `de`, `fr`, etc. |
63
+ | `@peax/consent-banner-locales` | Default complete map, named `en`, `de`, `fr`, etc., and `mergeWithDefaultLocales` |
47
64
  | `@peax/consent-banner-locales/<code>` | Named composite dictionary, e.g. `de` |
48
65
 
49
66
  Replace `dePreferences` with `de.preferences` and old banner fields with `de.banner`. Parser/types now live in `@peax/consent-banner-shared`. The old package-bound resolvers are removed; shared `resolveLocaleContent` accepts the application's map. Pass dictionaries explicitly to Banner after upgrading.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ declare type BannerLocales = Readonly<Record<string, LocaleContent | undefined>>;
2
+
1
3
  declare type BannerMessages = Readonly<{
2
4
  title: string;
3
5
  collapsedText: string;
@@ -42,6 +44,11 @@ export declare const hu: LocaleMessages;
42
44
  declare const it_2: LocaleMessages;
43
45
  export { it_2 as it }
44
46
 
47
+ declare type LocaleContent = Readonly<{
48
+ banner?: Partial<BannerMessages>;
49
+ preferences?: PreferencesLocaleContent;
50
+ }>;
51
+
45
52
  /** Complete copy shipped by a built-in language entry. */
46
53
  declare type LocaleMessages = Readonly<{
47
54
  banner: BannerMessages;
@@ -156,12 +163,38 @@ export declare const lt: LocaleMessages;
156
163
 
157
164
  export declare const lv: LocaleMessages;
158
165
 
166
+ /**
167
+ * Lays an application's copy over dictionaries it imports, field by field, so the banner gets
168
+ * a complete dictionary for every language that has one and the application keeps only the
169
+ * lines it changed. A field the copy omits, or sets to `null` or `undefined`, is the
170
+ * dictionary's; an empty string is kept. Sections the copy adds are kept as they are.
171
+ *
172
+ * Tags are matched as the banner matches them: trimmed and case-insensitive. Copy for a tag
173
+ * with no dictionary of its own, such as `de-AT`, is laid over its base language's (`de`); copy
174
+ * for a language with no dictionary at all is kept as it is and falls back to English in the
175
+ * banner. Every dictionary passed in is kept, written over or not, and one whose copy changes
176
+ * no line is returned as it is. The inputs are not changed.
177
+ *
178
+ * ```ts
179
+ * import { de, fr, mergeWithDefaultLocales } from '@peax/consent-banner-locales'
180
+ *
181
+ * const locales = mergeWithDefaultLocales({ de, fr }, { de: { banner: { title: 'Kekse' } } })
182
+ * ```
183
+ */
184
+ export declare const mergeWithDefaultLocales: (defaults?: BannerLocales | null, copies?: BannerLocales | null) => BannerLocales;
185
+
159
186
  export declare const mt: LocaleMessages;
160
187
 
161
188
  export declare const nl: LocaleMessages;
162
189
 
163
190
  export declare const pl: LocaleMessages;
164
191
 
192
+ /** Application copy can translate custom sections and omit any field. */
193
+ declare type PreferencesLocaleContent = Readonly<Record<string, Readonly<{
194
+ title?: string;
195
+ text?: string;
196
+ }> | undefined>>;
197
+
165
198
  declare type PreferencesMessages = Readonly<Record<BuiltInPreferencesSectionId, Readonly<{
166
199
  title: string;
167
200
  text: string;
package/dist/index.js CHANGED
@@ -23,8 +23,36 @@ import { ru as x } from "./ru.js";
23
23
  import { sk as S } from "./sk.js";
24
24
  import { sl as C } from "./sl.js";
25
25
  import { sv as w } from "./sv.js";
26
- //#region src/index.ts
27
- var T = {
26
+ //#region src/merge.ts
27
+ var T = (e, t) => e && Object.hasOwn(e, t) ? e[t] : void 0, E = (e) => Object.fromEntries(Object.entries(e && typeof e == "object" && !Array.isArray(e) ? e : {}).filter(([, e]) => e != null)), D = (e, t) => {
28
+ let n = /* @__PURE__ */ new Set([...Object.keys(E(e)), ...Object.keys(E(t))]);
29
+ return Object.fromEntries([...n].map((n) => [n, {
30
+ ...E(T(e, n)),
31
+ ...E(T(t, n))
32
+ }]));
33
+ }, O = (e, t) => ({
34
+ banner: {
35
+ ...E(e?.banner),
36
+ ...E(t.banner)
37
+ },
38
+ preferences: D(e?.preferences, t.preferences)
39
+ }), k = (e) => Object.keys(E(e.banner)).length > 0 || Object.values(E(e.preferences)).some((e) => Object.keys(E(e)).length > 0), A = (e) => e.trim().toLowerCase(), j = (e) => e.replace(/-.*/s, ""), M = (e, t) => {
40
+ let n = /* @__PURE__ */ new Map();
41
+ for (let [t, r] of Object.entries(e ?? {})) r && n.set(A(t), {
42
+ key: t,
43
+ base: r
44
+ });
45
+ for (let [e, r] of Object.entries(t ?? {})) {
46
+ if (!r) continue;
47
+ let t = A(e), i = n.get(t);
48
+ i ? i.copy = r : n.set(t, {
49
+ key: e,
50
+ base: n.get(j(t))?.base,
51
+ copy: r
52
+ });
53
+ }
54
+ return Object.fromEntries([...n.values()].map(({ key: e, base: t, copy: n }) => [e, n && (!t || k(n)) ? O(t, n) : t]));
55
+ }, N = {
28
56
  bg: e,
29
57
  cs: t,
30
58
  da: n,
@@ -52,4 +80,4 @@ var T = {
52
80
  sv: w
53
81
  };
54
82
  //#endregion
55
- export { e as bg, t as cs, n as da, r as de, T as default, i as el, a as en, o as es, s as et, c as fi, l as fr, u as ga, d as hr, f as hu, p as it, m as lt, h as lv, g as mt, _ as nl, v as pl, y as pt, b as ro, x as ru, S as sk, C as sl, w as sv };
83
+ export { e as bg, t as cs, n as da, r as de, N as default, i as el, a as en, o as es, s as et, c as fi, l as fr, u as ga, d as hr, f as hu, p as it, m as lt, h as lv, M as mergeWithDefaultLocales, g as mt, _ as nl, v as pl, y as pt, b as ro, x as ru, S as sk, C as sl, w as sv };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@peax/consent-banner-locales",
3
3
  "description": "Optional framework-independent translation dictionaries for Peax Consent banners.",
4
- "version": "0.7.0",
4
+ "version": "0.8.0",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/peaxco/consent",
7
7
  "repository": {
@@ -164,7 +164,7 @@
164
164
  },
165
165
  "devDependencies": {
166
166
  "@microsoft/api-extractor": "^7.59.3",
167
- "@peax/consent-banner-shared": "^0.7.0",
167
+ "@peax/consent-banner-shared": "^0.8.0",
168
168
  "@types/node": "^26.6.4",
169
169
  "typescript": "5.9.3",
170
170
  "vite": "^8.3.2",