@elytracms/studio 0.0.29 → 0.0.31
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/convex/redirects.ts +3 -0
- package/convex/schema.ts +2 -0
- package/dist/components/app-views/default-modules.d.ts +3 -1
- package/dist/components/app-views/default-modules.js +22 -18
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +10 -0
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/builder/property-inspector.js +3 -2
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +2 -1
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/cms/cms-index.js +2 -1
- package/dist/components/cms/cms-index.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +11 -2
- package/dist/components/cms/cms-store.js +17 -4
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +5 -4
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +5 -4
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/editable-cell.js +3 -2
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control.js +6 -5
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/scalar-value-editor.js +2 -1
- package/dist/components/cms/scalar-value-editor.js.map +1 -1
- package/dist/components/components/field-inspector.js +3 -2
- package/dist/components/components/field-inspector.js.map +1 -1
- package/dist/components/current-user-menu.js +4 -2
- package/dist/components/current-user-menu.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.d.ts +4 -3
- package/dist/components/redirects/redirects-collection-view-source.js +23 -3
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/redirects/redirects-store.js +1 -0
- package/dist/components/redirects/redirects-store.js.map +1 -1
- package/dist/customization.d.ts +7 -0
- package/dist/customization.js +6 -0
- package/dist/customization.js.map +1 -1
- package/dist/lib/cms-views/view-model.js +2 -2
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/i18n/i18n.d.ts +45 -0
- package/dist/lib/i18n/i18n.js +125 -0
- package/dist/lib/i18n/i18n.js.map +1 -0
- package/dist/lib/i18n/index.d.ts +12 -0
- package/dist/lib/i18n/index.js +4 -0
- package/dist/lib/i18n/index.js.map +1 -0
- package/dist/lib/i18n/messages.en.d.ts +15 -0
- package/dist/lib/i18n/messages.en.js +39 -0
- package/dist/lib/i18n/messages.en.js.map +1 -0
- package/dist/lib/i18n/types.d.ts +45 -0
- package/dist/lib/i18n/types.js +19 -0
- package/dist/lib/i18n/types.js.map +1 -0
- package/dist/lib/persistence/convex-adapter.js +2 -0
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/references/reference-model.js +4 -3
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +8 -0
- package/dist/lib/workspace/index.js +10 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { LocalizedString } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { MessageCatalog, SourceCatalog } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Pick the best available locale for the browser's ordered language preferences
|
|
5
|
+
* (EC-241): an exact tag match wins, else the base language (`de-DE` → `de`), else
|
|
6
|
+
* `defaultLocale`. Pure + deterministic — the browser list is the only input.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveActiveLocale(available: readonly string[], preferred: readonly string[], defaultLocale: string): string;
|
|
9
|
+
interface ResolvedI18n {
|
|
10
|
+
locale: string;
|
|
11
|
+
defaultLocale: string;
|
|
12
|
+
/** The active-locale catalog (empty when active === default → the English source is used). */
|
|
13
|
+
active: MessageCatalog;
|
|
14
|
+
source: SourceCatalog;
|
|
15
|
+
}
|
|
16
|
+
/** Resolve the studio i18n once per boot (config static; locale from the browser). */
|
|
17
|
+
export declare function resolveStudioI18n(): ResolvedI18n;
|
|
18
|
+
/**
|
|
19
|
+
* Translate a message key for the active UI locale (EC-241): active-locale string
|
|
20
|
+
* ?? English source ?? **the key itself** (a visible fallback + a one-time console
|
|
21
|
+
* signal — "failures are first-class", never a silent blank). Non-hook — reads the
|
|
22
|
+
* per-boot singleton, so config-build and pure code can localize too; {@link useT}
|
|
23
|
+
* is the React-idiomatic wrapper.
|
|
24
|
+
*/
|
|
25
|
+
export declare function translate(key: string, vars?: Record<string, string | number>): string;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a {@link LocalizedString} (field/collection/option label) against the
|
|
28
|
+
* active UI locale (EC-241) — **non-hook**, reading the per-boot singleton, so it is
|
|
29
|
+
* safe to call anywhere: a component body, a nested render helper, or config-build
|
|
30
|
+
* code. Returns `fallback` (default `''`) when the value is absent. Prefer this over
|
|
31
|
+
* `resolveLabel(x, useUiLocale())` at render sites — it localizes without a hook, so
|
|
32
|
+
* it can't trip the rules-of-hooks in a conditional/nested position.
|
|
33
|
+
*/
|
|
34
|
+
export declare function localizedLabel(value: LocalizedString | undefined, fallback?: string): string;
|
|
35
|
+
/** The active studio-UI locale (browser-resolved). */
|
|
36
|
+
export declare function useUiLocale(): string;
|
|
37
|
+
/** A `t(key, vars?)` translator bound to the active locale. */
|
|
38
|
+
export declare function useT(): (key: string, vars?: Record<string, string | number>) => string;
|
|
39
|
+
/** Resolve a {@link LocalizedString} label (field/collection/option) for the active locale. */
|
|
40
|
+
export declare function useLabel(): (value: LocalizedString | undefined, fallback?: string) => string;
|
|
41
|
+
/** Test seam: force the active locale (and clear the per-boot cache). Never used by app code. */
|
|
42
|
+
export declare function setUiLocaleForTests(locale: string | null): void;
|
|
43
|
+
/** Test seam: clear the per-boot resolution cache. Never used by app code. */
|
|
44
|
+
export declare function resetStudioI18nForTests(): void;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { resolveLabel } from '@elytracms/core/cms-core';
|
|
3
|
+
import { resolveDeploymentI18n } from '@elytracms/studio/lib/workspace';
|
|
4
|
+
import { EN_MESSAGES } from './messages.en';
|
|
5
|
+
/**
|
|
6
|
+
* Studio-UI localization runtime (EC-241). Resolves the active UI locale from the
|
|
7
|
+
* **browser's preferred locale** (no persisted selector in v1 — that rides EC-240's
|
|
8
|
+
* deferred account settings), then translates message keys with an
|
|
9
|
+
* active-locale → English → visible-fallback chain. The studio config is static per
|
|
10
|
+
* boot, so resolution is memoised once (like `getWorkspaceState`); a locale switch
|
|
11
|
+
* needs a reload, which is all v1 offers.
|
|
12
|
+
*/
|
|
13
|
+
const BUILTIN_DEFAULT_LOCALE = 'en';
|
|
14
|
+
/**
|
|
15
|
+
* Pick the best available locale for the browser's ordered language preferences
|
|
16
|
+
* (EC-241): an exact tag match wins, else the base language (`de-DE` → `de`), else
|
|
17
|
+
* `defaultLocale`. Pure + deterministic — the browser list is the only input.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveActiveLocale(available, preferred, defaultLocale) {
|
|
20
|
+
const lower = available.map((a) => a.toLowerCase());
|
|
21
|
+
for (const pref of preferred) {
|
|
22
|
+
const p = pref.toLowerCase();
|
|
23
|
+
const base = p.split('-')[0] ?? p;
|
|
24
|
+
const at = lower.findIndex((a) => a === p || a === base);
|
|
25
|
+
if (at >= 0)
|
|
26
|
+
return available[at];
|
|
27
|
+
}
|
|
28
|
+
return defaultLocale;
|
|
29
|
+
}
|
|
30
|
+
let cached = null;
|
|
31
|
+
let localeOverride = null;
|
|
32
|
+
function navigatorLanguages() {
|
|
33
|
+
if (typeof navigator === 'undefined')
|
|
34
|
+
return [];
|
|
35
|
+
if (navigator.languages && navigator.languages.length > 0)
|
|
36
|
+
return [...navigator.languages];
|
|
37
|
+
return navigator.language ? [navigator.language] : [];
|
|
38
|
+
}
|
|
39
|
+
const warned = new Set();
|
|
40
|
+
function warnOnce(message) {
|
|
41
|
+
if (warned.has(message))
|
|
42
|
+
return;
|
|
43
|
+
warned.add(message);
|
|
44
|
+
if (typeof console !== 'undefined')
|
|
45
|
+
console.warn(message);
|
|
46
|
+
}
|
|
47
|
+
/** Resolve the studio i18n once per boot (config static; locale from the browser). */
|
|
48
|
+
export function resolveStudioI18n() {
|
|
49
|
+
if (cached)
|
|
50
|
+
return cached;
|
|
51
|
+
const config = resolveDeploymentI18n() ?? {};
|
|
52
|
+
const defaultLocale = config.defaultLocale ?? BUILTIN_DEFAULT_LOCALE;
|
|
53
|
+
const locales = config.locales ?? {};
|
|
54
|
+
const available = [defaultLocale, ...Object.keys(locales).filter((l) => l !== defaultLocale)];
|
|
55
|
+
const locale = localeOverride ?? resolveActiveLocale(available, navigatorLanguages(), defaultLocale);
|
|
56
|
+
const active = locale === defaultLocale ? {} : (locales[locale] ?? {});
|
|
57
|
+
cached = { locale, defaultLocale, active, source: EN_MESSAGES };
|
|
58
|
+
// Coverage lint: warn once when the active locale doesn't cover every English key.
|
|
59
|
+
if (locale !== defaultLocale && locales[locale]) {
|
|
60
|
+
const missing = Object.keys(EN_MESSAGES).filter((k) => !(k in active));
|
|
61
|
+
if (missing.length > 0) {
|
|
62
|
+
warnOnce(`[i18n] locale "${locale}" is missing ${missing.length} message(s); falling back to English for those.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return cached;
|
|
66
|
+
}
|
|
67
|
+
function interpolate(text, vars) {
|
|
68
|
+
return text.replace(/\{(\w+)\}/g, (whole, key) => (key in vars ? String(vars[key]) : whole));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Translate a message key for the active UI locale (EC-241): active-locale string
|
|
72
|
+
* ?? English source ?? **the key itself** (a visible fallback + a one-time console
|
|
73
|
+
* signal — "failures are first-class", never a silent blank). Non-hook — reads the
|
|
74
|
+
* per-boot singleton, so config-build and pure code can localize too; {@link useT}
|
|
75
|
+
* is the React-idiomatic wrapper.
|
|
76
|
+
*/
|
|
77
|
+
export function translate(key, vars) {
|
|
78
|
+
const i18n = resolveStudioI18n();
|
|
79
|
+
const hit = i18n.active[key] ?? i18n.source[key]?.message;
|
|
80
|
+
if (hit === undefined) {
|
|
81
|
+
warnOnce(`[i18n] missing message key: "${key}"`);
|
|
82
|
+
return key;
|
|
83
|
+
}
|
|
84
|
+
return vars ? interpolate(hit, vars) : hit;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a {@link LocalizedString} (field/collection/option label) against the
|
|
88
|
+
* active UI locale (EC-241) — **non-hook**, reading the per-boot singleton, so it is
|
|
89
|
+
* safe to call anywhere: a component body, a nested render helper, or config-build
|
|
90
|
+
* code. Returns `fallback` (default `''`) when the value is absent. Prefer this over
|
|
91
|
+
* `resolveLabel(x, useUiLocale())` at render sites — it localizes without a hook, so
|
|
92
|
+
* it can't trip the rules-of-hooks in a conditional/nested position.
|
|
93
|
+
*/
|
|
94
|
+
export function localizedLabel(value, fallback = '') {
|
|
95
|
+
const { locale, defaultLocale } = resolveStudioI18n();
|
|
96
|
+
return resolveLabel(value, locale, defaultLocale) ?? fallback;
|
|
97
|
+
}
|
|
98
|
+
// --- React hooks (config static per boot → stable identities) ---
|
|
99
|
+
/** The active studio-UI locale (browser-resolved). */
|
|
100
|
+
export function useUiLocale() {
|
|
101
|
+
return resolveStudioI18n().locale;
|
|
102
|
+
}
|
|
103
|
+
/** A `t(key, vars?)` translator bound to the active locale. */
|
|
104
|
+
export function useT() {
|
|
105
|
+
return translate;
|
|
106
|
+
}
|
|
107
|
+
/** Resolve a {@link LocalizedString} label (field/collection/option) for the active locale. */
|
|
108
|
+
export function useLabel() {
|
|
109
|
+
return useMemo(() => {
|
|
110
|
+
const { locale, defaultLocale } = resolveStudioI18n();
|
|
111
|
+
return (value, fallback) => resolveLabel(value, locale, defaultLocale) ?? fallback ?? '';
|
|
112
|
+
}, []);
|
|
113
|
+
}
|
|
114
|
+
/** Test seam: force the active locale (and clear the per-boot cache). Never used by app code. */
|
|
115
|
+
export function setUiLocaleForTests(locale) {
|
|
116
|
+
localeOverride = locale;
|
|
117
|
+
cached = null;
|
|
118
|
+
warned.clear();
|
|
119
|
+
}
|
|
120
|
+
/** Test seam: clear the per-boot resolution cache. Never used by app code. */
|
|
121
|
+
export function resetStudioI18nForTests() {
|
|
122
|
+
cached = null;
|
|
123
|
+
warned.clear();
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=i18n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../../src/lib/i18n/i18n.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport { resolveLabel } from '@elytracms/core/cms-core'\nimport type { LocalizedString } from '@elytracms/core/cms-core'\nimport { resolveDeploymentI18n } from '@elytracms/studio/lib/workspace'\nimport { EN_MESSAGES } from './messages.en'\nimport type { MessageCatalog, SourceCatalog } from './types'\n\n/**\n * Studio-UI localization runtime (EC-241). Resolves the active UI locale from the\n * **browser's preferred locale** (no persisted selector in v1 — that rides EC-240's\n * deferred account settings), then translates message keys with an\n * active-locale → English → visible-fallback chain. The studio config is static per\n * boot, so resolution is memoised once (like `getWorkspaceState`); a locale switch\n * needs a reload, which is all v1 offers.\n */\n\nconst BUILTIN_DEFAULT_LOCALE = 'en'\n\n/**\n * Pick the best available locale for the browser's ordered language preferences\n * (EC-241): an exact tag match wins, else the base language (`de-DE` → `de`), else\n * `defaultLocale`. Pure + deterministic — the browser list is the only input.\n */\nexport function resolveActiveLocale(\n available: readonly string[],\n preferred: readonly string[],\n defaultLocale: string,\n): string {\n const lower = available.map((a) => a.toLowerCase())\n for (const pref of preferred) {\n const p = pref.toLowerCase()\n const base = p.split('-')[0] ?? p\n const at = lower.findIndex((a) => a === p || a === base)\n if (at >= 0) return available[at]!\n }\n return defaultLocale\n}\n\ninterface ResolvedI18n {\n locale: string\n defaultLocale: string\n /** The active-locale catalog (empty when active === default → the English source is used). */\n active: MessageCatalog\n source: SourceCatalog\n}\n\nlet cached: ResolvedI18n | null = null\nlet localeOverride: string | null = null\n\nfunction navigatorLanguages(): string[] {\n if (typeof navigator === 'undefined') return []\n if (navigator.languages && navigator.languages.length > 0) return [...navigator.languages]\n return navigator.language ? [navigator.language] : []\n}\n\nconst warned = new Set<string>()\nfunction warnOnce(message: string): void {\n if (warned.has(message)) return\n warned.add(message)\n if (typeof console !== 'undefined') console.warn(message)\n}\n\n/** Resolve the studio i18n once per boot (config static; locale from the browser). */\nexport function resolveStudioI18n(): ResolvedI18n {\n if (cached) return cached\n const config = resolveDeploymentI18n() ?? {}\n const defaultLocale = config.defaultLocale ?? BUILTIN_DEFAULT_LOCALE\n const locales = config.locales ?? {}\n const available = [defaultLocale, ...Object.keys(locales).filter((l) => l !== defaultLocale)]\n const locale = localeOverride ?? resolveActiveLocale(available, navigatorLanguages(), defaultLocale)\n const active = locale === defaultLocale ? {} : (locales[locale] ?? {})\n cached = { locale, defaultLocale, active, source: EN_MESSAGES }\n // Coverage lint: warn once when the active locale doesn't cover every English key.\n if (locale !== defaultLocale && locales[locale]) {\n const missing = Object.keys(EN_MESSAGES).filter((k) => !(k in active))\n if (missing.length > 0) {\n warnOnce(\n `[i18n] locale \"${locale}\" is missing ${missing.length} message(s); falling back to English for those.`,\n )\n }\n }\n return cached\n}\n\nfunction interpolate(text: string, vars: Record<string, string | number>): string {\n return text.replace(/\\{(\\w+)\\}/g, (whole, key: string) => (key in vars ? String(vars[key]) : whole))\n}\n\n/**\n * Translate a message key for the active UI locale (EC-241): active-locale string\n * ?? English source ?? **the key itself** (a visible fallback + a one-time console\n * signal — \"failures are first-class\", never a silent blank). Non-hook — reads the\n * per-boot singleton, so config-build and pure code can localize too; {@link useT}\n * is the React-idiomatic wrapper.\n */\nexport function translate(key: string, vars?: Record<string, string | number>): string {\n const i18n = resolveStudioI18n()\n const hit = i18n.active[key] ?? i18n.source[key]?.message\n if (hit === undefined) {\n warnOnce(`[i18n] missing message key: \"${key}\"`)\n return key\n }\n return vars ? interpolate(hit, vars) : hit\n}\n\n/**\n * Resolve a {@link LocalizedString} (field/collection/option label) against the\n * active UI locale (EC-241) — **non-hook**, reading the per-boot singleton, so it is\n * safe to call anywhere: a component body, a nested render helper, or config-build\n * code. Returns `fallback` (default `''`) when the value is absent. Prefer this over\n * `resolveLabel(x, useUiLocale())` at render sites — it localizes without a hook, so\n * it can't trip the rules-of-hooks in a conditional/nested position.\n */\nexport function localizedLabel(value: LocalizedString | undefined, fallback = ''): string {\n const { locale, defaultLocale } = resolveStudioI18n()\n return resolveLabel(value, locale, defaultLocale) ?? fallback\n}\n\n// --- React hooks (config static per boot → stable identities) ---\n\n/** The active studio-UI locale (browser-resolved). */\nexport function useUiLocale(): string {\n return resolveStudioI18n().locale\n}\n\n/** A `t(key, vars?)` translator bound to the active locale. */\nexport function useT(): (key: string, vars?: Record<string, string | number>) => string {\n return translate\n}\n\n/** Resolve a {@link LocalizedString} label (field/collection/option) for the active locale. */\nexport function useLabel(): (value: LocalizedString | undefined, fallback?: string) => string {\n return useMemo(() => {\n const { locale, defaultLocale } = resolveStudioI18n()\n return (value, fallback) => resolveLabel(value, locale, defaultLocale) ?? fallback ?? ''\n }, [])\n}\n\n/** Test seam: force the active locale (and clear the per-boot cache). Never used by app code. */\nexport function setUiLocaleForTests(locale: string | null): void {\n localeOverride = locale\n cached = null\n warned.clear()\n}\n\n/** Test seam: clear the per-boot resolution cache. Never used by app code. */\nexport function resetStudioI18nForTests(): void {\n cached = null\n warned.clear()\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@elytracms/studio/lib/i18n` — studio-UI localization (EC-241).
|
|
3
|
+
*
|
|
4
|
+
* - Public config surface (`StudioI18nConfig`, `MessageCatalog`, `msg`) is re-exported
|
|
5
|
+
* from `@elytracms/studio/customization` so a consumer authors catalogs there.
|
|
6
|
+
* - Studio components localize chrome via `translate`/`useT` and schema labels via
|
|
7
|
+
* `useLabel`.
|
|
8
|
+
*/
|
|
9
|
+
export type { SourceMessage, SourceCatalog, MessageCatalog, StudioI18nConfig } from './types';
|
|
10
|
+
export { msg } from './types';
|
|
11
|
+
export { EN_MESSAGES } from './messages.en';
|
|
12
|
+
export { resolveActiveLocale, resolveStudioI18n, translate, localizedLabel, useT, useUiLocale, useLabel, setUiLocaleForTests, resetStudioI18nForTests, } from './i18n';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { msg } from './types';
|
|
2
|
+
export { EN_MESSAGES } from './messages.en';
|
|
3
|
+
export { resolveActiveLocale, resolveStudioI18n, translate, localizedLabel, useT, useUiLocale, useLabel, setUiLocaleForTests, resetStudioI18nForTests, } from './i18n';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/i18n/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,EACT,cAAc,EACd,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,QAAQ,CAAA","sourcesContent":["/**\n * `@elytracms/studio/lib/i18n` — studio-UI localization (EC-241).\n *\n * - Public config surface (`StudioI18nConfig`, `MessageCatalog`, `msg`) is re-exported\n * from `@elytracms/studio/customization` so a consumer authors catalogs there.\n * - Studio components localize chrome via `translate`/`useT` and schema labels via\n * `useLabel`.\n */\nexport type { SourceMessage, SourceCatalog, MessageCatalog, StudioI18nConfig } from './types'\nexport { msg } from './types'\nexport { EN_MESSAGES } from './messages.en'\nexport {\n resolveActiveLocale,\n resolveStudioI18n,\n translate,\n localizedLabel,\n useT,\n useUiLocale,\n useLabel,\n setUiLocaleForTests,\n resetStudioI18nForTests,\n} from './i18n'\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SourceCatalog } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The engine's built-in **English source catalog** (EC-241) — the source of truth
|
|
4
|
+
* and the ultimate fallback for every studio-chrome string. A consumer adds a
|
|
5
|
+
* language via `defineStudio({ i18n: { locales: { de: deMessages } } })`; a key
|
|
6
|
+
* missing from the active locale falls back to the English `message` here, then to
|
|
7
|
+
* the key itself (a visible fallback + a one-time console signal), never a silent
|
|
8
|
+
* blank.
|
|
9
|
+
*
|
|
10
|
+
* v1 seeds a representative, high-traffic slice — the section rail + the shell user
|
|
11
|
+
* menu — to prove the pipeline end-to-end. Extracting every remaining studio string
|
|
12
|
+
* into keys is an ongoing mechanical sweep (see EC-241 "scope honesty"), not a
|
|
13
|
+
* silent omission.
|
|
14
|
+
*/
|
|
15
|
+
export declare const EN_MESSAGES: SourceCatalog;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { msg } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The engine's built-in **English source catalog** (EC-241) — the source of truth
|
|
4
|
+
* and the ultimate fallback for every studio-chrome string. A consumer adds a
|
|
5
|
+
* language via `defineStudio({ i18n: { locales: { de: deMessages } } })`; a key
|
|
6
|
+
* missing from the active locale falls back to the English `message` here, then to
|
|
7
|
+
* the key itself (a visible fallback + a one-time console signal), never a silent
|
|
8
|
+
* blank.
|
|
9
|
+
*
|
|
10
|
+
* v1 seeds a representative, high-traffic slice — the section rail + the shell user
|
|
11
|
+
* menu — to prove the pipeline end-to-end. Extracting every remaining studio string
|
|
12
|
+
* into keys is an ongoing mechanical sweep (see EC-241 "scope honesty"), not a
|
|
13
|
+
* silent omission.
|
|
14
|
+
*/
|
|
15
|
+
export const EN_MESSAGES = {
|
|
16
|
+
// --- Built-in navigation views (the section rail) ---
|
|
17
|
+
'nav.cms.label': msg('CMS'),
|
|
18
|
+
'nav.cms.description': msg('Model collections, fields, and documents.'),
|
|
19
|
+
'nav.components.label': msg('Components'),
|
|
20
|
+
'nav.components.description': msg('Browse the registry of primitives and project components.'),
|
|
21
|
+
'nav.redirects.label': msg('Redirects'),
|
|
22
|
+
'nav.redirects.description': msg('Create and manage URL redirects (301/302) for this site.'),
|
|
23
|
+
'nav.assets.label': msg('Assets'),
|
|
24
|
+
'nav.assets.description': msg('Manage uploaded media and metadata.'),
|
|
25
|
+
'nav.users.label': msg('Users'),
|
|
26
|
+
'nav.users.description': msg('Manage who has access to this deployment and their role.'),
|
|
27
|
+
'nav.extensions.label': msg('Extensions'),
|
|
28
|
+
'nav.extensions.description': msg('Install components, field types, adapters, and templates from the marketplace.'),
|
|
29
|
+
'nav.settings.label': msg('Settings'),
|
|
30
|
+
'nav.settings.description': msg('Configure project metadata and locales.'),
|
|
31
|
+
// --- Current-user menu (shell header) ---
|
|
32
|
+
'userMenu.accountMenu': msg('Account menu', 'aria-label on the avatar button that opens the user menu.'),
|
|
33
|
+
'userMenu.lightMode': msg('Light mode', 'Menu item: switch the studio to the light colour theme.'),
|
|
34
|
+
'userMenu.darkMode': msg('Dark mode', 'Menu item: switch the studio to the dark colour theme.'),
|
|
35
|
+
'userMenu.accountSettings': msg('Account settings'),
|
|
36
|
+
'userMenu.soon': msg('Soon', 'Badge on a feature that is not available yet.'),
|
|
37
|
+
'userMenu.signOut': msg('Sign out'),
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=messages.en.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.en.js","sourceRoot":"","sources":["../../../src/lib/i18n/messages.en.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,CAAA;AAG7B;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,WAAW,GAAkB;IACxC,uDAAuD;IACvD,eAAe,EAAE,GAAG,CAAC,KAAK,CAAC;IAC3B,qBAAqB,EAAE,GAAG,CAAC,2CAA2C,CAAC;IACvE,sBAAsB,EAAE,GAAG,CAAC,YAAY,CAAC;IACzC,4BAA4B,EAAE,GAAG,CAAC,2DAA2D,CAAC;IAC9F,qBAAqB,EAAE,GAAG,CAAC,WAAW,CAAC;IACvC,2BAA2B,EAAE,GAAG,CAAC,0DAA0D,CAAC;IAC5F,kBAAkB,EAAE,GAAG,CAAC,QAAQ,CAAC;IACjC,wBAAwB,EAAE,GAAG,CAAC,qCAAqC,CAAC;IACpE,iBAAiB,EAAE,GAAG,CAAC,OAAO,CAAC;IAC/B,uBAAuB,EAAE,GAAG,CAAC,0DAA0D,CAAC;IACxF,sBAAsB,EAAE,GAAG,CAAC,YAAY,CAAC;IACzC,4BAA4B,EAAE,GAAG,CAC/B,gFAAgF,CACjF;IACD,oBAAoB,EAAE,GAAG,CAAC,UAAU,CAAC;IACrC,0BAA0B,EAAE,GAAG,CAAC,yCAAyC,CAAC;IAE1E,2CAA2C;IAC3C,sBAAsB,EAAE,GAAG,CAAC,cAAc,EAAE,2DAA2D,CAAC;IACxG,oBAAoB,EAAE,GAAG,CAAC,YAAY,EAAE,yDAAyD,CAAC;IAClG,mBAAmB,EAAE,GAAG,CAAC,WAAW,EAAE,wDAAwD,CAAC;IAC/F,0BAA0B,EAAE,GAAG,CAAC,kBAAkB,CAAC;IACnD,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,+CAA+C,CAAC;IAC7E,kBAAkB,EAAE,GAAG,CAAC,UAAU,CAAC;CACpC,CAAA","sourcesContent":["import { msg } from './types'\nimport type { SourceCatalog } from './types'\n\n/**\n * The engine's built-in **English source catalog** (EC-241) — the source of truth\n * and the ultimate fallback for every studio-chrome string. A consumer adds a\n * language via `defineStudio({ i18n: { locales: { de: deMessages } } })`; a key\n * missing from the active locale falls back to the English `message` here, then to\n * the key itself (a visible fallback + a one-time console signal), never a silent\n * blank.\n *\n * v1 seeds a representative, high-traffic slice — the section rail + the shell user\n * menu — to prove the pipeline end-to-end. Extracting every remaining studio string\n * into keys is an ongoing mechanical sweep (see EC-241 \"scope honesty\"), not a\n * silent omission.\n */\nexport const EN_MESSAGES: SourceCatalog = {\n // --- Built-in navigation views (the section rail) ---\n 'nav.cms.label': msg('CMS'),\n 'nav.cms.description': msg('Model collections, fields, and documents.'),\n 'nav.components.label': msg('Components'),\n 'nav.components.description': msg('Browse the registry of primitives and project components.'),\n 'nav.redirects.label': msg('Redirects'),\n 'nav.redirects.description': msg('Create and manage URL redirects (301/302) for this site.'),\n 'nav.assets.label': msg('Assets'),\n 'nav.assets.description': msg('Manage uploaded media and metadata.'),\n 'nav.users.label': msg('Users'),\n 'nav.users.description': msg('Manage who has access to this deployment and their role.'),\n 'nav.extensions.label': msg('Extensions'),\n 'nav.extensions.description': msg(\n 'Install components, field types, adapters, and templates from the marketplace.',\n ),\n 'nav.settings.label': msg('Settings'),\n 'nav.settings.description': msg('Configure project metadata and locales.'),\n\n // --- Current-user menu (shell header) ---\n 'userMenu.accountMenu': msg('Account menu', 'aria-label on the avatar button that opens the user menu.'),\n 'userMenu.lightMode': msg('Light mode', 'Menu item: switch the studio to the light colour theme.'),\n 'userMenu.darkMode': msg('Dark mode', 'Menu item: switch the studio to the dark colour theme.'),\n 'userMenu.accountSettings': msg('Account settings'),\n 'userMenu.soon': msg('Soon', 'Badge on a feature that is not available yet.'),\n 'userMenu.signOut': msg('Sign out'),\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio-UI localization types (EC-241) — the leaf module both the message catalog
|
|
3
|
+
* and the resolver import, so nothing here may import studio runtime code (keeps the
|
|
4
|
+
* `defineStudio` config surface free of import cycles).
|
|
5
|
+
*
|
|
6
|
+
* This is the **studio-chrome** axis (engine-owned strings). Schema labels are a
|
|
7
|
+
* separate mechanism — a {@link import('@elytracms/core/cms-core').LocalizedString}
|
|
8
|
+
* on the field/collection def, resolved via `resolveLabel`. Content-*value*
|
|
9
|
+
* localization is a third, unrelated axis (`cms-core/localization.ts`).
|
|
10
|
+
*/
|
|
11
|
+
/** A source-catalog entry: the English string + an optional translator hint. */
|
|
12
|
+
export interface SourceMessage {
|
|
13
|
+
/** The English (source-of-truth) string. */
|
|
14
|
+
message: string;
|
|
15
|
+
/**
|
|
16
|
+
* Translator hint/context — the same word can translate differently by context
|
|
17
|
+
* (the standard `msgctxt`/Lingui-comment idea). **Authoring/translation-time only:**
|
|
18
|
+
* it lives on the in-engine source catalog and is NOT shipped in the flat
|
|
19
|
+
* consumer-provided translation catalogs.
|
|
20
|
+
*/
|
|
21
|
+
description?: string;
|
|
22
|
+
}
|
|
23
|
+
/** The in-engine English source catalog: message key → {@link SourceMessage}. */
|
|
24
|
+
export type SourceCatalog = Record<string, SourceMessage>;
|
|
25
|
+
/** A consumer-provided translation catalog: flat message key → localized string. */
|
|
26
|
+
export type MessageCatalog = Record<string, string>;
|
|
27
|
+
/** The studio-UI localization config passed to `defineStudio({ i18n })` (EC-241). */
|
|
28
|
+
export interface StudioI18nConfig {
|
|
29
|
+
/**
|
|
30
|
+
* The locale used when the browser's preferred locale matches nothing available.
|
|
31
|
+
* Defaults to `'en'` (the in-engine source language).
|
|
32
|
+
*/
|
|
33
|
+
defaultLocale?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Imported translation catalogs keyed by locale code, e.g. `{ de: deMessages }`.
|
|
36
|
+
* English is always available as the built-in source, so it need not be listed.
|
|
37
|
+
*/
|
|
38
|
+
locales?: Record<string, MessageCatalog>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Author a source-catalog entry with an optional translator hint (EC-241). Kept in
|
|
42
|
+
* this leaf module so `messages.en.ts` can build the catalog without importing the
|
|
43
|
+
* resolver (which would import studio runtime code).
|
|
44
|
+
*/
|
|
45
|
+
export declare function msg(message: string, description?: string): SourceMessage;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio-UI localization types (EC-241) — the leaf module both the message catalog
|
|
3
|
+
* and the resolver import, so nothing here may import studio runtime code (keeps the
|
|
4
|
+
* `defineStudio` config surface free of import cycles).
|
|
5
|
+
*
|
|
6
|
+
* This is the **studio-chrome** axis (engine-owned strings). Schema labels are a
|
|
7
|
+
* separate mechanism — a {@link import('@elytracms/core/cms-core').LocalizedString}
|
|
8
|
+
* on the field/collection def, resolved via `resolveLabel`. Content-*value*
|
|
9
|
+
* localization is a third, unrelated axis (`cms-core/localization.ts`).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Author a source-catalog entry with an optional translator hint (EC-241). Kept in
|
|
13
|
+
* this leaf module so `messages.en.ts` can build the catalog without importing the
|
|
14
|
+
* resolver (which would import studio runtime code).
|
|
15
|
+
*/
|
|
16
|
+
export function msg(message, description) {
|
|
17
|
+
return description !== undefined ? { message, description } : { message };
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/i18n/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAmCH;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,WAAoB;IACvD,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAA;AAC3E,CAAC","sourcesContent":["/**\n * Studio-UI localization types (EC-241) — the leaf module both the message catalog\n * and the resolver import, so nothing here may import studio runtime code (keeps the\n * `defineStudio` config surface free of import cycles).\n *\n * This is the **studio-chrome** axis (engine-owned strings). Schema labels are a\n * separate mechanism — a {@link import('@elytracms/core/cms-core').LocalizedString}\n * on the field/collection def, resolved via `resolveLabel`. Content-*value*\n * localization is a third, unrelated axis (`cms-core/localization.ts`).\n */\n\n/** A source-catalog entry: the English string + an optional translator hint. */\nexport interface SourceMessage {\n /** The English (source-of-truth) string. */\n message: string\n /**\n * Translator hint/context — the same word can translate differently by context\n * (the standard `msgctxt`/Lingui-comment idea). **Authoring/translation-time only:**\n * it lives on the in-engine source catalog and is NOT shipped in the flat\n * consumer-provided translation catalogs.\n */\n description?: string\n}\n\n/** The in-engine English source catalog: message key → {@link SourceMessage}. */\nexport type SourceCatalog = Record<string, SourceMessage>\n\n/** A consumer-provided translation catalog: flat message key → localized string. */\nexport type MessageCatalog = Record<string, string>\n\n/** The studio-UI localization config passed to `defineStudio({ i18n })` (EC-241). */\nexport interface StudioI18nConfig {\n /**\n * The locale used when the browser's preferred locale matches nothing available.\n * Defaults to `'en'` (the in-engine source language).\n */\n defaultLocale?: string\n /**\n * Imported translation catalogs keyed by locale code, e.g. `{ de: deMessages }`.\n * English is always available as the built-in source, so it need not be listed.\n */\n locales?: Record<string, MessageCatalog>\n}\n\n/**\n * Author a source-catalog entry with an optional translator hint (EC-241). Kept in\n * this leaf module so `messages.en.ts` can build the catalog without importing the\n * resolver (which would import studio runtime code).\n */\nexport function msg(message: string, description?: string): SourceMessage {\n return description !== undefined ? { message, description } : { message }\n}\n"]}
|
|
@@ -587,6 +587,7 @@ function redirectEntryFromDoc(doc, projectId) {
|
|
|
587
587
|
to: doc.to,
|
|
588
588
|
permanent: doc.permanent,
|
|
589
589
|
enabled: doc.enabled,
|
|
590
|
+
...(doc.meta !== undefined ? { meta: doc.meta } : {}),
|
|
590
591
|
createdAt: doc.createdAt,
|
|
591
592
|
updatedAt: doc.updatedAt,
|
|
592
593
|
};
|
|
@@ -614,6 +615,7 @@ class ConvexRedirectRepository {
|
|
|
614
615
|
to: input.to,
|
|
615
616
|
permanent: input.permanent ?? true,
|
|
616
617
|
enabled: input.enabled ?? true,
|
|
618
|
+
...(input.meta !== undefined ? { meta: input.meta } : {}),
|
|
617
619
|
createdAt: now,
|
|
618
620
|
updatedAt: now,
|
|
619
621
|
}));
|