@pocket-trash/localizations 0.0.3 → 0.0.8

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pocket Trash
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # @pocket-trash/localizations
2
2
 
3
- Shared localization utilities and message catalogs for Pocket Trash.
3
+ [![npm version](https://img.shields.io/npm/v/@pocket-trash/localizations.svg)](https://www.npmjs.com/package/@pocket-trash/localizations)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./package.json)
5
+ [![CI](https://github.com/Pocket-Trash/pocket-trash-localizations/actions/workflows/ci.yml/badge.svg)](https://github.com/Pocket-Trash/pocket-trash-localizations/actions/workflows/ci.yml)
6
+ [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](docs/contributing.md)
7
+
8
+ Shared localization utilities and translation catalogs for Pocket Trash.
9
+
10
+ ## Documentation
11
+
12
+ | File | Description |
13
+ | --- | --- |
14
+ | [contributing.md](docs/contributing.md) | Explains how to edit translations, run checks, commit changes, and open a pull request. |
15
+ | [publishing.md](docs/publishing.md) | Explains how changesets become package versions and npm releases. |
4
16
 
5
17
  ## Install
6
18
 
@@ -8,20 +20,234 @@ Shared localization utilities and message catalogs for Pocket Trash.
8
20
  npm install @pocket-trash/localizations
9
21
  ```
10
22
 
11
- ## Use
23
+ ## Quick Start
12
24
 
13
25
  ```ts
14
- import { formatMessage, resolveLocale } from '@pocket-trash/localizations';
26
+ import { formatTranslation, resolveLocale } from '@pocket-trash/localizations';
15
27
 
16
28
  const locale = resolveLocale('es-MX');
17
- const label = formatMessage('action.save', {}, locale);
29
+ const label = formatTranslation('action.save', {}, locale);
30
+ ```
31
+
32
+ `label` is `"Guardar"`.
33
+
34
+ ## Formatting Translations
35
+
36
+ Use `formatTranslation(key, values, locale)` when you need one translated
37
+ string.
38
+
39
+ ```ts
40
+ import { formatTranslation } from '@pocket-trash/localizations';
41
+
42
+ formatTranslation('action.save');
43
+ formatTranslation('locale.current', { locale: 'es-MX' }, 'es-MX');
44
+ ```
45
+
46
+ The second argument is the placeholder values object. Pass `{}` when the
47
+ translation does not need any values.
48
+
49
+ ```ts
50
+ formatTranslation('action.save', {}, 'es-MX');
51
+ ```
52
+
53
+ For translations with placeholders, pass the placeholder names and values:
54
+
55
+ ```ts
56
+ formatTranslation('locale.current', { locale: 'es-MX' }, 'es-MX');
57
+ ```
58
+
59
+ That fills `{locale}` in this translation:
60
+
61
+ ```ts
62
+ export const esMX = {
63
+ locale: {
64
+ current: 'Idioma actual: {locale}',
65
+ },
66
+ };
67
+ ```
68
+
69
+ If a placeholder value is missing, the placeholder stays in the string. That
70
+ makes missing data visible instead of silently deleting text.
71
+
72
+ ## Resolving Locales
73
+
74
+ Use `resolveLocale()` before formatting when locale input comes from a browser,
75
+ request header, user setting, or other string source.
76
+
77
+ ```ts
78
+ import { resolveLocale } from '@pocket-trash/localizations';
79
+
80
+ const locale = resolveLocale('fr-CA', 'es-MX');
81
+ // locale is "es-MX"
82
+ ```
83
+
84
+ `resolveLocale()` returns a TypeScript-safe `SupportedLocale`.
85
+
86
+ ```ts
87
+ import type { SupportedLocale } from '@pocket-trash/localizations';
88
+
89
+ const locale: SupportedLocale = resolveLocale('nl-BE');
90
+ // locale is "en-US" because nl-BE is not supported
91
+ ```
92
+
93
+ Passing an unsupported locale to `resolveLocale()` does not throw. It falls
94
+ back to `en-US`.
95
+
96
+ ```ts
97
+ const locale = resolveLocale('nl-BE');
98
+ formatTranslation('action.save', {}, locale);
99
+ // "Save"
100
+ ```
101
+
102
+ The `locale` parameter on `formatTranslation()` is TypeScript-safe. Passing an
103
+ unsupported literal like `'nl-BE'` directly is a type error.
104
+
105
+ ```ts
106
+ formatTranslation('action.save', {}, 'nl-BE');
107
+ // TypeScript error: "nl-BE" is not assignable to SupportedLocale
108
+ ```
109
+
110
+ Use `resolveLocale()` for raw external input. It returns a supported locale that
111
+ can be passed to `formatTranslation()`.
112
+
113
+ ## TypeScript Safety
114
+
115
+ Translation keys are type-safe when you use a known key.
116
+
117
+ ```ts
118
+ import type { TranslationKey } from '@pocket-trash/localizations';
119
+
120
+ const key: TranslationKey = 'action.save';
18
121
  ```
19
122
 
123
+ The current translation keys are:
124
+
125
+ ```ts
126
+ type TranslationKey =
127
+ | 'app.name'
128
+ | 'action.cancel'
129
+ | 'action.save'
130
+ | 'error.generic'
131
+ | 'locale.current';
132
+ ```
133
+
134
+ `formatTranslation()` only accepts known `TranslationKey` values. Unknown keys
135
+ are TypeScript errors.
136
+
137
+ ```ts
138
+ formatTranslation('unknown.key');
139
+ // TypeScript error: "unknown.key" is not assignable to TranslationKey
140
+ ```
141
+
142
+ ## Getting a Whole Catalog
143
+
144
+ Use `getTranslations()` when you need all translations for a locale.
145
+
146
+ ```ts
147
+ import { getTranslations } from '@pocket-trash/localizations';
148
+
149
+ const translations = getTranslations('es-MX');
150
+ const save = translations['action.save'];
151
+ ```
152
+
153
+ Returned catalogs are complete. If a non-English locale omits a translation,
154
+ or has a blank string, that key falls back to the `en-US` value.
155
+
156
+ ## Clerk Localizations
157
+
158
+ Use `getClerkLocalization()` when configuring Clerk UI components.
159
+
160
+ ```ts
161
+ import { getClerkLocalization, resolveLocale } from '@pocket-trash/localizations';
162
+
163
+ const locale = resolveLocale('es-MX');
164
+ const localization = getClerkLocalization(locale);
165
+ ```
166
+
167
+ English returns `undefined`, so Clerk uses its default English strings. Spanish
168
+ returns Clerk's `esMX` localization.
169
+
170
+ ## Adding Translations
171
+
172
+ Source locale files use nested objects:
173
+
174
+ ```ts
175
+ export const enUS = {
176
+ app: {
177
+ name: 'Pocket Trash',
178
+ },
179
+ action: {
180
+ cancel: 'Cancel',
181
+ save: 'Save',
182
+ },
183
+ };
184
+ ```
185
+
186
+ Callers still use dot keys:
187
+
188
+ ```ts
189
+ formatTranslation('action.save');
190
+ ```
191
+
192
+ Add new translation keys in two places:
193
+
194
+ 1. Add the nested field to `CompleteLocalizationResource` in `src/types/localization.ts`.
195
+ 2. Add the required English value to `src/localizations/en-US.ts`.
196
+ 3. Run `pnpm localization:sync`.
197
+ 4. Fill in translated values when available.
198
+ 5. Run `pnpm test`.
199
+
200
+ Non-English locale files can omit new keys until translations are ready. Missing
201
+ or blank keys fall back to `en-US` at runtime.
202
+
203
+ ## Scaffolding a Locale
204
+
205
+ Create and wire a new locale:
206
+
207
+ ```sh
208
+ pnpm localization:create nl-BE
209
+ ```
210
+
211
+ That command:
212
+
213
+ 1. Creates `src/localizations/nl-BE.ts`.
214
+ 2. Copies the nested `en-US` shape with every value set to `''`.
215
+ 3. Adds the locale to `SupportedLocale`, `SUPPORTED_LOCALES`, `localizations`,
216
+ and `translations`.
217
+ 4. Adds a Clerk localization mapping if `@clerk/localizations` exports one.
218
+
219
+ Sync missing keys into existing non-English locales:
220
+
221
+ ```sh
222
+ pnpm localization:sync
223
+ ```
224
+
225
+ `en-US` is the source of truth. Sync adds missing nested keys with blank values
226
+ and preserves existing translations.
227
+
20
228
  ## Develop
229
+ Common commands:
230
+
231
+ | Command | Description |
232
+ | --- | --- |
233
+ | `pnpm install` | Install dependencies. |
234
+ | `pnpm build` | Build the package with TypeScript. |
235
+ | `pnpm format` | Format files with Biome. |
236
+ | `pnpm lint` | Check formatting and lint rules with Biome. |
237
+ | `pnpm check` | Run Biome checks and apply safe fixes. |
238
+ | `pnpm test` | Build the package and run the test suite. |
239
+ | `pnpm localization:create nl-BE` | Create and wire a new locale from the `en-US` source shape. |
240
+ | `pnpm localization:sync` | Add missing source keys to existing non-English locale files. |
241
+ | `pnpm changeset` | Create a release changeset for a pull request. |
242
+ | `pnpm release` | Publish pending package changes from the release workflow. |
21
243
 
22
244
  ```sh
23
245
  pnpm install
246
+ pnpm format
247
+ pnpm lint
24
248
  pnpm test
249
+ pnpm localization:create nl-BE
250
+ pnpm localization:sync
25
251
  ```
26
252
 
27
253
  Publishing notes live in [docs/publishing.md](docs/publishing.md).
package/dist/index.d.ts CHANGED
@@ -1,31 +1,62 @@
1
- import { esMX } from '@clerk/localizations';
2
- export type SupportedLocale = 'en' | 'es-MX';
3
- export type MessageKey = 'app.name' | 'action.cancel' | 'action.save' | 'error.generic' | 'locale.current';
4
- export type Messages = Readonly<Record<MessageKey, string>>;
1
+ import { esMX as clerkEsMX } from "@clerk/localizations";
2
+ import type { TranslationKey, Translations } from "./types/localization.js";
3
+ export { enUS } from "./localizations/en-US.js";
4
+ export { esMX } from "./localizations/es-MX.js";
5
+ export type { CompleteLocalizationResource, LocalizationResource, TranslationKey, Translations, } from "./types/localization.js";
6
+ export type SupportedLocale = "en-US" | "es-MX";
7
+ export type MessageKey = TranslationKey;
8
+ export type Messages = Translations;
5
9
  export type LocalePreference = string | readonly string[] | null | undefined;
6
- export type ClerkLocalizationResource = typeof esMX;
7
- export declare const DEFAULT_LOCALE = "en";
8
- export declare const SUPPORTED_LOCALES: readonly ["en", "es-MX"];
10
+ export type ClerkLocalizationResource = typeof clerkEsMX;
11
+ export declare const DEFAULT_LOCALE = "en-US";
12
+ export declare const SUPPORTED_LOCALES: readonly ["en-US", "es-MX"];
13
+ export declare const localizations: Readonly<{
14
+ "en-US": {
15
+ app: {
16
+ name: string;
17
+ };
18
+ action: {
19
+ cancel: string;
20
+ save: string;
21
+ };
22
+ error: {
23
+ generic: string;
24
+ };
25
+ locale: {
26
+ current: string;
27
+ };
28
+ };
29
+ "es-MX": {
30
+ app: {
31
+ name: string;
32
+ };
33
+ action: {
34
+ cancel: string;
35
+ save: string;
36
+ };
37
+ error: {
38
+ generic: string;
39
+ };
40
+ locale: {
41
+ current: string;
42
+ };
43
+ };
44
+ }>;
45
+ export declare const translationKeys: readonly TranslationKey[];
46
+ export declare const translations: Readonly<{
47
+ "en-US": Readonly<Readonly<Record<TranslationKey, string>>>;
48
+ "es-MX": Readonly<Readonly<Record<TranslationKey, string>>>;
49
+ }>;
9
50
  export declare const messages: Readonly<{
10
- en: Readonly<{
11
- 'app.name': "Pocket Trash";
12
- 'action.cancel': "Cancel";
13
- 'action.save': "Save";
14
- 'error.generic': "Something went wrong.";
15
- 'locale.current': "Current language: {locale}";
16
- }>;
17
- 'es-MX': Readonly<{
18
- 'app.name': "Pocket Trash";
19
- 'action.cancel': "Cancelar";
20
- 'action.save': "Guardar";
21
- 'error.generic': "Algo salio mal.";
22
- 'locale.current': "Idioma actual: {locale}";
23
- }>;
51
+ "en-US": Readonly<Readonly<Record<TranslationKey, string>>>;
52
+ "es-MX": Readonly<Readonly<Record<TranslationKey, string>>>;
24
53
  }>;
25
- export declare const messageKeys: readonly MessageKey[];
54
+ export declare const messageKeys: readonly TranslationKey[];
26
55
  export declare const clerkLocalizations: Readonly<Record<SupportedLocale, ClerkLocalizationResource | undefined>>;
27
56
  export declare function resolveLocale(...preferences: readonly LocalePreference[]): SupportedLocale;
28
- export declare function getMessages(locale?: string | null): Messages;
29
- export declare function getClerkLocalization(locale?: string | null): ClerkLocalizationResource | undefined;
30
- export declare function formatMessage(key: MessageKey | string, values?: Readonly<Record<string, unknown>>, locale?: string | null): string;
57
+ export declare function getTranslations(locale?: SupportedLocale | null): Translations;
58
+ export declare const getMessages: typeof getTranslations;
59
+ export declare function getClerkLocalization(locale?: SupportedLocale | null): ClerkLocalizationResource | undefined;
60
+ export declare function formatTranslation(key: TranslationKey, values?: Readonly<Record<string, unknown>>, locale?: SupportedLocale | null): string;
61
+ export declare const formatMessage: typeof formatTranslation;
31
62
  export declare function assertCompleteCatalogs(): true;
package/dist/index.js CHANGED
@@ -1,34 +1,31 @@
1
- import { esMX } from '@clerk/localizations';
2
- export const DEFAULT_LOCALE = 'en';
3
- export const SUPPORTED_LOCALES = Object.freeze(['en', 'es-MX']);
1
+ import { esMX as clerkEsMX } from "@clerk/localizations";
2
+ import { enUS } from "./localizations/en-US.js";
3
+ import { esMX } from "./localizations/es-MX.js";
4
+ export { enUS } from "./localizations/en-US.js";
5
+ export { esMX } from "./localizations/es-MX.js";
6
+ export const DEFAULT_LOCALE = "en-US";
7
+ export const SUPPORTED_LOCALES = Object.freeze(["en-US", "es-MX"]);
4
8
  const localeAliases = new Map([
5
- ['en', 'en'],
6
- ['en-us', 'en'],
7
- ['en-ca', 'en'],
8
- ['es', 'es-MX'],
9
- ['es-mx', 'es-MX'],
10
- ['es-us', 'es-MX'],
9
+ ["es", "es-MX"],
10
+ ["es-mx", "es-MX"],
11
+ ["es-us", "es-MX"],
12
+ ["en-us", "en-US"],
11
13
  ]);
12
- export const messages = Object.freeze({
13
- en: Object.freeze({
14
- 'app.name': 'Pocket Trash',
15
- 'action.cancel': 'Cancel',
16
- 'action.save': 'Save',
17
- 'error.generic': 'Something went wrong.',
18
- 'locale.current': 'Current language: {locale}',
19
- }),
20
- 'es-MX': Object.freeze({
21
- 'app.name': 'Pocket Trash',
22
- 'action.cancel': 'Cancelar',
23
- 'action.save': 'Guardar',
24
- 'error.generic': 'Algo salio mal.',
25
- 'locale.current': 'Idioma actual: {locale}',
26
- }),
14
+ export const localizations = Object.freeze({
15
+ "en-US": enUS,
16
+ "es-MX": esMX,
27
17
  });
28
- export const messageKeys = Object.freeze(Object.keys(messages[DEFAULT_LOCALE]));
18
+ const defaultTranslations = flattenLocalization(enUS);
19
+ export const translationKeys = Object.freeze(Object.keys(defaultTranslations));
20
+ export const translations = Object.freeze({
21
+ "en-US": Object.freeze(defaultTranslations),
22
+ "es-MX": Object.freeze(mergeTranslations(defaultTranslations, flattenLocalization(esMX))),
23
+ });
24
+ export const messages = translations;
25
+ export const messageKeys = translationKeys;
29
26
  export const clerkLocalizations = Object.freeze({
30
- en: undefined,
31
- 'es-MX': esMX,
27
+ "en-US": undefined,
28
+ "es-MX": clerkEsMX,
32
29
  });
33
30
  export function resolveLocale(...preferences) {
34
31
  for (const preference of preferences.flat()) {
@@ -40,39 +37,47 @@ export function resolveLocale(...preferences) {
40
37
  }
41
38
  return DEFAULT_LOCALE;
42
39
  }
43
- export function getMessages(locale) {
44
- return messages[resolveLocale(locale)];
40
+ export function getTranslations(locale) {
41
+ return translations[resolveLocale(locale)];
45
42
  }
43
+ export const getMessages = getTranslations;
46
44
  export function getClerkLocalization(locale) {
47
45
  return clerkLocalizations[resolveLocale(locale)];
48
46
  }
49
- export function formatMessage(key, values = {}, locale = DEFAULT_LOCALE) {
50
- const template = getMessages(locale)[key] ?? messages[DEFAULT_LOCALE][key];
47
+ export function formatTranslation(key, values = {}, locale = DEFAULT_LOCALE) {
48
+ const template = getTranslations(locale)[key] ?? translations[DEFAULT_LOCALE][key];
51
49
  if (!template)
52
50
  return key;
53
51
  return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, name) => values[name] == null ? `{${name}}` : String(values[name]));
54
52
  }
53
+ export const formatMessage = formatTranslation;
55
54
  export function assertCompleteCatalogs() {
56
- const expected = new Set(messageKeys);
57
- for (const [locale, catalog] of Object.entries(messages)) {
58
- const keys = Object.keys(catalog);
59
- const missing = messageKeys.filter((key) => !(key in catalog));
55
+ const expected = new Set(translationKeys);
56
+ for (const [locale, catalog] of Object.entries(localizations)) {
57
+ const flatCatalog = flattenLocalization(catalog);
58
+ const keys = Object.keys(flatCatalog);
59
+ const missing = locale === DEFAULT_LOCALE
60
+ ? translationKeys.filter((key) => !(key in flatCatalog))
61
+ : [];
62
+ const empty = locale === DEFAULT_LOCALE
63
+ ? translationKeys.filter((key) => flatCatalog[key] === "")
64
+ : [];
60
65
  const extra = keys.filter((key) => !expected.has(key));
61
- if (missing.length || extra.length) {
62
- throw new Error(`${locale} catalog mismatch: missing ${missing.join(', ') || 'none'}; extra ${extra.join(', ') || 'none'}`);
66
+ if (missing.length || empty.length || extra.length) {
67
+ throw new Error(`${locale} catalog mismatch: missing ${missing.join(", ") || "none"}; empty ${empty.join(", ") || "none"}; extra ${extra.join(", ") || "none"}`);
63
68
  }
64
69
  }
65
70
  return true;
66
71
  }
67
72
  function parseLocalePreference(preference) {
68
- if (typeof preference !== 'string' || !preference.trim())
73
+ if (typeof preference !== "string" || !preference.trim())
69
74
  return [];
70
- if (!preference.includes(',') && !preference.includes(';'))
75
+ if (!preference.includes(",") && !preference.includes(";"))
71
76
  return [preference];
72
77
  return preference
73
- .split(',')
78
+ .split(",")
74
79
  .map((part, index) => {
75
- const [tag, ...params] = part.trim().split(';');
80
+ const [tag, ...params] = part.trim().split(";");
76
81
  const q = params
77
82
  .map((param) => param.trim().match(/^q=([0-9.]+)$/i)?.[1])
78
83
  .find(Boolean);
@@ -83,9 +88,34 @@ function parseLocalePreference(preference) {
83
88
  .map(({ tag }) => tag);
84
89
  }
85
90
  function matchLocale(tag) {
86
- const normalized = tag.trim().replace('_', '-').toLowerCase();
87
- if (normalized === '*')
91
+ const normalized = tag.trim().replace("_", "-").toLowerCase();
92
+ if (normalized === "*")
88
93
  return DEFAULT_LOCALE;
89
- return localeAliases.get(normalized) ?? localeAliases.get(normalized.split('-')[0] ?? '') ?? null;
94
+ return localeAliases.get(normalized) ?? null;
95
+ }
96
+ function flattenLocalization(resource) {
97
+ const entries = {};
98
+ for (const [key, value] of flattenEntries(resource)) {
99
+ entries[key] = value;
100
+ }
101
+ return entries;
102
+ }
103
+ function flattenEntries(resource, prefix = "") {
104
+ return Object.entries(resource).flatMap(([key, value]) => {
105
+ const path = prefix ? `${prefix}.${key}` : key;
106
+ if (typeof value === "string")
107
+ return [[path, value]];
108
+ if (value && typeof value === "object")
109
+ return flattenEntries(value, path);
110
+ return [];
111
+ });
112
+ }
113
+ function mergeTranslations(defaults, overrides) {
114
+ return Object.fromEntries(translationKeys.map((key) => [
115
+ key,
116
+ overrides[key] === "" || overrides[key] == null
117
+ ? defaults[key]
118
+ : overrides[key],
119
+ ]));
90
120
  }
91
121
  assertCompleteCatalogs();
@@ -0,0 +1,24 @@
1
+ /**
2
+ * English (United States) translations for Pocket Trash.
3
+ *
4
+ * This catalog is checked against `CompleteLocalizationResource` from
5
+ * `src/types/localization.ts`. When adding a translation key, update that type
6
+ * first, then add the required English string here. Other locale catalogs can
7
+ * omit most new keys until a translation is available; omitted keys fall back to
8
+ * this `en-US` catalog at runtime.
9
+ */
10
+ export declare const enUS: {
11
+ app: {
12
+ name: string;
13
+ };
14
+ action: {
15
+ cancel: string;
16
+ save: string;
17
+ };
18
+ error: {
19
+ generic: string;
20
+ };
21
+ locale: {
22
+ current: string;
23
+ };
24
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * English (United States) translations for Pocket Trash.
3
+ *
4
+ * This catalog is checked against `CompleteLocalizationResource` from
5
+ * `src/types/localization.ts`. When adding a translation key, update that type
6
+ * first, then add the required English string here. Other locale catalogs can
7
+ * omit most new keys until a translation is available; omitted keys fall back to
8
+ * this `en-US` catalog at runtime.
9
+ */
10
+ export const enUS = {
11
+ app: {
12
+ name: "Pocket Trash",
13
+ },
14
+ action: {
15
+ cancel: "Cancel",
16
+ save: "Save",
17
+ },
18
+ error: {
19
+ generic: "Something went wrong.",
20
+ },
21
+ locale: {
22
+ current: "Current language: {locale}",
23
+ },
24
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Spanish (Mexico) translations for Pocket Trash.
3
+ *
4
+ * This catalog is checked against `LocalizationResource` from
5
+ * `src/types/localization.ts`. When adding a translation key, update that type
6
+ * first, add the required English string in `src/localizations/en-US.ts`, then
7
+ * add this locale's translation when available. Most new keys should remain
8
+ * optional in the type so this catalog can omit untranslated strings and fall
9
+ * back to `en-US` at runtime.
10
+ */
11
+ export declare const esMX: {
12
+ app: {
13
+ name: string;
14
+ };
15
+ action: {
16
+ cancel: string;
17
+ save: string;
18
+ };
19
+ error: {
20
+ generic: string;
21
+ };
22
+ locale: {
23
+ current: string;
24
+ };
25
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Spanish (Mexico) translations for Pocket Trash.
3
+ *
4
+ * This catalog is checked against `LocalizationResource` from
5
+ * `src/types/localization.ts`. When adding a translation key, update that type
6
+ * first, add the required English string in `src/localizations/en-US.ts`, then
7
+ * add this locale's translation when available. Most new keys should remain
8
+ * optional in the type so this catalog can omit untranslated strings and fall
9
+ * back to `en-US` at runtime.
10
+ */
11
+ export const esMX = {
12
+ app: {
13
+ name: "Pocket Trash",
14
+ },
15
+ action: {
16
+ cancel: "Cancelar",
17
+ save: "Guardar",
18
+ },
19
+ error: {
20
+ generic: "Algo salio mal.",
21
+ },
22
+ locale: {
23
+ current: "Idioma actual: {locale}",
24
+ },
25
+ };
@@ -0,0 +1,25 @@
1
+ type DotPaths<T> = {
2
+ [K in keyof T & string]: T[K] extends string ? K : T[K] extends object ? `${K}.${DotPaths<T[K]>}` : never;
3
+ }[keyof T & string];
4
+ type DeepPartial<T> = {
5
+ readonly [K in keyof T]?: T[K] extends string ? string : DeepPartial<T[K]>;
6
+ };
7
+ export type CompleteLocalizationResource = Readonly<{
8
+ app: {
9
+ name: string;
10
+ };
11
+ action: {
12
+ cancel: string;
13
+ save: string;
14
+ };
15
+ error: {
16
+ generic: string;
17
+ };
18
+ locale: {
19
+ current: string;
20
+ };
21
+ }>;
22
+ export type TranslationKey = DotPaths<CompleteLocalizationResource>;
23
+ export type Translations = Readonly<Record<TranslationKey, string>>;
24
+ export type LocalizationResource = DeepPartial<CompleteLocalizationResource>;
25
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pocket-trash/localizations",
3
- "version": "0.0.3",
3
+ "version": "0.0.8",
4
4
  "description": "Shared localization utilities and message catalogs for Pocket Trash.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,23 +31,32 @@
31
31
  "publishConfig": {
32
32
  "access": "public"
33
33
  },
34
- "scripts": {
35
- "build": "tsc -p tsconfig.json",
36
- "changeset": "changeset",
37
- "changeset:version": "changeset version",
38
- "release": "changeset publish",
39
- "test": "pnpm run build && node --test"
40
- },
41
34
  "engines": {
42
35
  "node": ">=20"
43
36
  },
44
37
  "devDependencies": {
38
+ "@biomejs/biome": "^2.5.8",
45
39
  "@changesets/cli": "^2.31.1",
40
+ "@commitlint/cli": "^21.2.2",
41
+ "@commitlint/config-conventional": "^21.2.2",
42
+ "husky": "^9.1.7",
46
43
  "typescript": "^5.9.2"
47
44
  },
48
45
  "dependencies": {
49
46
  "@clerk/localizations": "^4.14.2",
50
47
  "lts": "^2.0.0"
51
48
  },
52
- "packageManager": "pnpm@11.10.0"
53
- }
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.json",
51
+ "check": "biome check --write .",
52
+ "changeset": "changeset",
53
+ "changeset:version": "changeset version && node scripts/stamp-changelog-release-date.mjs",
54
+ "dev": "tsc -p tsconfig.json --watch",
55
+ "format": "biome format --write .",
56
+ "lint": "biome ci .",
57
+ "localization:create": "node scripts/localizations.mjs create",
58
+ "localization:sync": "node scripts/localizations.mjs sync",
59
+ "release": "pnpm run changeset:version && pnpm install --lockfile-only && changeset publish",
60
+ "test": "pnpm run build && node --test"
61
+ }
62
+ }