@edgekits/astro-i18n 0.1.7 → 0.2.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/CHANGELOG.md +36 -0
- package/README.md +98 -27
- package/dist/integration/index.d.ts +25 -4
- package/dist/integration/index.js +35 -9
- package/dist/integration/vite-plugin.d.ts +1 -1
- package/dist/types.d.ts +7 -55
- package/dist/types.js +1 -44
- package/package.json +7 -17
- package/src/components/LanguageSwitcher.astro +89 -91
- package/src/config.entry.ts +5 -36
- package/src/constants.ts +5 -3
- package/src/cookie-storage.ts +19 -25
- package/src/fetcher.ts +3 -304
- package/src/format.ts +3 -107
- package/src/instance.ts +44 -0
- package/src/integration/index.ts +38 -9
- package/src/integration/vite-plugin.ts +1 -1
- package/src/middleware/i18n.ts +232 -114
- package/src/resolve-locale.ts +7 -39
- package/src/runtime.ts +2 -2
- package/src/schema.ts +3 -11
- package/src/translations-keys.ts +7 -38
- package/src/types.ts +16 -116
- package/dist/cli/generate.d.ts +0 -50
- package/dist/cli/generate.js +0 -160
- package/dist/cli/hashes.d.ts +0 -19
- package/dist/cli/hashes.js +0 -42
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -368
- package/dist/cli/load-config.d.ts +0 -7
- package/dist/cli/load-config.js +0 -19
- package/dist/cli/purge.d.ts +0 -29
- package/dist/cli/purge.js +0 -94
- package/dist/cli/wrangler.d.ts +0 -10
- package/dist/cli/wrangler.js +0 -71
- package/dist/key-format.d.ts +0 -8
- package/dist/key-format.js +0 -15
- package/src/accept-language.ts +0 -56
- package/src/assemble.ts +0 -29
- package/src/cli/generate.ts +0 -205
- package/src/cli/hashes.ts +0 -66
- package/src/cli/index.ts +0 -467
- package/src/cli/load-config.ts +0 -26
- package/src/cli/purge.ts +0 -122
- package/src/cli/wrangler.ts +0 -96
- package/src/deep-merge.ts +0 -38
- package/src/key-format.ts +0 -17
- package/src/locale-fallback.ts +0 -58
- package/src/markdown-path.ts +0 -31
- package/src/parse-boolean.ts +0 -20
package/src/types.ts
CHANGED
|
@@ -1,123 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The core's public types and config helpers, re-exported for this package's entries
|
|
3
|
+
* (root, `./config`, `./runtime`), plus what only Astro needs: the `App.Locals` the
|
|
4
|
+
* middleware fills. `I18nLocales` is the core's interface: augmenting it through either
|
|
5
|
+
* entry (the integration's injected `locales.d.ts`) merges into that one declaration.
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
keyPrefix: string
|
|
19
|
-
missingTranslationBanner: boolean
|
|
20
|
-
routing: 'prefix'
|
|
21
|
-
// Where the CLI writes `fallbacks.generated.ts` and `runtime-constants.ts`,
|
|
22
|
-
// relative to the project root.
|
|
23
|
-
generatedDir?: string
|
|
24
|
-
// Where the CLI reads `<locale>/<namespace>.json`, relative to the project root.
|
|
25
|
-
localesDir?: string
|
|
26
|
-
// Geo-IP step of the middleware's locale fallback: ISO 3166-1 alpha-2 country code
|
|
27
|
-
// (uppercase, as `request.cf.country` sends it) → one of `languages`. Without it the
|
|
28
|
-
// step is skipped; the package ships no table of its own.
|
|
29
|
-
countryToLocale?: Record<string, string>
|
|
30
|
-
// The locale of the project's `.md` endpoints: a `.md` path is redirected once to
|
|
31
|
-
// `/<markdownLocale>/<rest>` (a leading locale segment replaced, otherwise kept). Without
|
|
32
|
-
// it, `.md` paths pass through the middleware untouched.
|
|
33
|
-
markdownLocale?: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const DEFAULT_GENERATED_DIR = 'src/i18n'
|
|
37
|
-
export const DEFAULT_LOCALES_DIR = 'locales'
|
|
38
|
-
|
|
39
|
-
/** The locale keys of a config's `languages`: a literal union for a literal config. */
|
|
40
|
-
export type LocaleOf<C extends I18nConfig> = Extract<keyof C['languages'], string>
|
|
41
|
-
|
|
42
|
-
const COUNTRY_CODE = /^[A-Z]{2}$/
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Validates and returns the config unchanged. Throws at config time on an empty
|
|
46
|
-
* `languages`, a `defaultLocale` or `markdownLocale` that is not one of its keys, an
|
|
47
|
-
* unknown `routing`, or a `countryToLocale` entry whose key is not an uppercase country
|
|
48
|
-
* code or whose value is not one of `languages` (also a type error for a literal config).
|
|
49
|
-
*/
|
|
50
|
-
export function defineI18nConfig<const C extends I18nConfig>(
|
|
51
|
-
config: C & { countryToLocale?: Record<string, LocaleOf<C>>; markdownLocale?: LocaleOf<C> },
|
|
52
|
-
): C {
|
|
53
|
-
const locales = Object.keys(config.languages ?? {})
|
|
54
|
-
|
|
55
|
-
if (locales.length === 0) {
|
|
56
|
-
throw new Error('[@edgekits/astro-i18n] `languages` must define at least one locale.')
|
|
57
|
-
}
|
|
58
|
-
// Lowercase everywhere (config, `locales/` folders, URLs); `toIetfTag` relies on it.
|
|
59
|
-
for (const locale of locales) {
|
|
60
|
-
if (locale !== locale.toLowerCase()) {
|
|
61
|
-
throw new Error(
|
|
62
|
-
`[@edgekits/astro-i18n] \`languages\` key "${locale}" is not lowercase (use "${locale.toLowerCase()}").`,
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (!locales.includes(config.defaultLocale)) {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`[@edgekits/astro-i18n] \`defaultLocale\` "${config.defaultLocale}" is not one of \`languages\`: ${locales.join(', ')}.`,
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
if (config.markdownLocale !== undefined && !locales.includes(config.markdownLocale)) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`[@edgekits/astro-i18n] \`markdownLocale\` "${config.markdownLocale}" is not one of \`languages\`: ${locales.join(', ')}.`,
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
if (config.routing !== 'prefix') {
|
|
77
|
-
throw new Error(
|
|
78
|
-
`[@edgekits/astro-i18n] \`routing\` "${String(config.routing)}" is not supported; the only routing is 'prefix'.`,
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
for (const [country, locale] of Object.entries(config.countryToLocale ?? {})) {
|
|
82
|
-
if (!COUNTRY_CODE.test(country)) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
`[@edgekits/astro-i18n] \`countryToLocale\` key "${country}" is not an uppercase ISO 3166-1 alpha-2 country code.`,
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
if (!locales.includes(locale)) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
`[@edgekits/astro-i18n] \`countryToLocale\` maps "${country}" to "${locale}", which is not one of \`languages\`: ${locales.join(', ')}.`,
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return config
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* The project's locales, as keys. Empty here; the integration augments it from the
|
|
99
|
-
* config's `languages` (`injectTypes` at `astro:config:done`).
|
|
100
|
-
*/
|
|
101
|
-
export interface I18nLocales {}
|
|
102
|
-
|
|
103
|
-
type LocaleKey = Extract<keyof I18nLocales, string>
|
|
104
|
-
|
|
105
|
-
// Falls back to `string` while `I18nLocales` is empty, like `Namespace` over `I18n.Schema`.
|
|
106
|
-
export type Locale = [LocaleKey] extends [never] ? string : LocaleKey
|
|
107
|
-
|
|
108
|
-
/** The Worker bindings the engine reads from `cloudflare:workers`. */
|
|
109
|
-
export interface I18nBindings {
|
|
110
|
-
TRANSLATIONS: KVNamespace
|
|
111
|
-
I18N_CACHE?: string
|
|
112
|
-
DEBUG_I18N?: string
|
|
113
|
-
}
|
|
7
|
+
import type { Locale } from '@edgekits/i18n-core'
|
|
8
|
+
|
|
9
|
+
export { DEFAULT_GENERATED_DIR, defineI18nConfig, localesOf } from '@edgekits/i18n-core'
|
|
10
|
+
export type {
|
|
11
|
+
I18nBindings,
|
|
12
|
+
I18nConfig,
|
|
13
|
+
I18nLanguage,
|
|
14
|
+
I18nLocales,
|
|
15
|
+
Locale,
|
|
16
|
+
LocaleOf,
|
|
17
|
+
LocalesOf,
|
|
18
|
+
} from '@edgekits/i18n-core'
|
|
114
19
|
|
|
115
20
|
declare global {
|
|
116
|
-
// Augmented by the consumer's generated `i18n.generated.d.ts`.
|
|
117
|
-
namespace I18n {
|
|
118
|
-
interface Schema {}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
21
|
// Set by the i18n middleware on every request.
|
|
122
22
|
namespace App {
|
|
123
23
|
interface Locals {
|
package/dist/cli/generate.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The CLI's code generation, as pure functions of the collected locale tree: no
|
|
3
|
-
* filesystem, so the emitted text is testable. Formats are those of the site's
|
|
4
|
-
* `scripts/bundle-translations.ts`, header aside.
|
|
5
|
-
*/
|
|
6
|
-
export type TranslationTree = Record<string, unknown>;
|
|
7
|
-
export type CollectedData = Record<string, Record<string, TranslationTree>>;
|
|
8
|
-
export type KvBulkEntry = {
|
|
9
|
-
key: string;
|
|
10
|
-
value: string;
|
|
11
|
-
};
|
|
12
|
-
export declare const NAMESPACE_NAME: RegExp;
|
|
13
|
-
/**
|
|
14
|
-
* Throws, naming the offender, when a locale folder is not one of the config's
|
|
15
|
-
* `languages` or a namespace file name does not match NAMESPACE_NAME. Run on the
|
|
16
|
-
* collected tree before anything is generated or pushed.
|
|
17
|
-
*/
|
|
18
|
-
export declare function assertValidNames(collected: CollectedData, languages: Readonly<Record<string, unknown>>): void;
|
|
19
|
-
/**
|
|
20
|
-
* A single-quoted TS string literal, escaped by JSON.stringify. For every name the
|
|
21
|
-
* validation accepts it is byte-identical to the plain `'name'` the CLI always wrote, so
|
|
22
|
-
* existing generated files do not change; anything else comes out escaped, not injected.
|
|
23
|
-
*/
|
|
24
|
-
export declare function quoteString(value: string): string;
|
|
25
|
-
/** Locales that have at least one namespace, sorted. */
|
|
26
|
-
export declare function localesWithTranslations(collected: CollectedData): string[];
|
|
27
|
-
/**
|
|
28
|
-
* The locale the schema and the namespace list are derived from: the default locale
|
|
29
|
-
* when it has translations, otherwise the first locale that does.
|
|
30
|
-
*/
|
|
31
|
-
export declare function schemaSourceLocale(collected: CollectedData, defaultLocale: string): string | undefined;
|
|
32
|
-
/** `wrangler kv bulk put` input: one entry per locale and namespace. */
|
|
33
|
-
export declare function buildBulkData(collected: CollectedData, keyPrefix: string): KvBulkEntry[];
|
|
34
|
-
/**
|
|
35
|
-
* `src/i18n.generated.d.ts`: the global `I18n.Schema`, from the schema source locale.
|
|
36
|
-
* Throws, naming the key, when an array in it mixes kinds of elements.
|
|
37
|
-
*/
|
|
38
|
-
export declare function buildTypesContent(collected: CollectedData, defaultLocale: string): string;
|
|
39
|
-
/**
|
|
40
|
-
* `runtime-constants.ts`:
|
|
41
|
-
* - NAMESPACE_KEYS -> from the schema source locale (default locale preferred)
|
|
42
|
-
* - LOCALES_WITH_TRANSLATIONS -> from all collected locales
|
|
43
|
-
* - FULLY_TRANSLATED_LOCALES -> locales that have ALL namespace keys
|
|
44
|
-
*/
|
|
45
|
-
export declare function buildRuntimeConstantsContent(collected: CollectedData, defaultLocale: string): string;
|
|
46
|
-
/**
|
|
47
|
-
* `fallbacks.generated.ts`: one `FALLBACK_<NAMESPACE>` per default-locale namespace.
|
|
48
|
-
* Null when the default locale has no translations (nothing to fall back to).
|
|
49
|
-
*/
|
|
50
|
-
export declare function buildFallbacksContent(collected: CollectedData, defaultLocale: string): string | null;
|
package/dist/cli/generate.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { formatTranslationKvKey } from "../key-format.js";
|
|
2
|
-
const HEADER = `// Auto-generated by @edgekits/astro-i18n (edgekits-i18n CLI)
|
|
3
|
-
// Do not edit manually.
|
|
4
|
-
`;
|
|
5
|
-
// A namespace becomes part of a KV key, a cache URL and the `FALLBACK_<NAMESPACE>`
|
|
6
|
-
// identifier, so its file name is restricted to what is valid in all three.
|
|
7
|
-
export const NAMESPACE_NAME = /^[a-z0-9][a-z0-9_]*$/;
|
|
8
|
-
/**
|
|
9
|
-
* Throws, naming the offender, when a locale folder is not one of the config's
|
|
10
|
-
* `languages` or a namespace file name does not match NAMESPACE_NAME. Run on the
|
|
11
|
-
* collected tree before anything is generated or pushed.
|
|
12
|
-
*/
|
|
13
|
-
export function assertValidNames(collected, languages) {
|
|
14
|
-
const configured = Object.keys(languages);
|
|
15
|
-
for (const [locale, namespaces] of Object.entries(collected)) {
|
|
16
|
-
if (!configured.includes(locale)) {
|
|
17
|
-
throw new Error(`Locale folder "${locale}" is not one of \`languages\` in edgekits-i18n.config.ts: ${configured.join(', ')}.`);
|
|
18
|
-
}
|
|
19
|
-
for (const ns of Object.keys(namespaces)) {
|
|
20
|
-
if (!NAMESPACE_NAME.test(ns)) {
|
|
21
|
-
throw new Error(`Namespace "${ns}" (${locale}/${ns}.json) must match ${NAMESPACE_NAME}: lowercase letters, digits and underscores.`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* A single-quoted TS string literal, escaped by JSON.stringify. For every name the
|
|
28
|
-
* validation accepts it is byte-identical to the plain `'name'` the CLI always wrote, so
|
|
29
|
-
* existing generated files do not change; anything else comes out escaped, not injected.
|
|
30
|
-
*/
|
|
31
|
-
export function quoteString(value) {
|
|
32
|
-
const escaped = JSON.stringify(value).slice(1, -1).replace(/\\"/g, '"').replace(/'/g, "\\'");
|
|
33
|
-
return `'${escaped}'`;
|
|
34
|
-
}
|
|
35
|
-
/** Locales that have at least one namespace, sorted. */
|
|
36
|
-
export function localesWithTranslations(collected) {
|
|
37
|
-
return Object.keys(collected).sort();
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* The locale the schema and the namespace list are derived from: the default locale
|
|
41
|
-
* when it has translations, otherwise the first locale that does.
|
|
42
|
-
*/
|
|
43
|
-
export function schemaSourceLocale(collected, defaultLocale) {
|
|
44
|
-
if (collected[defaultLocale])
|
|
45
|
-
return defaultLocale;
|
|
46
|
-
return localesWithTranslations(collected)[0];
|
|
47
|
-
}
|
|
48
|
-
/** `wrangler kv bulk put` input: one entry per locale and namespace. */
|
|
49
|
-
export function buildBulkData(collected, keyPrefix) {
|
|
50
|
-
const bulkData = [];
|
|
51
|
-
for (const [locale, namespaces] of Object.entries(collected)) {
|
|
52
|
-
for (const [ns, json] of Object.entries(namespaces)) {
|
|
53
|
-
bulkData.push({ key: formatTranslationKvKey(keyPrefix, locale, ns), value: JSON.stringify(json) });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return bulkData;
|
|
57
|
-
}
|
|
58
|
-
function isTree(value) {
|
|
59
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
60
|
-
}
|
|
61
|
-
// The JSON kind of an array element; an array is typed only when all its elements share one.
|
|
62
|
-
function jsonKind(value) {
|
|
63
|
-
if (value === null)
|
|
64
|
-
return 'null';
|
|
65
|
-
if (Array.isArray(value))
|
|
66
|
-
return 'array';
|
|
67
|
-
return typeof value;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* The TS type of one JSON value. Objects become object types; arrays become `string[]`
|
|
71
|
-
* (strings, other scalars, or empty) or `Array<…>` typed from their first element
|
|
72
|
-
* (objects, nested arrays); every other leaf stays `string`. `keyPath` and `file` name the
|
|
73
|
-
* value in the error a mixed array raises.
|
|
74
|
-
*/
|
|
75
|
-
function generateSchemaTs(value, indent, keyPath, file) {
|
|
76
|
-
if (Array.isArray(value)) {
|
|
77
|
-
if (value.length === 0)
|
|
78
|
-
return 'string[]';
|
|
79
|
-
const kinds = [...new Set(value.map(jsonKind))];
|
|
80
|
-
if (kinds.length > 1) {
|
|
81
|
-
throw new Error(`Key "${keyPath}" (${file}) is an array that mixes ${kinds.join(', ')} elements: ` +
|
|
82
|
-
'I18n.Schema cannot type it. Use one kind of element per array.');
|
|
83
|
-
}
|
|
84
|
-
const first = value[0];
|
|
85
|
-
if (!isTree(first) && !Array.isArray(first))
|
|
86
|
-
return 'string[]';
|
|
87
|
-
return `Array<${generateSchemaTs(first, indent, `${keyPath}[0]`, file)}>`;
|
|
88
|
-
}
|
|
89
|
-
if (!isTree(value))
|
|
90
|
-
return 'string';
|
|
91
|
-
const entries = Object.entries(value);
|
|
92
|
-
if (entries.length === 0)
|
|
93
|
-
return '{}';
|
|
94
|
-
const pad = ' '.repeat(indent);
|
|
95
|
-
const padInner = ' '.repeat(indent + 2);
|
|
96
|
-
const lines = entries.map(([key, child]) => `${padInner}${JSON.stringify(key)}: ${generateSchemaTs(child, indent + 2, `${keyPath}.${key}`, file)}`);
|
|
97
|
-
return `{\n${lines.join(';\n')};\n${pad}}`;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* `src/i18n.generated.d.ts`: the global `I18n.Schema`, from the schema source locale.
|
|
101
|
-
* Throws, naming the key, when an array in it mixes kinds of elements.
|
|
102
|
-
*/
|
|
103
|
-
export function buildTypesContent(collected, defaultLocale) {
|
|
104
|
-
const source = schemaSourceLocale(collected, defaultLocale);
|
|
105
|
-
const schemaObject = (source && collected[source]) || {};
|
|
106
|
-
const namespaceLines = Object.entries(schemaObject).map(([ns, value]) => {
|
|
107
|
-
const valueTs = generateSchemaTs(value, 10, ns, `${source}/${ns}.json`);
|
|
108
|
-
return ` ${JSON.stringify(ns)}: ${valueTs}`;
|
|
109
|
-
});
|
|
110
|
-
const interfaceBody = namespaceLines.length > 0
|
|
111
|
-
? ` export interface Schema {\n${namespaceLines.join(';\n')};\n }`
|
|
112
|
-
: ` export interface Schema {}`;
|
|
113
|
-
return `${HEADER}
|
|
114
|
-
declare global {
|
|
115
|
-
namespace I18n {
|
|
116
|
-
${interfaceBody}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export {}
|
|
121
|
-
`;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* `runtime-constants.ts`:
|
|
125
|
-
* - NAMESPACE_KEYS -> from the schema source locale (default locale preferred)
|
|
126
|
-
* - LOCALES_WITH_TRANSLATIONS -> from all collected locales
|
|
127
|
-
* - FULLY_TRANSLATED_LOCALES -> locales that have ALL namespace keys
|
|
128
|
-
*/
|
|
129
|
-
export function buildRuntimeConstantsContent(collected, defaultLocale) {
|
|
130
|
-
const source = schemaSourceLocale(collected, defaultLocale);
|
|
131
|
-
const namespaceKeys = Object.keys((source && collected[source]) || {}).sort();
|
|
132
|
-
const locales = localesWithTranslations(collected);
|
|
133
|
-
const fullyTranslatedLocales = locales.filter((locale) => {
|
|
134
|
-
const localeNamespaces = Object.keys(collected[locale] || {});
|
|
135
|
-
return namespaceKeys.every((ns) => localeNamespaces.includes(ns));
|
|
136
|
-
});
|
|
137
|
-
return `${HEADER}
|
|
138
|
-
export const NAMESPACE_KEYS = [${namespaceKeys.map(quoteString).join(', ')}] as [string, ...string[]];
|
|
139
|
-
|
|
140
|
-
export const LOCALES_WITH_TRANSLATIONS = [${locales.map(quoteString).join(', ')}] as const;
|
|
141
|
-
|
|
142
|
-
export const FULLY_TRANSLATED_LOCALES = [${fullyTranslatedLocales.map(quoteString).join(', ')}] as const;
|
|
143
|
-
`;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* `fallbacks.generated.ts`: one `FALLBACK_<NAMESPACE>` per default-locale namespace.
|
|
147
|
-
* Null when the default locale has no translations (nothing to fall back to).
|
|
148
|
-
*/
|
|
149
|
-
export function buildFallbacksContent(collected, defaultLocale) {
|
|
150
|
-
const fallbackNamespaces = collected[defaultLocale];
|
|
151
|
-
if (!fallbackNamespaces)
|
|
152
|
-
return null;
|
|
153
|
-
const lines = [HEADER];
|
|
154
|
-
lines.push(`export const FALLBACK_LOCALE = ${JSON.stringify(defaultLocale)} as const;\n`);
|
|
155
|
-
for (const [ns, json] of Object.entries(fallbackNamespaces)) {
|
|
156
|
-
const constName = `FALLBACK_${ns.toUpperCase()}`;
|
|
157
|
-
lines.push(`export const ${constName} = ${JSON.stringify(json, null, 2)} as const;\n`);
|
|
158
|
-
}
|
|
159
|
-
return lines.join('\n');
|
|
160
|
-
}
|
package/dist/cli/hashes.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { CollectedData } from './generate.ts';
|
|
2
|
-
/**
|
|
3
|
-
* Per-namespace content hashes and the change diff that drives the cache purge.
|
|
4
|
-
* Pure apart from hashing. Same algorithm as the site's `scripts/bundle-translations.ts`,
|
|
5
|
-
* so an existing `.i18n-hashes.json` stays a valid baseline.
|
|
6
|
-
*/
|
|
7
|
-
export type HashMap = Record<string, string>;
|
|
8
|
-
export declare function stableStringify(value: unknown): string;
|
|
9
|
-
export declare function computeHash(value: unknown): string;
|
|
10
|
-
/**
|
|
11
|
-
* Hash every collected namespace and list the "<locale>:<namespace>" pairs whose hash
|
|
12
|
-
* differs from the previous run (all of them when there is no previous run).
|
|
13
|
-
*/
|
|
14
|
-
export declare function diffHashes(collected: CollectedData, previousHashes: HashMap): {
|
|
15
|
-
currentHashes: HashMap;
|
|
16
|
-
changedKeys: string[];
|
|
17
|
-
};
|
|
18
|
-
/** Purge API URLs for the changed "<locale>:<namespace>" pairs. */
|
|
19
|
-
export declare function purgeUrlsFor(changedKeys: readonly string[], keyPrefix: string): string[];
|
package/dist/cli/hashes.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import { formatTranslationCacheUrl } from "../key-format.js";
|
|
3
|
-
export function stableStringify(value) {
|
|
4
|
-
if (value === null || typeof value !== 'object') {
|
|
5
|
-
return JSON.stringify(value);
|
|
6
|
-
}
|
|
7
|
-
if (Array.isArray(value)) {
|
|
8
|
-
return '[' + value.map(stableStringify).join(',') + ']';
|
|
9
|
-
}
|
|
10
|
-
const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
|
|
11
|
-
return '{' + entries.map(([k, v]) => JSON.stringify(k) + ':' + stableStringify(v)).join(',') + '}';
|
|
12
|
-
}
|
|
13
|
-
export function computeHash(value) {
|
|
14
|
-
const json = stableStringify(value);
|
|
15
|
-
return crypto.createHash('sha256').update(json).digest('hex').slice(0, 12);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Hash every collected namespace and list the "<locale>:<namespace>" pairs whose hash
|
|
19
|
-
* differs from the previous run (all of them when there is no previous run).
|
|
20
|
-
*/
|
|
21
|
-
export function diffHashes(collected, previousHashes) {
|
|
22
|
-
const currentHashes = {};
|
|
23
|
-
const changedKeys = [];
|
|
24
|
-
for (const [locale, namespaces] of Object.entries(collected)) {
|
|
25
|
-
for (const [ns, json] of Object.entries(namespaces)) {
|
|
26
|
-
const hashKey = `${locale}:${ns}`;
|
|
27
|
-
const hash = computeHash(json);
|
|
28
|
-
currentHashes[hashKey] = hash;
|
|
29
|
-
if (previousHashes[hashKey] !== hash) {
|
|
30
|
-
changedKeys.push(hashKey);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return { currentHashes, changedKeys };
|
|
35
|
-
}
|
|
36
|
-
/** Purge API URLs for the changed "<locale>:<namespace>" pairs. */
|
|
37
|
-
export function purgeUrlsFor(changedKeys, keyPrefix) {
|
|
38
|
-
return changedKeys.map((hashKey) => {
|
|
39
|
-
const [locale, ns] = hashKey.split(':');
|
|
40
|
-
return formatTranslationCacheUrl(keyPrefix, locale, ns);
|
|
41
|
-
});
|
|
42
|
-
}
|
package/dist/cli/index.d.ts
DELETED