@emdash-cms/admin 0.1.1 → 0.3.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/dist/config-Dku9rWu6.d.ts +34 -0
- package/dist/config-Dku9rWu6.d.ts.map +1 -0
- package/dist/index.d.ts +50 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1511 -682
- package/dist/index.js.map +1 -1
- package/dist/locales/de/messages.mjs +1 -0
- package/dist/locales/en/messages.mjs +1 -0
- package/dist/locales/fr/messages.mjs +1 -0
- package/dist/locales/index.d.ts +8 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/index.js +20 -0
- package/dist/locales/index.js.map +1 -0
- package/dist/messages-2jYesPAq.js +6 -0
- package/dist/messages-2jYesPAq.js.map +1 -0
- package/dist/messages-BfrdxD3Y.js +6 -0
- package/dist/messages-BfrdxD3Y.js.map +1 -0
- package/dist/messages-CtrDoZ_2.js +6 -0
- package/dist/messages-CtrDoZ_2.js.map +1 -0
- package/dist/plugins-XhZqfegd.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/useLocale-CXsoFCFt.js +80 -0
- package/dist/useLocale-CXsoFCFt.js.map +1 -0
- package/package.json +19 -5
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useLingui } from "@lingui/react";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/locales/config.ts
|
|
5
|
+
/** Validate a locale code against the Intl.Locale API (BCP 47). */
|
|
6
|
+
function validateLocaleCode(code) {
|
|
7
|
+
try {
|
|
8
|
+
return new Intl.Locale(code).baseName;
|
|
9
|
+
} catch {
|
|
10
|
+
if (import.meta.env.DEV) throw new Error(`Invalid locale code: "${code}"`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Available locales — extend this list as translations are added. */
|
|
14
|
+
const SUPPORTED_LOCALES = [{
|
|
15
|
+
code: "en",
|
|
16
|
+
label: "English"
|
|
17
|
+
}, {
|
|
18
|
+
code: "de",
|
|
19
|
+
label: "Deutsch"
|
|
20
|
+
}].filter((l) => validateLocaleCode(l.code));
|
|
21
|
+
const SUPPORTED_LOCALE_CODES = new Set(SUPPORTED_LOCALES.map((l) => l.code));
|
|
22
|
+
const DEFAULT_LOCALE = SUPPORTED_LOCALES[0].code;
|
|
23
|
+
const LOCALE_LABELS = new Map(SUPPORTED_LOCALES.map((l) => [l.code, l.label]));
|
|
24
|
+
/** Get a display label for a locale code, falling back to uppercase code. */
|
|
25
|
+
function getLocaleLabel(code) {
|
|
26
|
+
return LOCALE_LABELS.get(code) ?? code.toUpperCase();
|
|
27
|
+
}
|
|
28
|
+
const LOCALE_COOKIE_RE = /(?:^|;\s*)emdash-locale=([^;]+)/;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the admin locale from a Request.
|
|
31
|
+
* Priority: emdash-locale cookie → Accept-Language → DEFAULT_LOCALE.
|
|
32
|
+
*/
|
|
33
|
+
function resolveLocale(request) {
|
|
34
|
+
const cookieLocale = (request.headers.get("cookie") ?? "").match(LOCALE_COOKIE_RE)?.[1]?.trim() ?? "";
|
|
35
|
+
if (SUPPORTED_LOCALE_CODES.has(cookieLocale)) return cookieLocale;
|
|
36
|
+
const acceptLang = request.headers.get("accept-language") ?? "";
|
|
37
|
+
for (const entry of acceptLang.split(",")) {
|
|
38
|
+
const tag = entry.split(";")[0].trim().split("-")[0].toLowerCase();
|
|
39
|
+
if (SUPPORTED_LOCALE_CODES.has(tag)) return tag;
|
|
40
|
+
}
|
|
41
|
+
return DEFAULT_LOCALE;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/locales/useLocale.ts
|
|
46
|
+
function setCookie(code) {
|
|
47
|
+
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
48
|
+
document.cookie = `emdash-locale=${code}; Path=/_emdash; SameSite=Lax; Max-Age=31536000${secure}`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the current locale and a function to switch locales.
|
|
52
|
+
* Loads the new catalog dynamically and sets a cookie for server-side persistence.
|
|
53
|
+
*/
|
|
54
|
+
function useLocale() {
|
|
55
|
+
const { i18n } = useLingui();
|
|
56
|
+
const [locale, setLocaleState] = React.useState(i18n.locale);
|
|
57
|
+
const setLocale = React.useCallback((code) => {
|
|
58
|
+
if (code === i18n.locale || !SUPPORTED_LOCALE_CODES.has(code)) return;
|
|
59
|
+
setCookie(code);
|
|
60
|
+
import(`./${code}/messages.mjs`).then(({ messages }) => i18n.loadAndActivate({
|
|
61
|
+
locale: code,
|
|
62
|
+
messages
|
|
63
|
+
})).catch(() => {
|
|
64
|
+
setCookie(i18n.locale);
|
|
65
|
+
});
|
|
66
|
+
}, [i18n]);
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
return i18n.on("change", () => {
|
|
69
|
+
setLocaleState(i18n.locale);
|
|
70
|
+
});
|
|
71
|
+
}, [i18n]);
|
|
72
|
+
return {
|
|
73
|
+
locale,
|
|
74
|
+
setLocale
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { getLocaleLabel as a, SUPPORTED_LOCALE_CODES as i, DEFAULT_LOCALE as n, resolveLocale as o, SUPPORTED_LOCALES as r, useLocale as t };
|
|
80
|
+
//# sourceMappingURL=useLocale-CXsoFCFt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLocale-CXsoFCFt.js","names":[],"sources":["../src/locales/config.ts","../src/locales/useLocale.ts"],"sourcesContent":["/**\n * Locale configuration — single source of truth for supported locales.\n *\n * Imported by both the Lingui provider (client) and admin.astro (server).\n */\n\nexport interface SupportedLocale {\n\tcode: string;\n\tlabel: string;\n}\n\n/** Validate a locale code against the Intl.Locale API (BCP 47). */\nfunction validateLocaleCode(code: string): string | void {\n\ttry {\n\t\treturn new Intl.Locale(code).baseName;\n\t} catch {\n\t\tif (import.meta.env.DEV) {\n\t\t\tthrow new Error(`Invalid locale code: \"${code}\"`);\n\t\t}\n\t}\n}\n\n/** Available locales — extend this list as translations are added. */\nexport const SUPPORTED_LOCALES: SupportedLocale[] = [\n\t/* First item is the default locale */\n\t{ code: \"en\", label: \"English\" },\n\t{ code: \"de\", label: \"Deutsch\" },\n].filter((l) => validateLocaleCode(l.code));\n\nexport const SUPPORTED_LOCALE_CODES = new Set(SUPPORTED_LOCALES.map((l) => l.code));\n\nexport const DEFAULT_LOCALE = SUPPORTED_LOCALES[0]!.code;\n\nconst LOCALE_LABELS = new Map(SUPPORTED_LOCALES.map((l) => [l.code, l.label]));\n\n/** Get a display label for a locale code, falling back to uppercase code. */\nexport function getLocaleLabel(code: string): string {\n\treturn LOCALE_LABELS.get(code) ?? code.toUpperCase();\n}\n\nconst LOCALE_COOKIE_RE = /(?:^|;\\s*)emdash-locale=([^;]+)/;\n\n/**\n * Resolve the admin locale from a Request.\n * Priority: emdash-locale cookie → Accept-Language → DEFAULT_LOCALE.\n */\nexport function resolveLocale(request: Request): string {\n\tconst cookieHeader = request.headers.get(\"cookie\") ?? \"\";\n\tconst cookieMatch = cookieHeader.match(LOCALE_COOKIE_RE);\n\tconst cookieLocale = cookieMatch?.[1]?.trim() ?? \"\";\n\n\tif (SUPPORTED_LOCALE_CODES.has(cookieLocale)) return cookieLocale;\n\n\tconst acceptLang = request.headers.get(\"accept-language\") ?? \"\";\n\tfor (const entry of acceptLang.split(\",\")) {\n\t\tconst tag = entry.split(\";\")[0]!.trim().split(\"-\")[0]!.toLowerCase();\n\t\tif (SUPPORTED_LOCALE_CODES.has(tag)) return tag;\n\t}\n\n\treturn DEFAULT_LOCALE;\n}\n","import { useLingui } from \"@lingui/react\";\nimport * as React from \"react\";\n\nimport { SUPPORTED_LOCALE_CODES } from \"./config.js\";\n\nfunction setCookie(code: string) {\n\tconst secure = window.location.protocol === \"https:\" ? \"; Secure\" : \"\";\n\tdocument.cookie = `emdash-locale=${code}; Path=/_emdash; SameSite=Lax; Max-Age=31536000${secure}`;\n}\n\n/**\n * Get the current locale and a function to switch locales.\n * Loads the new catalog dynamically and sets a cookie for server-side persistence.\n */\nexport function useLocale() {\n\tconst { i18n } = useLingui();\n\tconst [locale, setLocaleState] = React.useState(i18n.locale);\n\n\tconst setLocale = React.useCallback(\n\t\t(code: string) => {\n\t\t\tif (code === i18n.locale || !SUPPORTED_LOCALE_CODES.has(code)) return;\n\t\t\tsetCookie(code);\n\t\t\tvoid import(`./${code}/messages.mjs`)\n\t\t\t\t.then(({ messages }) => i18n.loadAndActivate({ locale: code, messages }))\n\t\t\t\t.catch(() => {\n\t\t\t\t\t// Revert cookie on failure so the user isn't stuck\n\t\t\t\t\tsetCookie(i18n.locale);\n\t\t\t\t});\n\t\t},\n\t\t[i18n],\n\t);\n\n\t// Subscribe to i18n change events to trigger re-renders\n\tReact.useEffect(() => {\n\t\tconst unsubscribe = i18n.on(\"change\", () => {\n\t\t\tsetLocaleState(i18n.locale);\n\t\t});\n\t\treturn unsubscribe;\n\t}, [i18n]);\n\n\treturn { locale, setLocale };\n}\n"],"mappings":";;;;;AAYA,SAAS,mBAAmB,MAA6B;AACxD,KAAI;AACH,SAAO,IAAI,KAAK,OAAO,KAAK,CAAC;SACtB;AACP,MAAI,OAAO,KAAK,IAAI,IACnB,OAAM,IAAI,MAAM,yBAAyB,KAAK,GAAG;;;;AAMpD,MAAa,oBAAuC,CAEnD;CAAE,MAAM;CAAM,OAAO;CAAW,EAChC;CAAE,MAAM;CAAM,OAAO;CAAW,CAChC,CAAC,QAAQ,MAAM,mBAAmB,EAAE,KAAK,CAAC;AAE3C,MAAa,yBAAyB,IAAI,IAAI,kBAAkB,KAAK,MAAM,EAAE,KAAK,CAAC;AAEnF,MAAa,iBAAiB,kBAAkB,GAAI;AAEpD,MAAM,gBAAgB,IAAI,IAAI,kBAAkB,KAAK,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;;AAG9E,SAAgB,eAAe,MAAsB;AACpD,QAAO,cAAc,IAAI,KAAK,IAAI,KAAK,aAAa;;AAGrD,MAAM,mBAAmB;;;;;AAMzB,SAAgB,cAAc,SAA0B;CAGvD,MAAM,gBAFe,QAAQ,QAAQ,IAAI,SAAS,IAAI,IACrB,MAAM,iBAAiB,GACrB,IAAI,MAAM,IAAI;AAEjD,KAAI,uBAAuB,IAAI,aAAa,CAAE,QAAO;CAErD,MAAM,aAAa,QAAQ,QAAQ,IAAI,kBAAkB,IAAI;AAC7D,MAAK,MAAM,SAAS,WAAW,MAAM,IAAI,EAAE;EAC1C,MAAM,MAAM,MAAM,MAAM,IAAI,CAAC,GAAI,MAAM,CAAC,MAAM,IAAI,CAAC,GAAI,aAAa;AACpE,MAAI,uBAAuB,IAAI,IAAI,CAAE,QAAO;;AAG7C,QAAO;;;;;ACxDR,SAAS,UAAA,MAAwB;;AAEjC,UAAS,SAAU,iBAAc,KAAA,iDAAA;;;;;;AAOjC,SAAgB,YAAS;CACvB,MAAA,EACF,SACM,WAAW;CAChB,MAAO,CAAA,QAAQ,kBAAkB,MAAM,SAAS,KAAK,OAAO;;AAE5D,MAAM,SAAU,KAAE,UAAM,CAAA,uBAAW,IAAA,KAAA,CAAA;AACjC,YAAM,KAAU;AAChB,EAAI,OAAS,KAAK,KAAA,gBAAW,MAAA,EAC7B,eACK,KAAO,gBAAW;GACrB,QAAQ;GACR;GACA,CAAC,CAAC,CAAC,YAAO;AAET,aAAA,KAAA,OAAA;IACH;IACA,CAAA,KAAK,CAAA;AAGN,OAAE,gBAAkB;AAIlB,SAHmB,KAAA,GAAA,gBAAA;AACrB,kBAAmB,KAAK,OAAK;IAC5B;IAED,CAAA,KAAO,CAAA;AACP,QAAO;;EAER;EACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emdash-cms/admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Admin UI for EmDash CMS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
14
|
},
|
|
15
|
-
"./styles.css": "./dist/styles.css"
|
|
15
|
+
"./styles.css": "./dist/styles.css",
|
|
16
|
+
"./locales": {
|
|
17
|
+
"types": "./dist/locales/index.d.ts",
|
|
18
|
+
"default": "./dist/locales/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./locales/*": "./dist/locales/*"
|
|
16
21
|
},
|
|
17
22
|
"dependencies": {
|
|
18
23
|
"@cloudflare/kumo": "^1.16.0",
|
|
@@ -20,6 +25,8 @@
|
|
|
20
25
|
"@dnd-kit/sortable": "^10.0.0",
|
|
21
26
|
"@dnd-kit/utilities": "^3.2.2",
|
|
22
27
|
"@floating-ui/react": "^0.27.16",
|
|
28
|
+
"@lingui/core": "^5.9.4",
|
|
29
|
+
"@lingui/react": "^5.9.4",
|
|
23
30
|
"@phosphor-icons/react": "^2.1.10",
|
|
24
31
|
"@tanstack/react-query": "5.90.21",
|
|
25
32
|
"@tanstack/react-router": "1.163.2",
|
|
@@ -46,10 +53,14 @@
|
|
|
46
53
|
"react-dom": "19.2.4",
|
|
47
54
|
"react-hotkeys-hook": "^5.2.4",
|
|
48
55
|
"tailwind-merge": "^3.3.0",
|
|
49
|
-
"@emdash-cms/blocks": "0.
|
|
56
|
+
"@emdash-cms/blocks": "0.3.0"
|
|
50
57
|
},
|
|
51
58
|
"devDependencies": {
|
|
52
59
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
60
|
+
"@babel/core": "^7.29.0",
|
|
61
|
+
"@lingui/babel-plugin-lingui-macro": "^5.9.4",
|
|
62
|
+
"@lingui/cli": "^5.9.4",
|
|
63
|
+
"@lingui/macro": "^5.9.4",
|
|
53
64
|
"@tailwindcss/cli": "^4.1.10",
|
|
54
65
|
"@tailwindcss/typography": "^0.5.19",
|
|
55
66
|
"@testing-library/react": "^16.3.0",
|
|
@@ -87,10 +98,13 @@
|
|
|
87
98
|
"author": "Matt Kane",
|
|
88
99
|
"license": "MIT",
|
|
89
100
|
"scripts": {
|
|
90
|
-
"build": "tsdown && npx @tailwindcss/cli -i src/styles.css -o dist/styles.css --minify",
|
|
101
|
+
"build": "node --run locale:compile && tsdown && node --run locale:copy && npx @tailwindcss/cli -i src/styles.css -o dist/styles.css --minify",
|
|
91
102
|
"dev": "tsdown src/index.ts --format esm --dts --watch",
|
|
92
103
|
"check": "publint && attw --pack --ignore-rules=cjs-resolves-to-esm --ignore-rules=no-resolution",
|
|
93
104
|
"test": "vitest",
|
|
94
|
-
"typecheck": "tsgo --noEmit"
|
|
105
|
+
"typecheck": "tsgo --noEmit",
|
|
106
|
+
"locale:compile": "lingui compile --namespace es",
|
|
107
|
+
"locale:copy": "node ./scripts/copy-locales.js",
|
|
108
|
+
"locale:extract": "lingui extract --clean"
|
|
95
109
|
}
|
|
96
110
|
}
|