@lovalingo/lovalingo 0.0.13 → 0.0.15
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/components/AixsterProvider.js +58 -42
- package/dist/components/LangRouter.js +17 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +1 -1
|
@@ -42,6 +42,7 @@ navigateRef, // For path mode routing
|
|
|
42
42
|
const missReportIntervalRef = useRef(null);
|
|
43
43
|
const retryTimeoutRef = useRef(null);
|
|
44
44
|
const isNavigatingRef = useRef(false);
|
|
45
|
+
const isInternalNavigationRef = useRef(false);
|
|
45
46
|
const translationCacheRef = useRef(new Map());
|
|
46
47
|
const exclusionsCacheRef = useRef(null);
|
|
47
48
|
// NEW: Hash-based translation cache for React Context system
|
|
@@ -306,43 +307,60 @@ navigateRef, // For path mode routing
|
|
|
306
307
|
}, [defaultLocale]);
|
|
307
308
|
// Change locale
|
|
308
309
|
const setLocale = useCallback((newLocale) => {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const previousLocale = locale; // Capture current locale before switching
|
|
312
|
-
// Save to localStorage
|
|
313
|
-
try {
|
|
314
|
-
localStorage.setItem(LOCALE_STORAGE_KEY, newLocale);
|
|
315
|
-
}
|
|
316
|
-
catch (e) {
|
|
317
|
-
console.warn('Failed to save locale to localStorage:', e);
|
|
318
|
-
}
|
|
319
|
-
// Update URL based on routing strategy
|
|
320
|
-
if (routing === 'path') {
|
|
321
|
-
// Path mode: use React Router navigate
|
|
322
|
-
const navigate = navigateRef?.current;
|
|
323
|
-
if (!navigate) {
|
|
324
|
-
console.error('PATH mode requires navigateRef prop. Pass navigateRef to both <LangRouter> and <LovalingoProvider>');
|
|
310
|
+
void (async () => {
|
|
311
|
+
if (!allLocales.includes(newLocale))
|
|
325
312
|
return;
|
|
313
|
+
const previousLocale = locale; // Capture current locale before switching
|
|
314
|
+
// Save to localStorage
|
|
315
|
+
try {
|
|
316
|
+
localStorage.setItem(LOCALE_STORAGE_KEY, newLocale);
|
|
326
317
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (allLocales.includes(pathParts[0])) {
|
|
330
|
-
pathParts.shift();
|
|
318
|
+
catch (e) {
|
|
319
|
+
console.warn('Failed to save locale to localStorage:', e);
|
|
331
320
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
321
|
+
isInternalNavigationRef.current = true;
|
|
322
|
+
// Show navigation overlay immediately (only when a non-default locale is involved)
|
|
323
|
+
if (locale !== defaultLocale || newLocale !== defaultLocale) {
|
|
324
|
+
setIsNavigationLoading(true);
|
|
325
|
+
}
|
|
326
|
+
// Prevent MutationObserver work during the switch to avoid React conflicts
|
|
327
|
+
isNavigatingRef.current = true;
|
|
328
|
+
// Update URL based on routing strategy
|
|
329
|
+
if (routing === 'path') {
|
|
330
|
+
const pathParts = window.location.pathname.split('/').filter(Boolean);
|
|
331
|
+
// Strip existing locale
|
|
332
|
+
if (allLocales.includes(pathParts[0])) {
|
|
333
|
+
pathParts.shift();
|
|
334
|
+
}
|
|
335
|
+
// Build new path with new locale
|
|
336
|
+
const basePath = pathParts.join('/');
|
|
337
|
+
const newPath = `/${newLocale}${basePath ? '/' + basePath : ''}${window.location.search}${window.location.hash}`;
|
|
338
|
+
// Prefer React Router navigation when available, but gracefully fallback for non-React-Router apps
|
|
339
|
+
const navigate = navigateRef?.current;
|
|
340
|
+
if (navigate) {
|
|
341
|
+
navigate(newPath);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
try {
|
|
345
|
+
window.history.pushState({}, '', newPath);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
window.location.assign(newPath);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else if (routing === 'query') {
|
|
353
|
+
const url = new URL(window.location.href);
|
|
354
|
+
url.searchParams.set('t', newLocale);
|
|
355
|
+
window.history.pushState({}, '', url.toString());
|
|
356
|
+
}
|
|
357
|
+
setLocaleState(newLocale);
|
|
358
|
+
// Always load data with overlay when switching locales to provide feedback and ensure cleanup
|
|
359
|
+
await loadData(newLocale, previousLocale, true);
|
|
360
|
+
})().finally(() => {
|
|
361
|
+
isInternalNavigationRef.current = false;
|
|
362
|
+
});
|
|
363
|
+
}, [allLocales, locale, routing, loadData, defaultLocale, navigateRef]);
|
|
346
364
|
// NEW: Get translation by hash (for React Context system)
|
|
347
365
|
const getTranslation = useCallback((hash, fallback) => {
|
|
348
366
|
return hashTranslations.get(hash) || null;
|
|
@@ -442,17 +460,16 @@ navigateRef, // For path mode routing
|
|
|
442
460
|
useEffect(() => {
|
|
443
461
|
let navigationTimeout = null;
|
|
444
462
|
const handlePopState = () => {
|
|
463
|
+
if (isInternalNavigationRef.current)
|
|
464
|
+
return;
|
|
445
465
|
const newLocale = detectLocale();
|
|
446
466
|
const previousLocale = locale;
|
|
447
467
|
// Show navigation overlay immediately
|
|
448
468
|
if (locale !== defaultLocale || newLocale !== defaultLocale) {
|
|
449
469
|
setIsNavigationLoading(true);
|
|
450
470
|
}
|
|
451
|
-
// SET NAVIGATION FLAG
|
|
471
|
+
// SET NAVIGATION FLAG (MutationObserver callback will ignore while navigating)
|
|
452
472
|
isNavigatingRef.current = true;
|
|
453
|
-
if (observerRef.current) {
|
|
454
|
-
observerRef.current.disconnect();
|
|
455
|
-
}
|
|
456
473
|
if (newLocale !== locale) {
|
|
457
474
|
setLocaleState(newLocale);
|
|
458
475
|
// Clear any pending navigation
|
|
@@ -477,17 +494,16 @@ navigateRef, // For path mode routing
|
|
|
477
494
|
const originalPushState = history.pushState;
|
|
478
495
|
const originalReplaceState = history.replaceState;
|
|
479
496
|
const handleNavigation = () => {
|
|
497
|
+
if (isInternalNavigationRef.current)
|
|
498
|
+
return;
|
|
480
499
|
const newLocale = detectLocale();
|
|
481
500
|
const previousLocale = locale;
|
|
482
501
|
// Show navigation overlay immediately
|
|
483
502
|
if (locale !== defaultLocale || newLocale !== defaultLocale) {
|
|
484
503
|
setIsNavigationLoading(true);
|
|
485
504
|
}
|
|
486
|
-
// SET NAVIGATION FLAG
|
|
505
|
+
// SET NAVIGATION FLAG (MutationObserver callback will ignore while navigating)
|
|
487
506
|
isNavigatingRef.current = true;
|
|
488
|
-
if (observerRef.current) {
|
|
489
|
-
observerRef.current.disconnect();
|
|
490
|
-
}
|
|
491
507
|
// Clear any pending navigation translation
|
|
492
508
|
if (navigationTimeout)
|
|
493
509
|
clearTimeout(navigationTimeout);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
-
import { BrowserRouter, Routes, Route, Navigate, Outlet, useParams, useNavigate } from 'react-router-dom';
|
|
2
|
+
import { BrowserRouter, Routes, Route, Navigate, Outlet, useLocation, useParams, useNavigate } from 'react-router-dom';
|
|
3
3
|
/**
|
|
4
4
|
* NavigateExporter - Internal component that exports navigate function via ref
|
|
5
5
|
*/
|
|
@@ -17,13 +17,27 @@ function NavigateExporter({ navigateRef }) {
|
|
|
17
17
|
*/
|
|
18
18
|
function LangGuard({ defaultLang, langs }) {
|
|
19
19
|
const { lang } = useParams();
|
|
20
|
+
const location = useLocation();
|
|
20
21
|
// If invalid language, redirect to default
|
|
21
22
|
if (!lang || !langs.includes(lang)) {
|
|
22
|
-
|
|
23
|
+
const parts = location.pathname.split('/').filter(Boolean);
|
|
24
|
+
// Drop the invalid locale segment (first)
|
|
25
|
+
if (parts.length > 0)
|
|
26
|
+
parts.shift();
|
|
27
|
+
const rest = parts.join('/');
|
|
28
|
+
const nextPath = `/${defaultLang}${rest ? `/${rest}` : ''}${location.search}${location.hash}`;
|
|
29
|
+
return React.createElement(Navigate, { to: nextPath, replace: true });
|
|
23
30
|
}
|
|
24
31
|
// Valid language - render children (user's routes)
|
|
25
32
|
return React.createElement(Outlet, { context: { lang } });
|
|
26
33
|
}
|
|
34
|
+
function RedirectToDefaultLang({ defaultLang }) {
|
|
35
|
+
const location = useLocation();
|
|
36
|
+
const nextPath = location.pathname === '/' || location.pathname === ''
|
|
37
|
+
? `/${defaultLang}${location.search}${location.hash}`
|
|
38
|
+
: `/${defaultLang}${location.pathname}${location.search}${location.hash}`;
|
|
39
|
+
return React.createElement(Navigate, { to: nextPath, replace: true });
|
|
40
|
+
}
|
|
27
41
|
/**
|
|
28
42
|
* LangRouter - Drop-in replacement for BrowserRouter that automatically handles language routing
|
|
29
43
|
*
|
|
@@ -56,5 +70,5 @@ export function LangRouter({ children, defaultLang, langs, navigateRef }) {
|
|
|
56
70
|
React.createElement(Route, { path: ":lang/*", element: React.createElement(LangGuard, { defaultLang: defaultLang, langs: langs }) },
|
|
57
71
|
React.createElement(Route, { index: true, element: React.createElement(React.Fragment, null, children) }),
|
|
58
72
|
React.createElement(Route, { path: "*", element: React.createElement(React.Fragment, null, children) })),
|
|
59
|
-
React.createElement(Route, { path: "*", element: React.createElement(
|
|
73
|
+
React.createElement(Route, { path: "*", element: React.createElement(RedirectToDefaultLang, { defaultLang: defaultLang }) }))));
|
|
60
74
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,4 +22,5 @@ export { useLangNavigate } from './hooks/useLangNavigate';
|
|
|
22
22
|
export { useLovalingo } from './hooks/useLovalingo';
|
|
23
23
|
export { useLovalingoTranslate } from './hooks/useLovalingoTranslate';
|
|
24
24
|
export { useLovalingoEdit } from './hooks/useLovalingoEdit';
|
|
25
|
+
export { VERSION } from './version';
|
|
25
26
|
export type { LovalingoConfig, LovalingoContextValue, Translation, Exclusion, HashTranslation } from './types';
|
package/dist/index.js
CHANGED
|
@@ -25,3 +25,5 @@ export { useLangNavigate } from './hooks/useLangNavigate';
|
|
|
25
25
|
export { useLovalingo } from './hooks/useLovalingo';
|
|
26
26
|
export { useLovalingoTranslate } from './hooks/useLovalingoTranslate';
|
|
27
27
|
export { useLovalingoEdit } from './hooks/useLovalingoEdit';
|
|
28
|
+
// Version
|
|
29
|
+
export { VERSION } from './version';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION = "0.0.15";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = "0.0.15";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovalingo/lovalingo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "React translation library with automatic routing, real-time AI translation, and zero-flash rendering. One-line language routing setup.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|