@applocale/react 0.0.0-stage → 0.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/chunk-SOQNEZ4L.js +99 -0
- package/dist/chunk-SOQNEZ4L.js.map +1 -0
- package/dist/index.cjs +200 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +71 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/visual-editor.cjs +125 -0
- package/dist/visual-editor.cjs.map +1 -0
- package/dist/visual-editor.d.cts +30 -0
- package/dist/visual-editor.d.ts +30 -0
- package/dist/visual-editor.js +11 -0
- package/dist/visual-editor.js.map +1 -0
- package/package.json +60 -4
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AppLocale React SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Install with `npm install @applocale/react@beta`. React 18+ is a peer dependency. Wrap the app in `LingoProvider` with `projectId`, `cdnBaseUrl` and `sourceLocale`. `LocaleProvider` is a compatible alias. `useT()` returns the translation function; `useLocale()` provides active locale, loading/error state and locale changes. Approved empty translations remain empty; missing keys fall back to the key name.
|
|
4
|
+
|
|
5
|
+
For authenticated staging sites, export `initVisualEditor({ projectId, apiKey, apiBaseUrl, defaultLocale })`. Mark text elements with `data-locale-key` and optional `data-locale-namespace`. Use an expiring Write-scoped key and call the returned editor's `destroy()` on unmount. Browser-visible keys can be read by site users; revoke them when review ends. See the repository deployment guide for complete integration and release instructions.
|
|
6
|
+
|
|
7
|
+
### Loading and retry feedback
|
|
8
|
+
|
|
9
|
+
Use `useTranslationStatus()` inside `LingoProvider` (or `LocaleProvider`) to show delivery feedback. It returns `{ status, error, retry }`, where `status` is `loading`, `ready`, or `error`. Call `retry()` after a failed request. A language change immediately clears the previous dictionary while the new locale loads; `t()` falls back to the key until it is ready. Apps can supply readable fallback copy during this interval. Intentional empty translations remain empty.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/visual-editor.ts
|
|
2
|
+
var AppLocaleVisualEditor = class {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.active = false;
|
|
5
|
+
this.destroyed = false;
|
|
6
|
+
this.pending = false;
|
|
7
|
+
this.toggle = () => {
|
|
8
|
+
if (this.destroyed) return;
|
|
9
|
+
this.active = !this.active;
|
|
10
|
+
this.button.textContent = this.active ? "Disable Visual Editor" : "Enable Visual Editor";
|
|
11
|
+
this.button.setAttribute("aria-pressed", String(this.active));
|
|
12
|
+
if (this.active) {
|
|
13
|
+
document.body.addEventListener("mouseover", this.over);
|
|
14
|
+
document.body.addEventListener("mouseout", this.out);
|
|
15
|
+
document.body.addEventListener("click", this.click, true);
|
|
16
|
+
} else this.detach();
|
|
17
|
+
};
|
|
18
|
+
this.over = (event) => {
|
|
19
|
+
this.target(event)?.classList.add("locale-edge-hover");
|
|
20
|
+
};
|
|
21
|
+
this.out = (event) => {
|
|
22
|
+
this.target(event)?.classList.remove("locale-edge-hover");
|
|
23
|
+
};
|
|
24
|
+
this.click = async (event) => {
|
|
25
|
+
const target = this.target(event);
|
|
26
|
+
if (!this.active || !target) return;
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
event.stopPropagation();
|
|
29
|
+
if (this.pending) return;
|
|
30
|
+
const keyName = target.getAttribute("data-locale-key");
|
|
31
|
+
if (!keyName?.trim()) return;
|
|
32
|
+
const current = target.textContent ?? "";
|
|
33
|
+
const value = window.prompt(`Edit translation: ${keyName}`, current);
|
|
34
|
+
if (value === null || value === current) return;
|
|
35
|
+
const base = (this.config.apiBaseUrl ?? "https://api.applocale.com/api").replace(/\/$/, "");
|
|
36
|
+
try {
|
|
37
|
+
const url = new URL(base);
|
|
38
|
+
const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
39
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && local)) throw new Error("Use an HTTPS API URL or a local development server.");
|
|
40
|
+
this.pending = true;
|
|
41
|
+
this.status.textContent = "Saving translation\u2026";
|
|
42
|
+
const response = await fetch(`${base}/projects/${encodeURIComponent(this.config.projectId)}/keys/visual-sync`, { method: "POST", headers: { "Content-Type": "application/json", "X-AppLocale-Key": this.config.apiKey }, body: JSON.stringify({ keyName, value, localeCode: this.config.defaultLocale ?? "en-US", namespace: target.getAttribute("data-locale-namespace") ?? this.config.namespace ?? "default" }) });
|
|
43
|
+
if (!response.ok) throw new Error(`Save failed (HTTP ${response.status}). Check the project key, scope and locale.`);
|
|
44
|
+
if (!this.destroyed) {
|
|
45
|
+
target.textContent = value;
|
|
46
|
+
this.status.textContent = "Translation saved.";
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (!this.destroyed) this.status.textContent = error instanceof Error ? error.message : "Unable to save translation. Please try again.";
|
|
50
|
+
} finally {
|
|
51
|
+
this.pending = false;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
this.config = config;
|
|
55
|
+
this.style = document.createElement("style");
|
|
56
|
+
this.style.textContent = ".locale-edge-hover{outline:2px dashed #ffae50!important;cursor:pointer!important}.locale-edge-btn{position:fixed;bottom:24px;right:24px;background:#292724;color:#fff4df;border:1px solid #c48e52;padding:12px 20px;border-radius:16px;font:600 14px system-ui;box-shadow:0 8px 32px #0004;z-index:2147483646}.locale-edge-status{position:fixed;right:24px;bottom:80px;max-width:320px;background:#292724;color:#fff4df;padding:12px;border-radius:12px;font:14px system-ui;z-index:2147483646}.locale-edge-status:empty{display:none}";
|
|
57
|
+
document.head.appendChild(this.style);
|
|
58
|
+
this.status = document.createElement("div");
|
|
59
|
+
this.status.className = "locale-edge-status";
|
|
60
|
+
this.status.setAttribute("role", "status");
|
|
61
|
+
this.status.setAttribute("aria-live", "polite");
|
|
62
|
+
document.body.appendChild(this.status);
|
|
63
|
+
this.button = document.createElement("button");
|
|
64
|
+
this.button.type = "button";
|
|
65
|
+
this.button.className = "locale-edge-btn";
|
|
66
|
+
this.button.textContent = "Enable Visual Editor";
|
|
67
|
+
this.button.setAttribute("aria-pressed", "false");
|
|
68
|
+
this.button.addEventListener("click", this.toggle);
|
|
69
|
+
document.body.appendChild(this.button);
|
|
70
|
+
}
|
|
71
|
+
target(event) {
|
|
72
|
+
return event.target instanceof Element ? event.target.closest("[data-locale-key]") : null;
|
|
73
|
+
}
|
|
74
|
+
detach() {
|
|
75
|
+
document.body.removeEventListener("mouseover", this.over);
|
|
76
|
+
document.body.removeEventListener("mouseout", this.out);
|
|
77
|
+
document.body.removeEventListener("click", this.click, true);
|
|
78
|
+
document.querySelectorAll(".locale-edge-hover").forEach((element) => element.classList.remove("locale-edge-hover"));
|
|
79
|
+
}
|
|
80
|
+
/** Call when unmounting the staging app/editor. Removes every owned element and listener. */
|
|
81
|
+
destroy() {
|
|
82
|
+
this.destroyed = true;
|
|
83
|
+
this.active = false;
|
|
84
|
+
this.detach();
|
|
85
|
+
this.button.removeEventListener("click", this.toggle);
|
|
86
|
+
this.button.remove();
|
|
87
|
+
this.status.remove();
|
|
88
|
+
this.style.remove();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var initAppLocale = (config) => new AppLocaleVisualEditor(config);
|
|
92
|
+
var initVisualEditor = initAppLocale;
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
AppLocaleVisualEditor,
|
|
96
|
+
initAppLocale,
|
|
97
|
+
initVisualEditor
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=chunk-SOQNEZ4L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/visual-editor.ts"],"sourcesContent":["/** Staging-only in-context editor. Give it an expiring project-scoped write key. */\nexport interface AppLocaleConfig {\n apiKey: string;\n projectId: string;\n apiBaseUrl?: string;\n defaultLocale?: string;\n namespace?: string;\n}\nexport class AppLocaleVisualEditor {\n private active = false;\n private destroyed = false;\n private pending = false;\n private readonly button: HTMLButtonElement;\n private readonly status: HTMLDivElement;\n private readonly style: HTMLStyleElement;\n private readonly config: AppLocaleConfig;\n constructor(config: AppLocaleConfig) {\n this.config = config;\n this.style = document.createElement('style');\n this.style.textContent = '.locale-edge-hover{outline:2px dashed #ffae50!important;cursor:pointer!important}.locale-edge-btn{position:fixed;bottom:24px;right:24px;background:#292724;color:#fff4df;border:1px solid #c48e52;padding:12px 20px;border-radius:16px;font:600 14px system-ui;box-shadow:0 8px 32px #0004;z-index:2147483646}.locale-edge-status{position:fixed;right:24px;bottom:80px;max-width:320px;background:#292724;color:#fff4df;padding:12px;border-radius:12px;font:14px system-ui;z-index:2147483646}.locale-edge-status:empty{display:none}';\n document.head.appendChild(this.style);\n this.status = document.createElement('div'); this.status.className = 'locale-edge-status'; this.status.setAttribute('role', 'status'); this.status.setAttribute('aria-live', 'polite'); document.body.appendChild(this.status);\n this.button = document.createElement('button'); this.button.type = 'button'; this.button.className = 'locale-edge-btn'; this.button.textContent = 'Enable Visual Editor'; this.button.setAttribute('aria-pressed', 'false'); this.button.addEventListener('click', this.toggle); document.body.appendChild(this.button);\n }\n private toggle = () => {\n if (this.destroyed) return;\n this.active = !this.active; this.button.textContent = this.active ? 'Disable Visual Editor' : 'Enable Visual Editor'; this.button.setAttribute('aria-pressed', String(this.active));\n if (this.active) { document.body.addEventListener('mouseover', this.over); document.body.addEventListener('mouseout', this.out); document.body.addEventListener('click', this.click, true); }\n else this.detach();\n };\n private target(event: MouseEvent): HTMLElement | null {\n return event.target instanceof Element ? event.target.closest<HTMLElement>('[data-locale-key]') : null;\n }\n private over = (event: MouseEvent) => { this.target(event)?.classList.add('locale-edge-hover'); };\n private out = (event: MouseEvent) => { this.target(event)?.classList.remove('locale-edge-hover'); };\n private click = async (event: MouseEvent) => {\n const target = this.target(event); if (!this.active || !target) return;\n event.preventDefault(); event.stopPropagation(); if (this.pending) return;\n const keyName = target.getAttribute('data-locale-key'); if (!keyName?.trim()) return;\n const current = target.textContent ?? ''; const value = window.prompt(`Edit translation: ${keyName}`, current);\n if (value === null || value === current) return;\n const base = (this.config.apiBaseUrl ?? 'https://api.applocale.com/api').replace(/\\/$/, '');\n try {\n const url = new URL(base); const local = ['localhost','127.0.0.1','[::1]'].includes(url.hostname);\n if (url.protocol !== 'https:' && !(url.protocol === 'http:' && local)) throw new Error('Use an HTTPS API URL or a local development server.');\n this.pending = true; this.status.textContent = 'Saving translation…';\n const response = await fetch(`${base}/projects/${encodeURIComponent(this.config.projectId)}/keys/visual-sync`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-AppLocale-Key': this.config.apiKey }, body: JSON.stringify({ keyName, value, localeCode: this.config.defaultLocale ?? 'en-US', namespace: target.getAttribute('data-locale-namespace') ?? this.config.namespace ?? 'default' }) });\n if (!response.ok) throw new Error(`Save failed (HTTP ${response.status}). Check the project key, scope and locale.`);\n if (!this.destroyed) { target.textContent = value; this.status.textContent = 'Translation saved.'; }\n } catch (error) { if (!this.destroyed) this.status.textContent = error instanceof Error ? error.message : 'Unable to save translation. Please try again.'; }\n finally { this.pending = false; }\n };\n private detach() {\n document.body.removeEventListener('mouseover', this.over); document.body.removeEventListener('mouseout', this.out); document.body.removeEventListener('click', this.click, true);\n document.querySelectorAll('.locale-edge-hover').forEach(element => element.classList.remove('locale-edge-hover'));\n }\n /** Call when unmounting the staging app/editor. Removes every owned element and listener. */\n destroy() { this.destroyed = true; this.active = false; this.detach(); this.button.removeEventListener('click', this.toggle); this.button.remove(); this.status.remove(); this.style.remove(); }\n}\nexport const initAppLocale = (config: AppLocaleConfig) => new AppLocaleVisualEditor(config);\nexport const initVisualEditor = initAppLocale;\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AppLocaleVisualEditor: () => AppLocaleVisualEditor,
|
|
24
|
+
DEFAULT_CDN_BASE_URL: () => DEFAULT_CDN_BASE_URL,
|
|
25
|
+
LingoProvider: () => LingoProvider,
|
|
26
|
+
LocaleProvider: () => LingoProvider,
|
|
27
|
+
initAppLocale: () => initAppLocale,
|
|
28
|
+
initVisualEditor: () => initVisualEditor,
|
|
29
|
+
useLocale: () => useLocale,
|
|
30
|
+
useT: () => useT,
|
|
31
|
+
useTranslationStatus: () => useTranslationStatus
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(index_exports);
|
|
34
|
+
|
|
35
|
+
// src/LingoProvider.tsx
|
|
36
|
+
var import_react2 = require("react");
|
|
37
|
+
|
|
38
|
+
// src/context.ts
|
|
39
|
+
var import_react = require("react");
|
|
40
|
+
var LingoContext = (0, import_react.createContext)(null);
|
|
41
|
+
|
|
42
|
+
// src/types.ts
|
|
43
|
+
var DEFAULT_CDN_BASE_URL = "https://cdn.applocale.com";
|
|
44
|
+
|
|
45
|
+
// src/LingoProvider.tsx
|
|
46
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
47
|
+
var LingoProvider = ({ projectId, defaultLocale = "en-US", cdnBaseUrl = DEFAULT_CDN_BASE_URL, sourceLocale, children }) => {
|
|
48
|
+
const [locale, setLocale] = (0, import_react2.useState)(defaultLocale);
|
|
49
|
+
const [attempt, setAttempt] = (0, import_react2.useState)(0);
|
|
50
|
+
const edgeUrl = sourceLocale ? `${cdnBaseUrl}/${projectId}/${locale}.json?fallback=${encodeURIComponent(sourceLocale)}` : `${cdnBaseUrl}/${projectId}/${locale}.json`;
|
|
51
|
+
const request = JSON.stringify([edgeUrl, attempt]);
|
|
52
|
+
const [result, setResult] = (0, import_react2.useState)({ request: "", status: "loading", error: null, translations: {} });
|
|
53
|
+
const current = result.request === request ? result : { status: "loading", error: null, translations: {} };
|
|
54
|
+
(0, import_react2.useEffect)(() => {
|
|
55
|
+
let cancelled = false;
|
|
56
|
+
fetch(edgeUrl).then(async (response) => {
|
|
57
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
58
|
+
const translations = await response.json();
|
|
59
|
+
if (!cancelled) setResult({ request, status: "ready", error: null, translations });
|
|
60
|
+
}).catch((error) => {
|
|
61
|
+
if (cancelled) return;
|
|
62
|
+
console.warn(`[AppLocale SDK] Failed to load locale ${locale}`, error);
|
|
63
|
+
setResult({ request, status: "error", error: `Could not load translations for ${locale}. Check the project is published, then try again.`, translations: {} });
|
|
64
|
+
});
|
|
65
|
+
return () => {
|
|
66
|
+
cancelled = true;
|
|
67
|
+
};
|
|
68
|
+
}, [edgeUrl, locale, request]);
|
|
69
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LingoContext.Provider, { value: {
|
|
70
|
+
t: (key) => current.translations[key] ?? key,
|
|
71
|
+
locale,
|
|
72
|
+
setLocale,
|
|
73
|
+
delivery: { status: current.status, error: current.error, retry: () => setAttempt((value) => value + 1) }
|
|
74
|
+
}, children });
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// src/hooks.ts
|
|
78
|
+
var import_react3 = require("react");
|
|
79
|
+
function useLingoContext() {
|
|
80
|
+
const context = (0, import_react3.useContext)(LingoContext);
|
|
81
|
+
if (!context) {
|
|
82
|
+
throw new Error("AppLocale hooks must be used within a <LingoProvider>; must be used within a <LocaleProvider> when using the compatibility API");
|
|
83
|
+
}
|
|
84
|
+
return context;
|
|
85
|
+
}
|
|
86
|
+
var useT = () => {
|
|
87
|
+
return useLingoContext().t;
|
|
88
|
+
};
|
|
89
|
+
var useLocale = () => {
|
|
90
|
+
const { locale, setLocale } = useLingoContext();
|
|
91
|
+
return [locale, setLocale];
|
|
92
|
+
};
|
|
93
|
+
var useTranslationStatus = () => useLingoContext().delivery ?? { status: "ready", error: null, retry: () => {
|
|
94
|
+
} };
|
|
95
|
+
|
|
96
|
+
// src/visual-editor.ts
|
|
97
|
+
var AppLocaleVisualEditor = class {
|
|
98
|
+
constructor(config) {
|
|
99
|
+
this.active = false;
|
|
100
|
+
this.destroyed = false;
|
|
101
|
+
this.pending = false;
|
|
102
|
+
this.toggle = () => {
|
|
103
|
+
if (this.destroyed) return;
|
|
104
|
+
this.active = !this.active;
|
|
105
|
+
this.button.textContent = this.active ? "Disable Visual Editor" : "Enable Visual Editor";
|
|
106
|
+
this.button.setAttribute("aria-pressed", String(this.active));
|
|
107
|
+
if (this.active) {
|
|
108
|
+
document.body.addEventListener("mouseover", this.over);
|
|
109
|
+
document.body.addEventListener("mouseout", this.out);
|
|
110
|
+
document.body.addEventListener("click", this.click, true);
|
|
111
|
+
} else this.detach();
|
|
112
|
+
};
|
|
113
|
+
this.over = (event) => {
|
|
114
|
+
this.target(event)?.classList.add("locale-edge-hover");
|
|
115
|
+
};
|
|
116
|
+
this.out = (event) => {
|
|
117
|
+
this.target(event)?.classList.remove("locale-edge-hover");
|
|
118
|
+
};
|
|
119
|
+
this.click = async (event) => {
|
|
120
|
+
const target = this.target(event);
|
|
121
|
+
if (!this.active || !target) return;
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
event.stopPropagation();
|
|
124
|
+
if (this.pending) return;
|
|
125
|
+
const keyName = target.getAttribute("data-locale-key");
|
|
126
|
+
if (!keyName?.trim()) return;
|
|
127
|
+
const current = target.textContent ?? "";
|
|
128
|
+
const value = window.prompt(`Edit translation: ${keyName}`, current);
|
|
129
|
+
if (value === null || value === current) return;
|
|
130
|
+
const base = (this.config.apiBaseUrl ?? "https://api.applocale.com/api").replace(/\/$/, "");
|
|
131
|
+
try {
|
|
132
|
+
const url = new URL(base);
|
|
133
|
+
const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
134
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && local)) throw new Error("Use an HTTPS API URL or a local development server.");
|
|
135
|
+
this.pending = true;
|
|
136
|
+
this.status.textContent = "Saving translation\u2026";
|
|
137
|
+
const response = await fetch(`${base}/projects/${encodeURIComponent(this.config.projectId)}/keys/visual-sync`, { method: "POST", headers: { "Content-Type": "application/json", "X-AppLocale-Key": this.config.apiKey }, body: JSON.stringify({ keyName, value, localeCode: this.config.defaultLocale ?? "en-US", namespace: target.getAttribute("data-locale-namespace") ?? this.config.namespace ?? "default" }) });
|
|
138
|
+
if (!response.ok) throw new Error(`Save failed (HTTP ${response.status}). Check the project key, scope and locale.`);
|
|
139
|
+
if (!this.destroyed) {
|
|
140
|
+
target.textContent = value;
|
|
141
|
+
this.status.textContent = "Translation saved.";
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (!this.destroyed) this.status.textContent = error instanceof Error ? error.message : "Unable to save translation. Please try again.";
|
|
145
|
+
} finally {
|
|
146
|
+
this.pending = false;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
this.config = config;
|
|
150
|
+
this.style = document.createElement("style");
|
|
151
|
+
this.style.textContent = ".locale-edge-hover{outline:2px dashed #ffae50!important;cursor:pointer!important}.locale-edge-btn{position:fixed;bottom:24px;right:24px;background:#292724;color:#fff4df;border:1px solid #c48e52;padding:12px 20px;border-radius:16px;font:600 14px system-ui;box-shadow:0 8px 32px #0004;z-index:2147483646}.locale-edge-status{position:fixed;right:24px;bottom:80px;max-width:320px;background:#292724;color:#fff4df;padding:12px;border-radius:12px;font:14px system-ui;z-index:2147483646}.locale-edge-status:empty{display:none}";
|
|
152
|
+
document.head.appendChild(this.style);
|
|
153
|
+
this.status = document.createElement("div");
|
|
154
|
+
this.status.className = "locale-edge-status";
|
|
155
|
+
this.status.setAttribute("role", "status");
|
|
156
|
+
this.status.setAttribute("aria-live", "polite");
|
|
157
|
+
document.body.appendChild(this.status);
|
|
158
|
+
this.button = document.createElement("button");
|
|
159
|
+
this.button.type = "button";
|
|
160
|
+
this.button.className = "locale-edge-btn";
|
|
161
|
+
this.button.textContent = "Enable Visual Editor";
|
|
162
|
+
this.button.setAttribute("aria-pressed", "false");
|
|
163
|
+
this.button.addEventListener("click", this.toggle);
|
|
164
|
+
document.body.appendChild(this.button);
|
|
165
|
+
}
|
|
166
|
+
target(event) {
|
|
167
|
+
return event.target instanceof Element ? event.target.closest("[data-locale-key]") : null;
|
|
168
|
+
}
|
|
169
|
+
detach() {
|
|
170
|
+
document.body.removeEventListener("mouseover", this.over);
|
|
171
|
+
document.body.removeEventListener("mouseout", this.out);
|
|
172
|
+
document.body.removeEventListener("click", this.click, true);
|
|
173
|
+
document.querySelectorAll(".locale-edge-hover").forEach((element) => element.classList.remove("locale-edge-hover"));
|
|
174
|
+
}
|
|
175
|
+
/** Call when unmounting the staging app/editor. Removes every owned element and listener. */
|
|
176
|
+
destroy() {
|
|
177
|
+
this.destroyed = true;
|
|
178
|
+
this.active = false;
|
|
179
|
+
this.detach();
|
|
180
|
+
this.button.removeEventListener("click", this.toggle);
|
|
181
|
+
this.button.remove();
|
|
182
|
+
this.status.remove();
|
|
183
|
+
this.style.remove();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var initAppLocale = (config) => new AppLocaleVisualEditor(config);
|
|
187
|
+
var initVisualEditor = initAppLocale;
|
|
188
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
189
|
+
0 && (module.exports = {
|
|
190
|
+
AppLocaleVisualEditor,
|
|
191
|
+
DEFAULT_CDN_BASE_URL,
|
|
192
|
+
LingoProvider,
|
|
193
|
+
LocaleProvider,
|
|
194
|
+
initAppLocale,
|
|
195
|
+
initVisualEditor,
|
|
196
|
+
useLocale,
|
|
197
|
+
useT,
|
|
198
|
+
useTranslationStatus
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/LingoProvider.tsx","../src/context.ts","../src/types.ts","../src/hooks.ts","../src/visual-editor.ts"],"sourcesContent":["export { LingoProvider } from './LingoProvider';\nexport { useT, useLocale, useTranslationStatus } from './hooks';\nexport { DEFAULT_CDN_BASE_URL } from './types';\nexport type { LingoProviderProps, LingoContextValue, TranslationDelivery } from './types';\nexport { LocaleProvider } from './LocaleProvider';\nexport type { LocaleProviderProps, LocaleContextValue } from './types';\nexport { AppLocaleVisualEditor, initAppLocale, initVisualEditor } from './visual-editor';\nexport type { AppLocaleConfig } from './visual-editor';\n","import React, { useState, useEffect } from 'react';\nimport { LingoContext } from './context';\nimport { DEFAULT_CDN_BASE_URL } from './types';\nimport type { LingoProviderProps, TranslationDelivery } from './types';\n\ntype DeliveryResult = { request: string; status: TranslationDelivery['status']; error: string | null; translations: Record<string, string> };\n\nexport const LingoProvider = ({ projectId, defaultLocale = 'en-US', cdnBaseUrl = DEFAULT_CDN_BASE_URL, sourceLocale, children }: LingoProviderProps) => {\n const [locale, setLocale] = useState(defaultLocale);\n const [attempt, setAttempt] = useState(0);\n const edgeUrl = sourceLocale\n ? `${cdnBaseUrl}/${projectId}/${locale}.json?fallback=${encodeURIComponent(sourceLocale)}`\n : `${cdnBaseUrl}/${projectId}/${locale}.json`;\n const request = JSON.stringify([edgeUrl, attempt]);\n const [result, setResult] = useState<DeliveryResult>({ request: '', status: 'loading', error: null, translations: {} });\n // A new language immediately stops displaying the previous language's copy.\n const current: Pick<DeliveryResult, 'status' | 'error' | 'translations'> = result.request === request ? result : { status: 'loading', error: null, translations: {} };\n\n useEffect(() => {\n let cancelled = false;\n fetch(edgeUrl).then(async response => {\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n const translations = await response.json() as Record<string, string>;\n if (!cancelled) setResult({ request, status: 'ready', error: null, translations });\n }).catch(error => {\n if (cancelled) return;\n console.warn(`[AppLocale SDK] Failed to load locale ${locale}`, error);\n setResult({ request, status: 'error', error: `Could not load translations for ${locale}. Check the project is published, then try again.`, translations: {} });\n });\n return () => { cancelled = true; };\n }, [edgeUrl, locale, request]);\n\n return <LingoContext.Provider value={{\n t: key => current.translations[key] ?? key,\n locale,\n setLocale,\n delivery: { status: current.status, error: current.error, retry: () => setAttempt(value => value + 1) },\n }}>{children}</LingoContext.Provider>;\n};\n","import { createContext } from 'react';\nimport type { LingoContextValue } from './types';\n\nexport const LingoContext = createContext<LingoContextValue | null>(null);\n","import type { ReactNode } from 'react';\n\n/** Default CDN base URL used when none is provided to the LingoProvider. */\nexport const DEFAULT_CDN_BASE_URL = 'https://cdn.applocale.com';\n\n/**\n * Props accepted by the LingoProvider component.\n *\n * If `sourceLocale` is set, the SDK appends `?fallback=<sourceLocale>` to\n * fetch requests so the edge worker can fall back to the project's source\n * locale when a target locale is missing. Recommended to set to your\n * project's source locale code (e.g. `\"en-US\"`).\n */\nexport interface LingoProviderProps {\n /** The project’s public UUID from AppLocale (numeric database IDs are not accepted). */\n projectId: string;\n /** Initial locale to load. Defaults to \"en-US\". */\n defaultLocale?: string;\n /**\n * Base URL for the translations CDN / edge worker.\n * Translations are fetched from `${cdnBaseUrl}/${projectId}/${locale}.json`.\n * Defaults to {@link DEFAULT_CDN_BASE_URL}.\n */\n cdnBaseUrl?: string;\n /**\n * Optional. If set, the SDK appends `?fallback=<sourceLocale>` to fetch\n * requests so the edge worker can fall back to the project's source locale\n * when a target locale is missing. Recommended to set to your project's\n * source locale code (e.g. `\"en-US\"`). When omitted, no `?fallback=` query\n * parameter is appended and the edge worker uses its own default (`\"en\"`).\n */\n sourceLocale?: string;\n children: ReactNode;\n}\n\n/** Value exposed through the Lingo context. */\nexport interface LingoContextValue {\n /** Translate a key, falling back to the key itself when missing. */\n t: (key: string) => string;\n /** The currently active locale. */\n locale: string;\n /** Change the active locale (triggers a re-fetch of translations). */\n setLocale: (locale: string) => void;\n /** Delivery feedback for applications that show loading/retry controls. */\n delivery?: TranslationDelivery;\n}\n\nexport interface TranslationDelivery {\n status: 'loading' | 'ready' | 'error';\n error: string | null;\n retry: () => void;\n}\n\n/** AppLocale compatibility aliases; both provider names share the same context. */\nexport type LocaleProviderProps = LingoProviderProps;\nexport type LocaleContextValue = LingoContextValue;\n","import { useContext } from 'react';\nimport { LingoContext } from './context';\n\nfunction useLingoContext() {\n const context = useContext(LingoContext);\n if (!context) {\n throw new Error('AppLocale hooks must be used within a <LingoProvider>; must be used within a <LocaleProvider> when using the compatibility API');\n }\n return context;\n}\n\n/**\n * Returns the translation function `t(key) => string`.\n * Falls back to the key name when a translation is missing.\n */\nexport const useT = () => {\n return useLingoContext().t;\n};\n\n/**\n * Returns the current locale and a setter to change it.\n * Changing the locale triggers a re-fetch of the translations.\n */\nexport const useLocale = (): [string, (locale: string) => void] => {\n const { locale, setLocale } = useLingoContext();\n return [locale, setLocale];\n};\n\n/** Loading and recovery feedback from the provider; existing t/locale APIs stay compatible. */\nexport const useTranslationStatus = () => useLingoContext().delivery ?? { status: 'ready' as const, error: null, retry: () => {} };\n","/** Staging-only in-context editor. Give it an expiring project-scoped write key. */\nexport interface AppLocaleConfig {\n apiKey: string;\n projectId: string;\n apiBaseUrl?: string;\n defaultLocale?: string;\n namespace?: string;\n}\nexport class AppLocaleVisualEditor {\n private active = false;\n private destroyed = false;\n private pending = false;\n private readonly button: HTMLButtonElement;\n private readonly status: HTMLDivElement;\n private readonly style: HTMLStyleElement;\n private readonly config: AppLocaleConfig;\n constructor(config: AppLocaleConfig) {\n this.config = config;\n this.style = document.createElement('style');\n this.style.textContent = '.locale-edge-hover{outline:2px dashed #ffae50!important;cursor:pointer!important}.locale-edge-btn{position:fixed;bottom:24px;right:24px;background:#292724;color:#fff4df;border:1px solid #c48e52;padding:12px 20px;border-radius:16px;font:600 14px system-ui;box-shadow:0 8px 32px #0004;z-index:2147483646}.locale-edge-status{position:fixed;right:24px;bottom:80px;max-width:320px;background:#292724;color:#fff4df;padding:12px;border-radius:12px;font:14px system-ui;z-index:2147483646}.locale-edge-status:empty{display:none}';\n document.head.appendChild(this.style);\n this.status = document.createElement('div'); this.status.className = 'locale-edge-status'; this.status.setAttribute('role', 'status'); this.status.setAttribute('aria-live', 'polite'); document.body.appendChild(this.status);\n this.button = document.createElement('button'); this.button.type = 'button'; this.button.className = 'locale-edge-btn'; this.button.textContent = 'Enable Visual Editor'; this.button.setAttribute('aria-pressed', 'false'); this.button.addEventListener('click', this.toggle); document.body.appendChild(this.button);\n }\n private toggle = () => {\n if (this.destroyed) return;\n this.active = !this.active; this.button.textContent = this.active ? 'Disable Visual Editor' : 'Enable Visual Editor'; this.button.setAttribute('aria-pressed', String(this.active));\n if (this.active) { document.body.addEventListener('mouseover', this.over); document.body.addEventListener('mouseout', this.out); document.body.addEventListener('click', this.click, true); }\n else this.detach();\n };\n private target(event: MouseEvent): HTMLElement | null {\n return event.target instanceof Element ? event.target.closest<HTMLElement>('[data-locale-key]') : null;\n }\n private over = (event: MouseEvent) => { this.target(event)?.classList.add('locale-edge-hover'); };\n private out = (event: MouseEvent) => { this.target(event)?.classList.remove('locale-edge-hover'); };\n private click = async (event: MouseEvent) => {\n const target = this.target(event); if (!this.active || !target) return;\n event.preventDefault(); event.stopPropagation(); if (this.pending) return;\n const keyName = target.getAttribute('data-locale-key'); if (!keyName?.trim()) return;\n const current = target.textContent ?? ''; const value = window.prompt(`Edit translation: ${keyName}`, current);\n if (value === null || value === current) return;\n const base = (this.config.apiBaseUrl ?? 'https://api.applocale.com/api').replace(/\\/$/, '');\n try {\n const url = new URL(base); const local = ['localhost','127.0.0.1','[::1]'].includes(url.hostname);\n if (url.protocol !== 'https:' && !(url.protocol === 'http:' && local)) throw new Error('Use an HTTPS API URL or a local development server.');\n this.pending = true; this.status.textContent = 'Saving translation…';\n const response = await fetch(`${base}/projects/${encodeURIComponent(this.config.projectId)}/keys/visual-sync`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-AppLocale-Key': this.config.apiKey }, body: JSON.stringify({ keyName, value, localeCode: this.config.defaultLocale ?? 'en-US', namespace: target.getAttribute('data-locale-namespace') ?? this.config.namespace ?? 'default' }) });\n if (!response.ok) throw new Error(`Save failed (HTTP ${response.status}). Check the project key, scope and locale.`);\n if (!this.destroyed) { target.textContent = value; this.status.textContent = 'Translation saved.'; }\n } catch (error) { if (!this.destroyed) this.status.textContent = error instanceof Error ? error.message : 'Unable to save translation. Please try again.'; }\n finally { this.pending = false; }\n };\n private detach() {\n document.body.removeEventListener('mouseover', this.over); document.body.removeEventListener('mouseout', this.out); document.body.removeEventListener('click', this.click, true);\n document.querySelectorAll('.locale-edge-hover').forEach(element => element.classList.remove('locale-edge-hover'));\n }\n /** Call when unmounting the staging app/editor. Removes every owned element and listener. */\n destroy() { this.destroyed = true; this.active = false; this.detach(); this.button.removeEventListener('click', this.toggle); this.button.remove(); this.status.remove(); this.style.remove(); }\n}\nexport const initAppLocale = (config: AppLocaleConfig) => new AppLocaleVisualEditor(config);\nexport const initVisualEditor = initAppLocale;\n"],"mappings":"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","names":["import_react","import_react"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
export { AppLocaleConfig, AppLocaleVisualEditor, initAppLocale, initVisualEditor } from './visual-editor.cjs';
|
|
4
|
+
|
|
5
|
+
/** Default CDN base URL used when none is provided to the LingoProvider. */
|
|
6
|
+
declare const DEFAULT_CDN_BASE_URL = "https://cdn.applocale.com";
|
|
7
|
+
/**
|
|
8
|
+
* Props accepted by the LingoProvider component.
|
|
9
|
+
*
|
|
10
|
+
* If `sourceLocale` is set, the SDK appends `?fallback=<sourceLocale>` to
|
|
11
|
+
* fetch requests so the edge worker can fall back to the project's source
|
|
12
|
+
* locale when a target locale is missing. Recommended to set to your
|
|
13
|
+
* project's source locale code (e.g. `"en-US"`).
|
|
14
|
+
*/
|
|
15
|
+
interface LingoProviderProps {
|
|
16
|
+
/** The project’s public UUID from AppLocale (numeric database IDs are not accepted). */
|
|
17
|
+
projectId: string;
|
|
18
|
+
/** Initial locale to load. Defaults to "en-US". */
|
|
19
|
+
defaultLocale?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Base URL for the translations CDN / edge worker.
|
|
22
|
+
* Translations are fetched from `${cdnBaseUrl}/${projectId}/${locale}.json`.
|
|
23
|
+
* Defaults to {@link DEFAULT_CDN_BASE_URL}.
|
|
24
|
+
*/
|
|
25
|
+
cdnBaseUrl?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Optional. If set, the SDK appends `?fallback=<sourceLocale>` to fetch
|
|
28
|
+
* requests so the edge worker can fall back to the project's source locale
|
|
29
|
+
* when a target locale is missing. Recommended to set to your project's
|
|
30
|
+
* source locale code (e.g. `"en-US"`). When omitted, no `?fallback=` query
|
|
31
|
+
* parameter is appended and the edge worker uses its own default (`"en"`).
|
|
32
|
+
*/
|
|
33
|
+
sourceLocale?: string;
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
}
|
|
36
|
+
/** Value exposed through the Lingo context. */
|
|
37
|
+
interface LingoContextValue {
|
|
38
|
+
/** Translate a key, falling back to the key itself when missing. */
|
|
39
|
+
t: (key: string) => string;
|
|
40
|
+
/** The currently active locale. */
|
|
41
|
+
locale: string;
|
|
42
|
+
/** Change the active locale (triggers a re-fetch of translations). */
|
|
43
|
+
setLocale: (locale: string) => void;
|
|
44
|
+
/** Delivery feedback for applications that show loading/retry controls. */
|
|
45
|
+
delivery?: TranslationDelivery;
|
|
46
|
+
}
|
|
47
|
+
interface TranslationDelivery {
|
|
48
|
+
status: 'loading' | 'ready' | 'error';
|
|
49
|
+
error: string | null;
|
|
50
|
+
retry: () => void;
|
|
51
|
+
}
|
|
52
|
+
/** AppLocale compatibility aliases; both provider names share the same context. */
|
|
53
|
+
type LocaleProviderProps = LingoProviderProps;
|
|
54
|
+
type LocaleContextValue = LingoContextValue;
|
|
55
|
+
|
|
56
|
+
declare const LingoProvider: ({ projectId, defaultLocale, cdnBaseUrl, sourceLocale, children }: LingoProviderProps) => react_jsx_runtime.JSX.Element;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns the translation function `t(key) => string`.
|
|
60
|
+
* Falls back to the key name when a translation is missing.
|
|
61
|
+
*/
|
|
62
|
+
declare const useT: () => (key: string) => string;
|
|
63
|
+
/**
|
|
64
|
+
* Returns the current locale and a setter to change it.
|
|
65
|
+
* Changing the locale triggers a re-fetch of the translations.
|
|
66
|
+
*/
|
|
67
|
+
declare const useLocale: () => [string, (locale: string) => void];
|
|
68
|
+
/** Loading and recovery feedback from the provider; existing t/locale APIs stay compatible. */
|
|
69
|
+
declare const useTranslationStatus: () => TranslationDelivery;
|
|
70
|
+
|
|
71
|
+
export { DEFAULT_CDN_BASE_URL, type LingoContextValue, LingoProvider, type LingoProviderProps, type LocaleContextValue, LingoProvider as LocaleProvider, type LocaleProviderProps, type TranslationDelivery, useLocale, useT, useTranslationStatus };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
export { AppLocaleConfig, AppLocaleVisualEditor, initAppLocale, initVisualEditor } from './visual-editor.js';
|
|
4
|
+
|
|
5
|
+
/** Default CDN base URL used when none is provided to the LingoProvider. */
|
|
6
|
+
declare const DEFAULT_CDN_BASE_URL = "https://cdn.applocale.com";
|
|
7
|
+
/**
|
|
8
|
+
* Props accepted by the LingoProvider component.
|
|
9
|
+
*
|
|
10
|
+
* If `sourceLocale` is set, the SDK appends `?fallback=<sourceLocale>` to
|
|
11
|
+
* fetch requests so the edge worker can fall back to the project's source
|
|
12
|
+
* locale when a target locale is missing. Recommended to set to your
|
|
13
|
+
* project's source locale code (e.g. `"en-US"`).
|
|
14
|
+
*/
|
|
15
|
+
interface LingoProviderProps {
|
|
16
|
+
/** The project’s public UUID from AppLocale (numeric database IDs are not accepted). */
|
|
17
|
+
projectId: string;
|
|
18
|
+
/** Initial locale to load. Defaults to "en-US". */
|
|
19
|
+
defaultLocale?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Base URL for the translations CDN / edge worker.
|
|
22
|
+
* Translations are fetched from `${cdnBaseUrl}/${projectId}/${locale}.json`.
|
|
23
|
+
* Defaults to {@link DEFAULT_CDN_BASE_URL}.
|
|
24
|
+
*/
|
|
25
|
+
cdnBaseUrl?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Optional. If set, the SDK appends `?fallback=<sourceLocale>` to fetch
|
|
28
|
+
* requests so the edge worker can fall back to the project's source locale
|
|
29
|
+
* when a target locale is missing. Recommended to set to your project's
|
|
30
|
+
* source locale code (e.g. `"en-US"`). When omitted, no `?fallback=` query
|
|
31
|
+
* parameter is appended and the edge worker uses its own default (`"en"`).
|
|
32
|
+
*/
|
|
33
|
+
sourceLocale?: string;
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
}
|
|
36
|
+
/** Value exposed through the Lingo context. */
|
|
37
|
+
interface LingoContextValue {
|
|
38
|
+
/** Translate a key, falling back to the key itself when missing. */
|
|
39
|
+
t: (key: string) => string;
|
|
40
|
+
/** The currently active locale. */
|
|
41
|
+
locale: string;
|
|
42
|
+
/** Change the active locale (triggers a re-fetch of translations). */
|
|
43
|
+
setLocale: (locale: string) => void;
|
|
44
|
+
/** Delivery feedback for applications that show loading/retry controls. */
|
|
45
|
+
delivery?: TranslationDelivery;
|
|
46
|
+
}
|
|
47
|
+
interface TranslationDelivery {
|
|
48
|
+
status: 'loading' | 'ready' | 'error';
|
|
49
|
+
error: string | null;
|
|
50
|
+
retry: () => void;
|
|
51
|
+
}
|
|
52
|
+
/** AppLocale compatibility aliases; both provider names share the same context. */
|
|
53
|
+
type LocaleProviderProps = LingoProviderProps;
|
|
54
|
+
type LocaleContextValue = LingoContextValue;
|
|
55
|
+
|
|
56
|
+
declare const LingoProvider: ({ projectId, defaultLocale, cdnBaseUrl, sourceLocale, children }: LingoProviderProps) => react_jsx_runtime.JSX.Element;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns the translation function `t(key) => string`.
|
|
60
|
+
* Falls back to the key name when a translation is missing.
|
|
61
|
+
*/
|
|
62
|
+
declare const useT: () => (key: string) => string;
|
|
63
|
+
/**
|
|
64
|
+
* Returns the current locale and a setter to change it.
|
|
65
|
+
* Changing the locale triggers a re-fetch of the translations.
|
|
66
|
+
*/
|
|
67
|
+
declare const useLocale: () => [string, (locale: string) => void];
|
|
68
|
+
/** Loading and recovery feedback from the provider; existing t/locale APIs stay compatible. */
|
|
69
|
+
declare const useTranslationStatus: () => TranslationDelivery;
|
|
70
|
+
|
|
71
|
+
export { DEFAULT_CDN_BASE_URL, type LingoContextValue, LingoProvider, type LingoProviderProps, type LocaleContextValue, LingoProvider as LocaleProvider, type LocaleProviderProps, type TranslationDelivery, useLocale, useT, useTranslationStatus };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppLocaleVisualEditor,
|
|
3
|
+
initAppLocale,
|
|
4
|
+
initVisualEditor
|
|
5
|
+
} from "./chunk-SOQNEZ4L.js";
|
|
6
|
+
|
|
7
|
+
// src/LingoProvider.tsx
|
|
8
|
+
import { useState, useEffect } from "react";
|
|
9
|
+
|
|
10
|
+
// src/context.ts
|
|
11
|
+
import { createContext } from "react";
|
|
12
|
+
var LingoContext = createContext(null);
|
|
13
|
+
|
|
14
|
+
// src/types.ts
|
|
15
|
+
var DEFAULT_CDN_BASE_URL = "https://cdn.applocale.com";
|
|
16
|
+
|
|
17
|
+
// src/LingoProvider.tsx
|
|
18
|
+
import { jsx } from "react/jsx-runtime";
|
|
19
|
+
var LingoProvider = ({ projectId, defaultLocale = "en-US", cdnBaseUrl = DEFAULT_CDN_BASE_URL, sourceLocale, children }) => {
|
|
20
|
+
const [locale, setLocale] = useState(defaultLocale);
|
|
21
|
+
const [attempt, setAttempt] = useState(0);
|
|
22
|
+
const edgeUrl = sourceLocale ? `${cdnBaseUrl}/${projectId}/${locale}.json?fallback=${encodeURIComponent(sourceLocale)}` : `${cdnBaseUrl}/${projectId}/${locale}.json`;
|
|
23
|
+
const request = JSON.stringify([edgeUrl, attempt]);
|
|
24
|
+
const [result, setResult] = useState({ request: "", status: "loading", error: null, translations: {} });
|
|
25
|
+
const current = result.request === request ? result : { status: "loading", error: null, translations: {} };
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
let cancelled = false;
|
|
28
|
+
fetch(edgeUrl).then(async (response) => {
|
|
29
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
30
|
+
const translations = await response.json();
|
|
31
|
+
if (!cancelled) setResult({ request, status: "ready", error: null, translations });
|
|
32
|
+
}).catch((error) => {
|
|
33
|
+
if (cancelled) return;
|
|
34
|
+
console.warn(`[AppLocale SDK] Failed to load locale ${locale}`, error);
|
|
35
|
+
setResult({ request, status: "error", error: `Could not load translations for ${locale}. Check the project is published, then try again.`, translations: {} });
|
|
36
|
+
});
|
|
37
|
+
return () => {
|
|
38
|
+
cancelled = true;
|
|
39
|
+
};
|
|
40
|
+
}, [edgeUrl, locale, request]);
|
|
41
|
+
return /* @__PURE__ */ jsx(LingoContext.Provider, { value: {
|
|
42
|
+
t: (key) => current.translations[key] ?? key,
|
|
43
|
+
locale,
|
|
44
|
+
setLocale,
|
|
45
|
+
delivery: { status: current.status, error: current.error, retry: () => setAttempt((value) => value + 1) }
|
|
46
|
+
}, children });
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/hooks.ts
|
|
50
|
+
import { useContext } from "react";
|
|
51
|
+
function useLingoContext() {
|
|
52
|
+
const context = useContext(LingoContext);
|
|
53
|
+
if (!context) {
|
|
54
|
+
throw new Error("AppLocale hooks must be used within a <LingoProvider>; must be used within a <LocaleProvider> when using the compatibility API");
|
|
55
|
+
}
|
|
56
|
+
return context;
|
|
57
|
+
}
|
|
58
|
+
var useT = () => {
|
|
59
|
+
return useLingoContext().t;
|
|
60
|
+
};
|
|
61
|
+
var useLocale = () => {
|
|
62
|
+
const { locale, setLocale } = useLingoContext();
|
|
63
|
+
return [locale, setLocale];
|
|
64
|
+
};
|
|
65
|
+
var useTranslationStatus = () => useLingoContext().delivery ?? { status: "ready", error: null, retry: () => {
|
|
66
|
+
} };
|
|
67
|
+
export {
|
|
68
|
+
AppLocaleVisualEditor,
|
|
69
|
+
DEFAULT_CDN_BASE_URL,
|
|
70
|
+
LingoProvider,
|
|
71
|
+
LingoProvider as LocaleProvider,
|
|
72
|
+
initAppLocale,
|
|
73
|
+
initVisualEditor,
|
|
74
|
+
useLocale,
|
|
75
|
+
useT,
|
|
76
|
+
useTranslationStatus
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/LingoProvider.tsx","../src/context.ts","../src/types.ts","../src/hooks.ts"],"sourcesContent":["import React, { useState, useEffect } from 'react';\nimport { LingoContext } from './context';\nimport { DEFAULT_CDN_BASE_URL } from './types';\nimport type { LingoProviderProps, TranslationDelivery } from './types';\n\ntype DeliveryResult = { request: string; status: TranslationDelivery['status']; error: string | null; translations: Record<string, string> };\n\nexport const LingoProvider = ({ projectId, defaultLocale = 'en-US', cdnBaseUrl = DEFAULT_CDN_BASE_URL, sourceLocale, children }: LingoProviderProps) => {\n const [locale, setLocale] = useState(defaultLocale);\n const [attempt, setAttempt] = useState(0);\n const edgeUrl = sourceLocale\n ? `${cdnBaseUrl}/${projectId}/${locale}.json?fallback=${encodeURIComponent(sourceLocale)}`\n : `${cdnBaseUrl}/${projectId}/${locale}.json`;\n const request = JSON.stringify([edgeUrl, attempt]);\n const [result, setResult] = useState<DeliveryResult>({ request: '', status: 'loading', error: null, translations: {} });\n // A new language immediately stops displaying the previous language's copy.\n const current: Pick<DeliveryResult, 'status' | 'error' | 'translations'> = result.request === request ? result : { status: 'loading', error: null, translations: {} };\n\n useEffect(() => {\n let cancelled = false;\n fetch(edgeUrl).then(async response => {\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n const translations = await response.json() as Record<string, string>;\n if (!cancelled) setResult({ request, status: 'ready', error: null, translations });\n }).catch(error => {\n if (cancelled) return;\n console.warn(`[AppLocale SDK] Failed to load locale ${locale}`, error);\n setResult({ request, status: 'error', error: `Could not load translations for ${locale}. Check the project is published, then try again.`, translations: {} });\n });\n return () => { cancelled = true; };\n }, [edgeUrl, locale, request]);\n\n return <LingoContext.Provider value={{\n t: key => current.translations[key] ?? key,\n locale,\n setLocale,\n delivery: { status: current.status, error: current.error, retry: () => setAttempt(value => value + 1) },\n }}>{children}</LingoContext.Provider>;\n};\n","import { createContext } from 'react';\nimport type { LingoContextValue } from './types';\n\nexport const LingoContext = createContext<LingoContextValue | null>(null);\n","import type { ReactNode } from 'react';\n\n/** Default CDN base URL used when none is provided to the LingoProvider. */\nexport const DEFAULT_CDN_BASE_URL = 'https://cdn.applocale.com';\n\n/**\n * Props accepted by the LingoProvider component.\n *\n * If `sourceLocale` is set, the SDK appends `?fallback=<sourceLocale>` to\n * fetch requests so the edge worker can fall back to the project's source\n * locale when a target locale is missing. Recommended to set to your\n * project's source locale code (e.g. `\"en-US\"`).\n */\nexport interface LingoProviderProps {\n /** The project’s public UUID from AppLocale (numeric database IDs are not accepted). */\n projectId: string;\n /** Initial locale to load. Defaults to \"en-US\". */\n defaultLocale?: string;\n /**\n * Base URL for the translations CDN / edge worker.\n * Translations are fetched from `${cdnBaseUrl}/${projectId}/${locale}.json`.\n * Defaults to {@link DEFAULT_CDN_BASE_URL}.\n */\n cdnBaseUrl?: string;\n /**\n * Optional. If set, the SDK appends `?fallback=<sourceLocale>` to fetch\n * requests so the edge worker can fall back to the project's source locale\n * when a target locale is missing. Recommended to set to your project's\n * source locale code (e.g. `\"en-US\"`). When omitted, no `?fallback=` query\n * parameter is appended and the edge worker uses its own default (`\"en\"`).\n */\n sourceLocale?: string;\n children: ReactNode;\n}\n\n/** Value exposed through the Lingo context. */\nexport interface LingoContextValue {\n /** Translate a key, falling back to the key itself when missing. */\n t: (key: string) => string;\n /** The currently active locale. */\n locale: string;\n /** Change the active locale (triggers a re-fetch of translations). */\n setLocale: (locale: string) => void;\n /** Delivery feedback for applications that show loading/retry controls. */\n delivery?: TranslationDelivery;\n}\n\nexport interface TranslationDelivery {\n status: 'loading' | 'ready' | 'error';\n error: string | null;\n retry: () => void;\n}\n\n/** AppLocale compatibility aliases; both provider names share the same context. */\nexport type LocaleProviderProps = LingoProviderProps;\nexport type LocaleContextValue = LingoContextValue;\n","import { useContext } from 'react';\nimport { LingoContext } from './context';\n\nfunction useLingoContext() {\n const context = useContext(LingoContext);\n if (!context) {\n throw new Error('AppLocale hooks must be used within a <LingoProvider>; must be used within a <LocaleProvider> when using the compatibility API');\n }\n return context;\n}\n\n/**\n * Returns the translation function `t(key) => string`.\n * Falls back to the key name when a translation is missing.\n */\nexport const useT = () => {\n return useLingoContext().t;\n};\n\n/**\n * Returns the current locale and a setter to change it.\n * Changing the locale triggers a re-fetch of the translations.\n */\nexport const useLocale = (): [string, (locale: string) => void] => {\n const { locale, setLocale } = useLingoContext();\n return [locale, setLocale];\n};\n\n/** Loading and recovery feedback from the provider; existing t/locale APIs stay compatible. */\nexport const useTranslationStatus = () => useLingoContext().delivery ?? { status: 'ready' as const, error: null, retry: () => {} };\n"],"mappings":";;;;;;;AAAA,SAAgB,UAAU,iBAAiB;;;ACA3C,SAAS,qBAAqB;AAGvB,IAAM,eAAe,cAAwC,IAAI;;;ACAjE,IAAM,uBAAuB;;;AF6B3B;AAzBF,IAAM,gBAAgB,CAAC,EAAE,WAAW,gBAAgB,SAAS,aAAa,sBAAsB,cAAc,SAAS,MAA0B;AACtJ,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,aAAa;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,CAAC;AACxC,QAAM,UAAU,eACZ,GAAG,UAAU,IAAI,SAAS,IAAI,MAAM,kBAAkB,mBAAmB,YAAY,CAAC,KACtF,GAAG,UAAU,IAAI,SAAS,IAAI,MAAM;AACxC,QAAM,UAAU,KAAK,UAAU,CAAC,SAAS,OAAO,CAAC;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAyB,EAAE,SAAS,IAAI,QAAQ,WAAW,OAAO,MAAM,cAAc,CAAC,EAAE,CAAC;AAEtH,QAAM,UAAqE,OAAO,YAAY,UAAU,SAAS,EAAE,QAAQ,WAAW,OAAO,MAAM,cAAc,CAAC,EAAE;AAEpK,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,UAAM,OAAO,EAAE,KAAK,OAAM,aAAY;AACpC,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,QAAQ,SAAS,MAAM,EAAE;AAC3D,YAAM,eAAe,MAAM,SAAS,KAAK;AACzC,UAAI,CAAC,UAAW,WAAU,EAAE,SAAS,QAAQ,SAAS,OAAO,MAAM,aAAa,CAAC;AAAA,IACnF,CAAC,EAAE,MAAM,WAAS;AAChB,UAAI,UAAW;AACf,cAAQ,KAAK,yCAAyC,MAAM,IAAI,KAAK;AACrE,gBAAU,EAAE,SAAS,QAAQ,SAAS,OAAO,mCAAmC,MAAM,qDAAqD,cAAc,CAAC,EAAE,CAAC;AAAA,IAC/J,CAAC;AACD,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAM;AAAA,EACnC,GAAG,CAAC,SAAS,QAAQ,OAAO,CAAC;AAE7B,SAAO,oBAAC,aAAa,UAAb,EAAsB,OAAO;AAAA,IACnC,GAAG,SAAO,QAAQ,aAAa,GAAG,KAAK;AAAA,IACvC;AAAA,IACA;AAAA,IACA,UAAU,EAAE,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,OAAO,OAAO,MAAM,WAAW,WAAS,QAAQ,CAAC,EAAE;AAAA,EACxG,GAAI,UAAS;AACf;;;AGtCA,SAAS,kBAAkB;AAG3B,SAAS,kBAAkB;AACzB,QAAM,UAAU,WAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,gIAAgI;AAAA,EAClJ;AACA,SAAO;AACT;AAMO,IAAM,OAAO,MAAM;AACxB,SAAO,gBAAgB,EAAE;AAC3B;AAMO,IAAM,YAAY,MAA0C;AACjE,QAAM,EAAE,QAAQ,UAAU,IAAI,gBAAgB;AAC9C,SAAO,CAAC,QAAQ,SAAS;AAC3B;AAGO,IAAM,uBAAuB,MAAM,gBAAgB,EAAE,YAAY,EAAE,QAAQ,SAAkB,OAAO,MAAM,OAAO,MAAM;AAAC,EAAE;","names":[]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/visual-editor.ts
|
|
21
|
+
var visual_editor_exports = {};
|
|
22
|
+
__export(visual_editor_exports, {
|
|
23
|
+
AppLocaleVisualEditor: () => AppLocaleVisualEditor,
|
|
24
|
+
initAppLocale: () => initAppLocale,
|
|
25
|
+
initVisualEditor: () => initVisualEditor
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(visual_editor_exports);
|
|
28
|
+
var AppLocaleVisualEditor = class {
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.active = false;
|
|
31
|
+
this.destroyed = false;
|
|
32
|
+
this.pending = false;
|
|
33
|
+
this.toggle = () => {
|
|
34
|
+
if (this.destroyed) return;
|
|
35
|
+
this.active = !this.active;
|
|
36
|
+
this.button.textContent = this.active ? "Disable Visual Editor" : "Enable Visual Editor";
|
|
37
|
+
this.button.setAttribute("aria-pressed", String(this.active));
|
|
38
|
+
if (this.active) {
|
|
39
|
+
document.body.addEventListener("mouseover", this.over);
|
|
40
|
+
document.body.addEventListener("mouseout", this.out);
|
|
41
|
+
document.body.addEventListener("click", this.click, true);
|
|
42
|
+
} else this.detach();
|
|
43
|
+
};
|
|
44
|
+
this.over = (event) => {
|
|
45
|
+
this.target(event)?.classList.add("locale-edge-hover");
|
|
46
|
+
};
|
|
47
|
+
this.out = (event) => {
|
|
48
|
+
this.target(event)?.classList.remove("locale-edge-hover");
|
|
49
|
+
};
|
|
50
|
+
this.click = async (event) => {
|
|
51
|
+
const target = this.target(event);
|
|
52
|
+
if (!this.active || !target) return;
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
event.stopPropagation();
|
|
55
|
+
if (this.pending) return;
|
|
56
|
+
const keyName = target.getAttribute("data-locale-key");
|
|
57
|
+
if (!keyName?.trim()) return;
|
|
58
|
+
const current = target.textContent ?? "";
|
|
59
|
+
const value = window.prompt(`Edit translation: ${keyName}`, current);
|
|
60
|
+
if (value === null || value === current) return;
|
|
61
|
+
const base = (this.config.apiBaseUrl ?? "https://api.applocale.com/api").replace(/\/$/, "");
|
|
62
|
+
try {
|
|
63
|
+
const url = new URL(base);
|
|
64
|
+
const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
65
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && local)) throw new Error("Use an HTTPS API URL or a local development server.");
|
|
66
|
+
this.pending = true;
|
|
67
|
+
this.status.textContent = "Saving translation\u2026";
|
|
68
|
+
const response = await fetch(`${base}/projects/${encodeURIComponent(this.config.projectId)}/keys/visual-sync`, { method: "POST", headers: { "Content-Type": "application/json", "X-AppLocale-Key": this.config.apiKey }, body: JSON.stringify({ keyName, value, localeCode: this.config.defaultLocale ?? "en-US", namespace: target.getAttribute("data-locale-namespace") ?? this.config.namespace ?? "default" }) });
|
|
69
|
+
if (!response.ok) throw new Error(`Save failed (HTTP ${response.status}). Check the project key, scope and locale.`);
|
|
70
|
+
if (!this.destroyed) {
|
|
71
|
+
target.textContent = value;
|
|
72
|
+
this.status.textContent = "Translation saved.";
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (!this.destroyed) this.status.textContent = error instanceof Error ? error.message : "Unable to save translation. Please try again.";
|
|
76
|
+
} finally {
|
|
77
|
+
this.pending = false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
this.config = config;
|
|
81
|
+
this.style = document.createElement("style");
|
|
82
|
+
this.style.textContent = ".locale-edge-hover{outline:2px dashed #ffae50!important;cursor:pointer!important}.locale-edge-btn{position:fixed;bottom:24px;right:24px;background:#292724;color:#fff4df;border:1px solid #c48e52;padding:12px 20px;border-radius:16px;font:600 14px system-ui;box-shadow:0 8px 32px #0004;z-index:2147483646}.locale-edge-status{position:fixed;right:24px;bottom:80px;max-width:320px;background:#292724;color:#fff4df;padding:12px;border-radius:12px;font:14px system-ui;z-index:2147483646}.locale-edge-status:empty{display:none}";
|
|
83
|
+
document.head.appendChild(this.style);
|
|
84
|
+
this.status = document.createElement("div");
|
|
85
|
+
this.status.className = "locale-edge-status";
|
|
86
|
+
this.status.setAttribute("role", "status");
|
|
87
|
+
this.status.setAttribute("aria-live", "polite");
|
|
88
|
+
document.body.appendChild(this.status);
|
|
89
|
+
this.button = document.createElement("button");
|
|
90
|
+
this.button.type = "button";
|
|
91
|
+
this.button.className = "locale-edge-btn";
|
|
92
|
+
this.button.textContent = "Enable Visual Editor";
|
|
93
|
+
this.button.setAttribute("aria-pressed", "false");
|
|
94
|
+
this.button.addEventListener("click", this.toggle);
|
|
95
|
+
document.body.appendChild(this.button);
|
|
96
|
+
}
|
|
97
|
+
target(event) {
|
|
98
|
+
return event.target instanceof Element ? event.target.closest("[data-locale-key]") : null;
|
|
99
|
+
}
|
|
100
|
+
detach() {
|
|
101
|
+
document.body.removeEventListener("mouseover", this.over);
|
|
102
|
+
document.body.removeEventListener("mouseout", this.out);
|
|
103
|
+
document.body.removeEventListener("click", this.click, true);
|
|
104
|
+
document.querySelectorAll(".locale-edge-hover").forEach((element) => element.classList.remove("locale-edge-hover"));
|
|
105
|
+
}
|
|
106
|
+
/** Call when unmounting the staging app/editor. Removes every owned element and listener. */
|
|
107
|
+
destroy() {
|
|
108
|
+
this.destroyed = true;
|
|
109
|
+
this.active = false;
|
|
110
|
+
this.detach();
|
|
111
|
+
this.button.removeEventListener("click", this.toggle);
|
|
112
|
+
this.button.remove();
|
|
113
|
+
this.status.remove();
|
|
114
|
+
this.style.remove();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var initAppLocale = (config) => new AppLocaleVisualEditor(config);
|
|
118
|
+
var initVisualEditor = initAppLocale;
|
|
119
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
120
|
+
0 && (module.exports = {
|
|
121
|
+
AppLocaleVisualEditor,
|
|
122
|
+
initAppLocale,
|
|
123
|
+
initVisualEditor
|
|
124
|
+
});
|
|
125
|
+
//# sourceMappingURL=visual-editor.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/visual-editor.ts"],"sourcesContent":["/** Staging-only in-context editor. Give it an expiring project-scoped write key. */\nexport interface AppLocaleConfig {\n apiKey: string;\n projectId: string;\n apiBaseUrl?: string;\n defaultLocale?: string;\n namespace?: string;\n}\nexport class AppLocaleVisualEditor {\n private active = false;\n private destroyed = false;\n private pending = false;\n private readonly button: HTMLButtonElement;\n private readonly status: HTMLDivElement;\n private readonly style: HTMLStyleElement;\n private readonly config: AppLocaleConfig;\n constructor(config: AppLocaleConfig) {\n this.config = config;\n this.style = document.createElement('style');\n this.style.textContent = '.locale-edge-hover{outline:2px dashed #ffae50!important;cursor:pointer!important}.locale-edge-btn{position:fixed;bottom:24px;right:24px;background:#292724;color:#fff4df;border:1px solid #c48e52;padding:12px 20px;border-radius:16px;font:600 14px system-ui;box-shadow:0 8px 32px #0004;z-index:2147483646}.locale-edge-status{position:fixed;right:24px;bottom:80px;max-width:320px;background:#292724;color:#fff4df;padding:12px;border-radius:12px;font:14px system-ui;z-index:2147483646}.locale-edge-status:empty{display:none}';\n document.head.appendChild(this.style);\n this.status = document.createElement('div'); this.status.className = 'locale-edge-status'; this.status.setAttribute('role', 'status'); this.status.setAttribute('aria-live', 'polite'); document.body.appendChild(this.status);\n this.button = document.createElement('button'); this.button.type = 'button'; this.button.className = 'locale-edge-btn'; this.button.textContent = 'Enable Visual Editor'; this.button.setAttribute('aria-pressed', 'false'); this.button.addEventListener('click', this.toggle); document.body.appendChild(this.button);\n }\n private toggle = () => {\n if (this.destroyed) return;\n this.active = !this.active; this.button.textContent = this.active ? 'Disable Visual Editor' : 'Enable Visual Editor'; this.button.setAttribute('aria-pressed', String(this.active));\n if (this.active) { document.body.addEventListener('mouseover', this.over); document.body.addEventListener('mouseout', this.out); document.body.addEventListener('click', this.click, true); }\n else this.detach();\n };\n private target(event: MouseEvent): HTMLElement | null {\n return event.target instanceof Element ? event.target.closest<HTMLElement>('[data-locale-key]') : null;\n }\n private over = (event: MouseEvent) => { this.target(event)?.classList.add('locale-edge-hover'); };\n private out = (event: MouseEvent) => { this.target(event)?.classList.remove('locale-edge-hover'); };\n private click = async (event: MouseEvent) => {\n const target = this.target(event); if (!this.active || !target) return;\n event.preventDefault(); event.stopPropagation(); if (this.pending) return;\n const keyName = target.getAttribute('data-locale-key'); if (!keyName?.trim()) return;\n const current = target.textContent ?? ''; const value = window.prompt(`Edit translation: ${keyName}`, current);\n if (value === null || value === current) return;\n const base = (this.config.apiBaseUrl ?? 'https://api.applocale.com/api').replace(/\\/$/, '');\n try {\n const url = new URL(base); const local = ['localhost','127.0.0.1','[::1]'].includes(url.hostname);\n if (url.protocol !== 'https:' && !(url.protocol === 'http:' && local)) throw new Error('Use an HTTPS API URL or a local development server.');\n this.pending = true; this.status.textContent = 'Saving translation…';\n const response = await fetch(`${base}/projects/${encodeURIComponent(this.config.projectId)}/keys/visual-sync`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-AppLocale-Key': this.config.apiKey }, body: JSON.stringify({ keyName, value, localeCode: this.config.defaultLocale ?? 'en-US', namespace: target.getAttribute('data-locale-namespace') ?? this.config.namespace ?? 'default' }) });\n if (!response.ok) throw new Error(`Save failed (HTTP ${response.status}). Check the project key, scope and locale.`);\n if (!this.destroyed) { target.textContent = value; this.status.textContent = 'Translation saved.'; }\n } catch (error) { if (!this.destroyed) this.status.textContent = error instanceof Error ? error.message : 'Unable to save translation. Please try again.'; }\n finally { this.pending = false; }\n };\n private detach() {\n document.body.removeEventListener('mouseover', this.over); document.body.removeEventListener('mouseout', this.out); document.body.removeEventListener('click', this.click, true);\n document.querySelectorAll('.locale-edge-hover').forEach(element => element.classList.remove('locale-edge-hover'));\n }\n /** Call when unmounting the staging app/editor. Removes every owned element and listener. */\n destroy() { this.destroyed = true; this.active = false; this.detach(); this.button.removeEventListener('click', this.toggle); this.button.remove(); this.status.remove(); this.style.remove(); }\n}\nexport const initAppLocale = (config: AppLocaleConfig) => new AppLocaleVisualEditor(config);\nexport const initVisualEditor = initAppLocale;\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Staging-only in-context editor. Give it an expiring project-scoped write key. */
|
|
2
|
+
interface AppLocaleConfig {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
projectId: string;
|
|
5
|
+
apiBaseUrl?: string;
|
|
6
|
+
defaultLocale?: string;
|
|
7
|
+
namespace?: string;
|
|
8
|
+
}
|
|
9
|
+
declare class AppLocaleVisualEditor {
|
|
10
|
+
private active;
|
|
11
|
+
private destroyed;
|
|
12
|
+
private pending;
|
|
13
|
+
private readonly button;
|
|
14
|
+
private readonly status;
|
|
15
|
+
private readonly style;
|
|
16
|
+
private readonly config;
|
|
17
|
+
constructor(config: AppLocaleConfig);
|
|
18
|
+
private toggle;
|
|
19
|
+
private target;
|
|
20
|
+
private over;
|
|
21
|
+
private out;
|
|
22
|
+
private click;
|
|
23
|
+
private detach;
|
|
24
|
+
/** Call when unmounting the staging app/editor. Removes every owned element and listener. */
|
|
25
|
+
destroy(): void;
|
|
26
|
+
}
|
|
27
|
+
declare const initAppLocale: (config: AppLocaleConfig) => AppLocaleVisualEditor;
|
|
28
|
+
declare const initVisualEditor: (config: AppLocaleConfig) => AppLocaleVisualEditor;
|
|
29
|
+
|
|
30
|
+
export { type AppLocaleConfig, AppLocaleVisualEditor, initAppLocale, initVisualEditor };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Staging-only in-context editor. Give it an expiring project-scoped write key. */
|
|
2
|
+
interface AppLocaleConfig {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
projectId: string;
|
|
5
|
+
apiBaseUrl?: string;
|
|
6
|
+
defaultLocale?: string;
|
|
7
|
+
namespace?: string;
|
|
8
|
+
}
|
|
9
|
+
declare class AppLocaleVisualEditor {
|
|
10
|
+
private active;
|
|
11
|
+
private destroyed;
|
|
12
|
+
private pending;
|
|
13
|
+
private readonly button;
|
|
14
|
+
private readonly status;
|
|
15
|
+
private readonly style;
|
|
16
|
+
private readonly config;
|
|
17
|
+
constructor(config: AppLocaleConfig);
|
|
18
|
+
private toggle;
|
|
19
|
+
private target;
|
|
20
|
+
private over;
|
|
21
|
+
private out;
|
|
22
|
+
private click;
|
|
23
|
+
private detach;
|
|
24
|
+
/** Call when unmounting the staging app/editor. Removes every owned element and listener. */
|
|
25
|
+
destroy(): void;
|
|
26
|
+
}
|
|
27
|
+
declare const initAppLocale: (config: AppLocaleConfig) => AppLocaleVisualEditor;
|
|
28
|
+
declare const initVisualEditor: (config: AppLocaleConfig) => AppLocaleVisualEditor;
|
|
29
|
+
|
|
30
|
+
export { type AppLocaleConfig, AppLocaleVisualEditor, initAppLocale, initVisualEditor };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applocale/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
|
+
"description": "AppLocale React translation and staging visual editor SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"prepare": "npm run build",
|
|
30
|
+
"dev": "tsup --watch",
|
|
31
|
+
"test": "vitest run --coverage",
|
|
32
|
+
"test:watch": "vitest"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [],
|
|
35
|
+
"author": "",
|
|
36
|
+
"license": "ISC",
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": ">=18",
|
|
39
|
+
"react-dom": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
43
|
+
"@testing-library/react": "^16.3.2",
|
|
44
|
+
"@types/react": "^19.2.6",
|
|
45
|
+
"@vitest/coverage-v8": "^4.1.12",
|
|
46
|
+
"esbuild": "^0.28.1",
|
|
47
|
+
"jsdom": "^29.1.1",
|
|
48
|
+
"react": "^19.2.6",
|
|
49
|
+
"react-dom": "^19.2.6",
|
|
50
|
+
"tsup": "^8.5.1",
|
|
51
|
+
"typescript": "^5.7.2",
|
|
52
|
+
"vitest": "^4.1.12"
|
|
53
|
+
},
|
|
54
|
+
"overrides": {
|
|
55
|
+
"tsup": {
|
|
56
|
+
"esbuild": "^0.28.1"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=22"
|
|
61
|
+
}
|
|
62
|
+
}
|