@lovalingo/lovalingo 0.0.27 → 0.1.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/README.md +72 -170
- package/dist/components/AixsterProvider.d.ts +0 -17
- package/dist/components/AixsterProvider.js +103 -196
- package/dist/components/LanguageSwitcher.d.ts +1 -0
- package/dist/components/LanguageSwitcher.js +41 -20
- package/dist/components/NavigationOverlay.js +5 -27
- package/dist/index.d.ts +1 -2
- package/dist/index.js +0 -1
- package/dist/types.d.ts +2 -19
- package/dist/utils/api.d.ts +15 -2
- package/dist/utils/api.js +29 -64
- package/dist/utils/markerEngine.d.ts +21 -0
- package/dist/utils/markerEngine.js +279 -0
- package/dist/utils/translator.d.ts +1 -12
- package/dist/utils/translator.js +2 -141
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/dist/components/AutoTranslate.d.ts +0 -10
- package/dist/components/AutoTranslate.js +0 -69
package/README.md
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
# @lovalingo/lovalingo
|
|
1
|
+
# @lovalingo/lovalingo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lovalingo is a translation runtime for React (React Router) and Next.js that loads **pre-built artifacts** (JSON bundles + DOM rules) from the Lovalingo backend and applies them with **zero-flash**.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It does **not** generate translations in the browser.
|
|
6
|
+
|
|
7
|
+
## How it works (high level)
|
|
8
|
+
|
|
9
|
+
1. Your app renders normally (source language).
|
|
10
|
+
2. Lovalingo loads the current locale’s bundle from the backend.
|
|
11
|
+
3. The runtime mutates the DOM before paint (and keeps up with route changes + dynamic content).
|
|
12
|
+
4. Optional: Lovalingo fetches DOM rules (CSS/JS/DOM patches) to fix edge cases (hidden UI, wrapping, etc.).
|
|
13
|
+
5. Optional SEO: Lovalingo updates `<head>` (canonical + hreflang + basic meta) using `seo-bundle`.
|
|
14
|
+
|
|
15
|
+
All artifacts are produced server-side by the pipeline (render → audit → deterministic translate → optional fix loop).
|
|
6
16
|
|
|
7
17
|
## Installation
|
|
8
18
|
|
|
@@ -10,106 +20,75 @@ If you ship with Lovable, v0, Emergent, Vite, Claude Code or similar tools, Lova
|
|
|
10
20
|
npm install @lovalingo/lovalingo react-router-dom
|
|
11
21
|
```
|
|
12
22
|
|
|
13
|
-
##
|
|
23
|
+
## React Router
|
|
14
24
|
|
|
15
|
-
###
|
|
16
|
-
|
|
17
|
-
**One-line setup** that automatically handles language routing like `/en/pricing`, `/fr/pricing`, `/de/pricing`:
|
|
25
|
+
### Query mode (default)
|
|
18
26
|
|
|
19
27
|
```tsx
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { useRef } from 'react';
|
|
23
|
-
|
|
24
|
-
function App() {
|
|
25
|
-
const navigateRef = useRef();
|
|
28
|
+
import { BrowserRouter } from "react-router-dom";
|
|
29
|
+
import { LovalingoProvider } from "@lovalingo/lovalingo";
|
|
26
30
|
|
|
31
|
+
export function App() {
|
|
27
32
|
return (
|
|
28
|
-
<
|
|
33
|
+
<BrowserRouter>
|
|
29
34
|
<LovalingoProvider
|
|
30
35
|
publicAnonKey="aix_your_public_anon_key"
|
|
31
36
|
defaultLocale="en"
|
|
32
|
-
locales={[
|
|
33
|
-
routing="
|
|
34
|
-
navigateRef={navigateRef}
|
|
37
|
+
locales={["en", "de", "fr"]}
|
|
38
|
+
routing="query"
|
|
35
39
|
>
|
|
36
|
-
<
|
|
37
|
-
{/* No language prefix needed! */}
|
|
38
|
-
<Route path="/" element={<Home />} />
|
|
39
|
-
<Route path="home" element={<Home />} />
|
|
40
|
-
<Route path="pricing" element={<Pricing />} />
|
|
41
|
-
<Route path="about" element={<About />} />
|
|
42
|
-
</Routes>
|
|
40
|
+
<YourApp />
|
|
43
41
|
</LovalingoProvider>
|
|
44
|
-
</
|
|
42
|
+
</BrowserRouter>
|
|
45
43
|
);
|
|
46
44
|
}
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
**Common error (React Router v6):** If you see `LovalingoProvider is not a <Route> component`, you’re either:
|
|
52
|
-
- on an older `@lovalingo/lovalingo` version (upgrade), or
|
|
53
|
-
- you accidentally placed `<LovalingoProvider>` directly inside `<Routes>`.
|
|
47
|
+
URLs look like: `/pricing?t=de`.
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
```tsx
|
|
57
|
-
<LovalingoProvider ...>
|
|
58
|
-
<Routes>...</Routes>
|
|
59
|
-
</LovalingoProvider>
|
|
60
|
-
```
|
|
49
|
+
### Path mode (SEO-friendly URLs)
|
|
61
50
|
|
|
62
|
-
Incorrect:
|
|
63
51
|
```tsx
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
URLs automatically work as:
|
|
70
|
-
- `/en/`, `/en/home`, `/en/pricing`, `/en/about`
|
|
71
|
-
- `/fr/`, `/fr/home`, `/fr/pricing`, `/fr/about`
|
|
72
|
-
- `/de/`, `/de/home`, `/de/pricing`, `/de/about`
|
|
73
|
-
|
|
74
|
-
### Option 2: Query Mode (Simple - Query Parameters)
|
|
52
|
+
import { useRef } from "react";
|
|
53
|
+
import { Routes, Route } from "react-router-dom";
|
|
54
|
+
import { LangRouter, LovalingoProvider } from "@lovalingo/lovalingo";
|
|
75
55
|
|
|
76
|
-
|
|
56
|
+
export function App() {
|
|
57
|
+
const navigateRef = useRef<((path: string) => void) | undefined>(undefined);
|
|
77
58
|
|
|
78
|
-
```tsx
|
|
79
|
-
import { LovalingoProvider } from '@lovalingo/lovalingo';
|
|
80
|
-
import { BrowserRouter } from 'react-router-dom';
|
|
81
|
-
|
|
82
|
-
function App() {
|
|
83
59
|
return (
|
|
84
|
-
<
|
|
60
|
+
<LangRouter defaultLang="en" langs={["en", "de", "fr"]} navigateRef={navigateRef}>
|
|
85
61
|
<LovalingoProvider
|
|
86
62
|
publicAnonKey="aix_your_public_anon_key"
|
|
87
63
|
defaultLocale="en"
|
|
88
|
-
locales={[
|
|
89
|
-
routing="
|
|
64
|
+
locales={["en", "de", "fr"]}
|
|
65
|
+
routing="path"
|
|
66
|
+
navigateRef={navigateRef}
|
|
90
67
|
>
|
|
91
|
-
<
|
|
68
|
+
<Routes>
|
|
69
|
+
<Route path="/" element={<Home />} />
|
|
70
|
+
<Route path="pricing" element={<Pricing />} />
|
|
71
|
+
<Route path="about" element={<About />} />
|
|
72
|
+
</Routes>
|
|
92
73
|
</LovalingoProvider>
|
|
93
|
-
</
|
|
74
|
+
</LangRouter>
|
|
94
75
|
);
|
|
95
76
|
}
|
|
96
77
|
```
|
|
97
78
|
|
|
98
|
-
|
|
79
|
+
URLs look like: `/de/pricing`.
|
|
80
|
+
|
|
81
|
+
## Next.js (App Router)
|
|
99
82
|
|
|
100
83
|
```tsx
|
|
101
84
|
// app/layout.tsx
|
|
102
|
-
import { LovalingoProvider } from
|
|
85
|
+
import { LovalingoProvider } from "@lovalingo/lovalingo";
|
|
103
86
|
|
|
104
|
-
export default function RootLayout({ children }) {
|
|
87
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
105
88
|
return (
|
|
106
89
|
<html>
|
|
107
90
|
<body>
|
|
108
|
-
<LovalingoProvider
|
|
109
|
-
publicAnonKey="aix_your_public_anon_key"
|
|
110
|
-
defaultLocale="en"
|
|
111
|
-
locales={['en', 'de', 'fr', 'es']}
|
|
112
|
-
>
|
|
91
|
+
<LovalingoProvider publicAnonKey="aix_your_public_anon_key" defaultLocale="en" locales={["en", "de", "fr"]}>
|
|
113
92
|
{children}
|
|
114
93
|
</LovalingoProvider>
|
|
115
94
|
</body>
|
|
@@ -118,121 +97,27 @@ export default function RootLayout({ children }) {
|
|
|
118
97
|
}
|
|
119
98
|
```
|
|
120
99
|
|
|
121
|
-
##
|
|
122
|
-
|
|
123
|
-
```tsx
|
|
124
|
-
<LovalingoProvider
|
|
125
|
-
publicAnonKey="aix_xxx" // Required: Your Lovalingo Public Anon Key (safe to expose)
|
|
126
|
-
defaultLocale="en" // Required: Source language
|
|
127
|
-
locales={['en', 'de', 'fr']} // Required: Supported languages
|
|
128
|
-
apiBase="https://..." // Optional: Custom API endpoint
|
|
129
|
-
routing="query" // Optional: 'query' | 'path' (default: 'query')
|
|
130
|
-
autoPrefixLinks={true} // Optional (path mode): keep locale when the app renders absolute links like "/pricing"
|
|
131
|
-
switcherPosition="bottom-right" // Optional: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
|
|
132
|
-
switcherOffsetY={20} // Optional: Vertical offset in pixels
|
|
133
|
-
editMode={false} // Optional: Enable edit mode
|
|
134
|
-
editKey="KeyE" // Optional: Keyboard shortcut for edit mode
|
|
135
|
-
mode="dom" // Optional: 'dom' | 'context' (default: 'dom')
|
|
136
|
-
pathNormalization={{ enabled: true }}// Optional: normalize paths for stable translation keys (default: enabled)
|
|
137
|
-
seo={true} // Optional: auto canonical + hreflang + sitemap link (default: true)
|
|
138
|
-
sitemap={true} // Optional: auto-inject sitemap link (default: true)
|
|
139
|
-
>
|
|
140
|
-
{children}
|
|
141
|
-
</LovalingoProvider>
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Optional: Inject via `index.html`
|
|
145
|
-
|
|
146
|
-
If your tooling prefers you to avoid hardcoding the key inside JSX, you can inject it at runtime:
|
|
147
|
-
|
|
148
|
-
```html
|
|
149
|
-
<meta name="lovalingo-public-anon-key" content="aix_xxx" />
|
|
150
|
-
<script>
|
|
151
|
-
window.__LOVALINGO_PUBLIC_ANON_KEY__ = "aix_xxx";
|
|
152
|
-
</script>
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Then you may omit `publicAnonKey` and Lovalingo will read it from the meta tag or `window.__LOVALINGO_PUBLIC_ANON_KEY__`.
|
|
156
|
-
|
|
157
|
-
### Routing Modes
|
|
158
|
-
|
|
159
|
-
- **`query`** (default): Language in query parameter `/pricing?t=fr`
|
|
160
|
-
- Use with standard React Router setup
|
|
161
|
-
- No URL structure changes needed
|
|
162
|
-
|
|
163
|
-
- **`path`**: Language in URL path `/fr/pricing`
|
|
164
|
-
- Use with `<LangRouter>` wrapper
|
|
165
|
-
- Automatic language routing
|
|
166
|
-
- SEO-optimized URLs
|
|
167
|
-
- Non-localized by default: `/auth`, `/login`, `/signup` (no `/:lang/` prefix)
|
|
168
|
-
- See [PATH_EXAMPLES.md](./PATH_EXAMPLES.md) for detailed examples
|
|
100
|
+
## SEO (canonical + hreflang + meta)
|
|
169
101
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
### LangLink - Language-Aware Links
|
|
102
|
+
Enabled by default. Disable if you already manage `<head>` yourself:
|
|
173
103
|
|
|
174
104
|
```tsx
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
function Navigation() {
|
|
178
|
-
return (
|
|
179
|
-
<nav>
|
|
180
|
-
{/* Automatically becomes /en/pricing or /fr/pricing */}
|
|
181
|
-
<LangLink to="pricing">Pricing</LangLink>
|
|
182
|
-
<LangLink to="about">About</LangLink>
|
|
183
|
-
</nav>
|
|
184
|
-
);
|
|
185
|
-
}
|
|
105
|
+
<LovalingoProvider seo={false} ... />
|
|
186
106
|
```
|
|
187
107
|
|
|
188
|
-
|
|
108
|
+
## Sitemap link tag
|
|
189
109
|
|
|
190
|
-
|
|
191
|
-
import { useLang } from '@lovalingo/lovalingo';
|
|
192
|
-
|
|
193
|
-
function MyComponent() {
|
|
194
|
-
const lang = useLang(); // 'en', 'fr', 'de', etc.
|
|
195
|
-
return <div>Current language: {lang}</div>;
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### useLangNavigate - Programmatic Navigation
|
|
110
|
+
By default Lovalingo injects `<link rel="sitemap" href="/sitemap.xml">` for discovery. Disable it:
|
|
200
111
|
|
|
201
112
|
```tsx
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
function MyComponent() {
|
|
205
|
-
const navigate = useLangNavigate();
|
|
206
|
-
|
|
207
|
-
const goToPricing = () => {
|
|
208
|
-
navigate('pricing'); // Goes to /en/pricing or /fr/pricing automatically
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
return <button onClick={goToPricing}>View Pricing</button>;
|
|
212
|
-
}
|
|
113
|
+
<LovalingoProvider sitemap={false} ... />
|
|
213
114
|
```
|
|
214
115
|
|
|
215
|
-
|
|
116
|
+
You still need to serve `/sitemap.xml` on your own domain (recommended: reverse-proxy to Lovalingo’s `generate-sitemap` endpoint).
|
|
216
117
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
Access locale state and switching:
|
|
220
|
-
|
|
221
|
-
```tsx
|
|
222
|
-
import { useLovalingo } from '@lovalingo/lovalingo';
|
|
223
|
-
|
|
224
|
-
function MyComponent() {
|
|
225
|
-
const { locale, setLocale, isLoading, config } = useLovalingo();
|
|
226
|
-
|
|
227
|
-
return (
|
|
228
|
-
<button onClick={() => setLocale('de')}>
|
|
229
|
-
Switch to German
|
|
230
|
-
</button>
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
```
|
|
118
|
+
## License
|
|
234
119
|
|
|
235
|
-
|
|
120
|
+
Commercial license (not open source). See `react-package/LICENSE`.
|
|
236
121
|
|
|
237
122
|
Manual translation control:
|
|
238
123
|
|
|
@@ -290,6 +175,23 @@ function MyComponent() {
|
|
|
290
175
|
- ✅ RTL ready: automatically sets `<html dir="rtl">` for `ar/he/fa/ur`
|
|
291
176
|
- ✅ Optional Context Mode: `<AutoTranslate>` with hash-based caching for React text nodes
|
|
292
177
|
|
|
178
|
+
## Language Switcher
|
|
179
|
+
|
|
180
|
+
Lovalingo includes a floating language switcher.
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
<LovalingoProvider
|
|
184
|
+
publicAnonKey="aix_xxx"
|
|
185
|
+
defaultLocale="en"
|
|
186
|
+
locales={["en", "de", "fr"]}
|
|
187
|
+
switcherPosition="bottom-right"
|
|
188
|
+
switcherOffsetY={20}
|
|
189
|
+
switcherTheme="light" // "dark" | "light" (default: "dark")
|
|
190
|
+
>
|
|
191
|
+
<App />
|
|
192
|
+
</LovalingoProvider>
|
|
193
|
+
```
|
|
194
|
+
|
|
293
195
|
## SEO (Canonical + hreflang)
|
|
294
196
|
|
|
295
197
|
Lovalingo can keep `<head>` SEO signals in sync with the active locale:
|
|
@@ -6,22 +6,5 @@ interface LovalingoProviderProps extends LovalingoConfig {
|
|
|
6
6
|
seo?: boolean;
|
|
7
7
|
navigateRef?: React.MutableRefObject<((path: string) => void) | undefined>;
|
|
8
8
|
}
|
|
9
|
-
type LovalingoSeleniumBridge = {
|
|
10
|
-
getStatus: () => {
|
|
11
|
-
mode: LovalingoConfig["mode"];
|
|
12
|
-
locale: string;
|
|
13
|
-
defaultLocale: string;
|
|
14
|
-
path: string;
|
|
15
|
-
missed: number;
|
|
16
|
-
};
|
|
17
|
-
flushMisses: () => Promise<{
|
|
18
|
-
reported: number;
|
|
19
|
-
locale: string;
|
|
20
|
-
path: string;
|
|
21
|
-
}>;
|
|
22
|
-
};
|
|
23
|
-
declare global {
|
|
24
|
-
var __LOVALINGO_SELENIUM__: LovalingoSeleniumBridge | undefined;
|
|
25
|
-
}
|
|
26
9
|
export declare const LovalingoProvider: React.FC<LovalingoProviderProps>;
|
|
27
10
|
export {};
|