@lingo.dev/compiler 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/LICENSE.md +201 -0
- package/README.md +192 -0
- package/build/_virtual/rolldown_runtime.cjs +29 -0
- package/build/_virtual/rolldown_runtime.mjs +7 -0
- package/build/index.cjs +0 -0
- package/build/index.d.cts +2 -0
- package/build/index.d.mts +2 -0
- package/build/index.mjs +1 -0
- package/build/metadata/manager.cjs +131 -0
- package/build/metadata/manager.mjs +123 -0
- package/build/metadata/manager.mjs.map +1 -0
- package/build/plugin/build-translator.cjs +198 -0
- package/build/plugin/build-translator.mjs +196 -0
- package/build/plugin/build-translator.mjs.map +1 -0
- package/build/plugin/cleanup.cjs +20 -0
- package/build/plugin/cleanup.mjs +20 -0
- package/build/plugin/cleanup.mjs.map +1 -0
- package/build/plugin/next-compiler-loader.cjs +41 -0
- package/build/plugin/next-compiler-loader.d.cts +12 -0
- package/build/plugin/next-compiler-loader.d.cts.map +1 -0
- package/build/plugin/next-compiler-loader.d.mts +13 -0
- package/build/plugin/next-compiler-loader.d.mts.map +1 -0
- package/build/plugin/next-compiler-loader.mjs +42 -0
- package/build/plugin/next-compiler-loader.mjs.map +1 -0
- package/build/plugin/next-config-loader.cjs +13 -0
- package/build/plugin/next-config-loader.d.cts +8 -0
- package/build/plugin/next-config-loader.d.cts.map +1 -0
- package/build/plugin/next-config-loader.d.mts +9 -0
- package/build/plugin/next-config-loader.d.mts.map +1 -0
- package/build/plugin/next-config-loader.mjs +14 -0
- package/build/plugin/next-config-loader.mjs.map +1 -0
- package/build/plugin/next-locale-client-loader.cjs +9 -0
- package/build/plugin/next-locale-client-loader.d.cts +8 -0
- package/build/plugin/next-locale-client-loader.d.cts.map +1 -0
- package/build/plugin/next-locale-client-loader.d.mts +9 -0
- package/build/plugin/next-locale-client-loader.d.mts.map +1 -0
- package/build/plugin/next-locale-client-loader.mjs +10 -0
- package/build/plugin/next-locale-client-loader.mjs.map +1 -0
- package/build/plugin/next-locale-server-loader.cjs +9 -0
- package/build/plugin/next-locale-server-loader.d.cts +8 -0
- package/build/plugin/next-locale-server-loader.d.cts.map +1 -0
- package/build/plugin/next-locale-server-loader.d.mts +9 -0
- package/build/plugin/next-locale-server-loader.d.mts.map +1 -0
- package/build/plugin/next-locale-server-loader.mjs +10 -0
- package/build/plugin/next-locale-server-loader.mjs.map +1 -0
- package/build/plugin/next.cjs +220 -0
- package/build/plugin/next.d.cts +9 -0
- package/build/plugin/next.d.cts.map +1 -0
- package/build/plugin/next.d.mts +9 -0
- package/build/plugin/next.d.mts.map +1 -0
- package/build/plugin/next.mjs +222 -0
- package/build/plugin/next.mjs.map +1 -0
- package/build/plugin/transform/babel-compat.cjs +13 -0
- package/build/plugin/transform/babel-compat.mjs +10 -0
- package/build/plugin/transform/babel-compat.mjs.map +1 -0
- package/build/plugin/transform/index.cjs +44 -0
- package/build/plugin/transform/index.mjs +42 -0
- package/build/plugin/transform/index.mjs.map +1 -0
- package/build/plugin/transform/metadata.cjs +142 -0
- package/build/plugin/transform/metadata.mjs +141 -0
- package/build/plugin/transform/metadata.mjs.map +1 -0
- package/build/plugin/transform/parse-override.cjs +145 -0
- package/build/plugin/transform/parse-override.mjs +144 -0
- package/build/plugin/transform/parse-override.mjs.map +1 -0
- package/build/plugin/transform/process-file.cjs +391 -0
- package/build/plugin/transform/process-file.mjs +390 -0
- package/build/plugin/transform/process-file.mjs.map +1 -0
- package/build/plugin/transform/use-i18n.cjs +8 -0
- package/build/plugin/transform/use-i18n.mjs +7 -0
- package/build/plugin/transform/use-i18n.mjs.map +1 -0
- package/build/plugin/transform/utils.cjs +205 -0
- package/build/plugin/transform/utils.mjs +192 -0
- package/build/plugin/transform/utils.mjs.map +1 -0
- package/build/plugin/unplugin.cjs +188 -0
- package/build/plugin/unplugin.d.cts +8 -0
- package/build/plugin/unplugin.d.cts.map +1 -0
- package/build/plugin/unplugin.d.mts +8 -0
- package/build/plugin/unplugin.d.mts.map +1 -0
- package/build/plugin/unplugin.mjs +186 -0
- package/build/plugin/unplugin.mjs.map +1 -0
- package/build/plugin/vite.cjs +28 -0
- package/build/plugin/vite.d.cts +9 -0
- package/build/plugin/vite.d.cts.map +1 -0
- package/build/plugin/vite.d.mts +9 -0
- package/build/plugin/vite.d.mts.map +1 -0
- package/build/plugin/vite.mjs +29 -0
- package/build/plugin/vite.mjs.map +1 -0
- package/build/plugin/webpack.cjs +27 -0
- package/build/plugin/webpack.d.cts +8 -0
- package/build/plugin/webpack.d.cts.map +1 -0
- package/build/plugin/webpack.d.mts +8 -0
- package/build/plugin/webpack.d.mts.map +1 -0
- package/build/plugin/webpack.mjs +28 -0
- package/build/plugin/webpack.mjs.map +1 -0
- package/build/react/client/index.cjs +9 -0
- package/build/react/client/index.d.cts +5 -0
- package/build/react/client/index.d.mts +5 -0
- package/build/react/client/index.mjs +6 -0
- package/build/react/client/useTranslation.cjs +71 -0
- package/build/react/client/useTranslation.d.cts +42 -0
- package/build/react/client/useTranslation.d.cts.map +1 -0
- package/build/react/client/useTranslation.d.mts +42 -0
- package/build/react/client/useTranslation.d.mts.map +1 -0
- package/build/react/client/useTranslation.mjs +71 -0
- package/build/react/client/useTranslation.mjs.map +1 -0
- package/build/react/next/client.cjs +25 -0
- package/build/react/next/client.d.cts +9 -0
- package/build/react/next/client.d.cts.map +1 -0
- package/build/react/next/client.d.mts +9 -0
- package/build/react/next/client.d.mts.map +1 -0
- package/build/react/next/client.mjs +24 -0
- package/build/react/next/client.mjs.map +1 -0
- package/build/react/next/cookie-locale-resolver.cjs +29 -0
- package/build/react/next/cookie-locale-resolver.d.cts +33 -0
- package/build/react/next/cookie-locale-resolver.d.cts.map +1 -0
- package/build/react/next/cookie-locale-resolver.d.mts +33 -0
- package/build/react/next/cookie-locale-resolver.d.mts.map +1 -0
- package/build/react/next/cookie-locale-resolver.mjs +29 -0
- package/build/react/next/cookie-locale-resolver.mjs.map +1 -0
- package/build/react/next/server.cjs +21 -0
- package/build/react/next/server.d.cts +13 -0
- package/build/react/next/server.d.cts.map +1 -0
- package/build/react/next/server.d.mts +14 -0
- package/build/react/next/server.d.mts.map +1 -0
- package/build/react/next/server.mjs +20 -0
- package/build/react/next/server.mjs.map +1 -0
- package/build/react/server/ServerLingoProvider.cjs +19 -0
- package/build/react/server/ServerLingoProvider.d.cts +12 -0
- package/build/react/server/ServerLingoProvider.d.cts.map +1 -0
- package/build/react/server/ServerLingoProvider.d.mts +12 -0
- package/build/react/server/ServerLingoProvider.d.mts.map +1 -0
- package/build/react/server/ServerLingoProvider.mjs +19 -0
- package/build/react/server/ServerLingoProvider.mjs.map +1 -0
- package/build/react/server/index.cjs +7 -0
- package/build/react/server/index.d.cts +4 -0
- package/build/react/server/index.d.mts +4 -0
- package/build/react/server/index.mjs +5 -0
- package/build/react/server/useTranslation.cjs +60 -0
- package/build/react/server/useTranslation.d.cts +36 -0
- package/build/react/server/useTranslation.d.cts.map +1 -0
- package/build/react/server/useTranslation.d.mts +36 -0
- package/build/react/server/useTranslation.d.mts.map +1 -0
- package/build/react/server/useTranslation.mjs +60 -0
- package/build/react/server/useTranslation.mjs.map +1 -0
- package/build/react/server-only/index.cjs +42 -0
- package/build/react/server-only/index.d.cts +38 -0
- package/build/react/server-only/index.d.cts.map +1 -0
- package/build/react/server-only/index.d.mts +38 -0
- package/build/react/server-only/index.d.mts.map +1 -0
- package/build/react/server-only/index.mjs +42 -0
- package/build/react/server-only/index.mjs.map +1 -0
- package/build/react/server-only/translations.cjs +85 -0
- package/build/react/server-only/translations.mjs +85 -0
- package/build/react/server-only/translations.mjs.map +1 -0
- package/build/react/shared/LingoContext.cjs +14 -0
- package/build/react/shared/LingoContext.d.cts +41 -0
- package/build/react/shared/LingoContext.d.cts.map +1 -0
- package/build/react/shared/LingoContext.d.mts +41 -0
- package/build/react/shared/LingoContext.d.mts.map +1 -0
- package/build/react/shared/LingoContext.mjs +13 -0
- package/build/react/shared/LingoContext.mjs.map +1 -0
- package/build/react/shared/LingoProvider.cjs +274 -0
- package/build/react/shared/LingoProvider.d.cts +76 -0
- package/build/react/shared/LingoProvider.d.cts.map +1 -0
- package/build/react/shared/LingoProvider.d.mts +76 -0
- package/build/react/shared/LingoProvider.d.mts.map +1 -0
- package/build/react/shared/LingoProvider.mjs +274 -0
- package/build/react/shared/LingoProvider.mjs.map +1 -0
- package/build/react/shared/LocaleSwitcher.cjs +61 -0
- package/build/react/shared/LocaleSwitcher.d.cts +71 -0
- package/build/react/shared/LocaleSwitcher.d.cts.map +1 -0
- package/build/react/shared/LocaleSwitcher.d.mts +71 -0
- package/build/react/shared/LocaleSwitcher.d.mts.map +1 -0
- package/build/react/shared/LocaleSwitcher.mjs +61 -0
- package/build/react/shared/LocaleSwitcher.mjs.map +1 -0
- package/build/react/shared/render-rich-text.cjs +55 -0
- package/build/react/shared/render-rich-text.d.cts +17 -0
- package/build/react/shared/render-rich-text.d.cts.map +1 -0
- package/build/react/shared/render-rich-text.d.mts +17 -0
- package/build/react/shared/render-rich-text.d.mts.map +1 -0
- package/build/react/shared/render-rich-text.mjs +54 -0
- package/build/react/shared/render-rich-text.mjs.map +1 -0
- package/build/react/shared/utils.cjs +34 -0
- package/build/react/shared/utils.mjs +35 -0
- package/build/react/shared/utils.mjs.map +1 -0
- package/build/react/types.d.cts +16 -0
- package/build/react/types.d.cts.map +1 -0
- package/build/react/types.d.mts +16 -0
- package/build/react/types.d.mts.map +1 -0
- package/build/translation-server/logger.cjs +37 -0
- package/build/translation-server/logger.mjs +37 -0
- package/build/translation-server/logger.mjs.map +1 -0
- package/build/translation-server/translation-server.cjs +547 -0
- package/build/translation-server/translation-server.mjs +544 -0
- package/build/translation-server/translation-server.mjs.map +1 -0
- package/build/translation-server/ws-events.cjs +15 -0
- package/build/translation-server/ws-events.mjs +15 -0
- package/build/translation-server/ws-events.mjs.map +1 -0
- package/build/translators/api.cjs +12 -0
- package/build/translators/api.mjs +12 -0
- package/build/translators/api.mjs.map +1 -0
- package/build/translators/cache-factory.cjs +26 -0
- package/build/translators/cache-factory.mjs +27 -0
- package/build/translators/cache-factory.mjs.map +1 -0
- package/build/translators/lingo/model-factory.cjs +179 -0
- package/build/translators/lingo/model-factory.mjs +174 -0
- package/build/translators/lingo/model-factory.mjs.map +1 -0
- package/build/translators/lingo/prompt.cjs +43 -0
- package/build/translators/lingo/prompt.mjs +43 -0
- package/build/translators/lingo/prompt.mjs.map +1 -0
- package/build/translators/lingo/service.cjs +152 -0
- package/build/translators/lingo/service.mjs +152 -0
- package/build/translators/lingo/service.mjs.map +1 -0
- package/build/translators/lingo/shots.cjs +28 -0
- package/build/translators/lingo/shots.mjs +28 -0
- package/build/translators/lingo/shots.mjs.map +1 -0
- package/build/translators/local-cache.cjs +115 -0
- package/build/translators/local-cache.mjs +113 -0
- package/build/translators/local-cache.mjs.map +1 -0
- package/build/translators/parse-xml.cjs +109 -0
- package/build/translators/parse-xml.mjs +108 -0
- package/build/translators/parse-xml.mjs.map +1 -0
- package/build/translators/pluralization/icu-validator.cjs +36 -0
- package/build/translators/pluralization/icu-validator.mjs +36 -0
- package/build/translators/pluralization/icu-validator.mjs.map +1 -0
- package/build/translators/pluralization/pattern-detector.cjs +25 -0
- package/build/translators/pluralization/pattern-detector.mjs +25 -0
- package/build/translators/pluralization/pattern-detector.mjs.map +1 -0
- package/build/translators/pluralization/prompt.cjs +98 -0
- package/build/translators/pluralization/prompt.mjs +98 -0
- package/build/translators/pluralization/prompt.mjs.map +1 -0
- package/build/translators/pluralization/service.cjs +247 -0
- package/build/translators/pluralization/service.mjs +247 -0
- package/build/translators/pluralization/service.mjs.map +1 -0
- package/build/translators/pluralization/shots.cjs +53 -0
- package/build/translators/pluralization/shots.mjs +53 -0
- package/build/translators/pluralization/shots.mjs.map +1 -0
- package/build/translators/pluralization/types.d.cts +17 -0
- package/build/translators/pluralization/types.d.cts.map +1 -0
- package/build/translators/pluralization/types.d.mts +17 -0
- package/build/translators/pluralization/types.d.mts.map +1 -0
- package/build/translators/pseudotranslator/index.cjs +129 -0
- package/build/translators/pseudotranslator/index.mjs +129 -0
- package/build/translators/pseudotranslator/index.mjs.map +1 -0
- package/build/translators/translation-service.cjs +182 -0
- package/build/translators/translation-service.mjs +183 -0
- package/build/translators/translation-service.mjs.map +1 -0
- package/build/translators/translator-factory.cjs +49 -0
- package/build/translators/translator-factory.mjs +50 -0
- package/build/translators/translator-factory.mjs.map +1 -0
- package/build/types.d.cts +161 -0
- package/build/types.d.cts.map +1 -0
- package/build/types.d.mts +161 -0
- package/build/types.d.mts.map +1 -0
- package/build/utils/config-factory.cjs +58 -0
- package/build/utils/config-factory.mjs +58 -0
- package/build/utils/config-factory.mjs.map +1 -0
- package/build/utils/hash.cjs +17 -0
- package/build/utils/hash.mjs +16 -0
- package/build/utils/hash.mjs.map +1 -0
- package/build/utils/is-valid-locale.cjs +14 -0
- package/build/utils/is-valid-locale.mjs +14 -0
- package/build/utils/is-valid-locale.mjs.map +1 -0
- package/build/utils/logger.cjs +51 -0
- package/build/utils/logger.mjs +50 -0
- package/build/utils/logger.mjs.map +1 -0
- package/build/utils/path-helpers.cjs +49 -0
- package/build/utils/path-helpers.mjs +47 -0
- package/build/utils/path-helpers.mjs.map +1 -0
- package/build/utils/timeout.cjs +42 -0
- package/build/utils/timeout.mjs +41 -0
- package/build/utils/timeout.mjs.map +1 -0
- package/build/virtual/code-generator.cjs +54 -0
- package/build/virtual/code-generator.mjs +53 -0
- package/build/virtual/code-generator.mjs.map +1 -0
- package/build/virtual/config.cjs +10 -0
- package/build/virtual/config.d.cts +9 -0
- package/build/virtual/config.d.cts.map +1 -0
- package/build/virtual/config.d.mts +9 -0
- package/build/virtual/config.d.mts.map +1 -0
- package/build/virtual/config.mjs +8 -0
- package/build/virtual/config.mjs.map +1 -0
- package/build/virtual/locale/client.cjs +23 -0
- package/build/virtual/locale/client.d.cts +19 -0
- package/build/virtual/locale/client.d.cts.map +1 -0
- package/build/virtual/locale/client.d.mts +19 -0
- package/build/virtual/locale/client.d.mts.map +1 -0
- package/build/virtual/locale/client.mjs +22 -0
- package/build/virtual/locale/client.mjs.map +1 -0
- package/build/virtual/locale/server.cjs +13 -0
- package/build/virtual/locale/server.d.cts +13 -0
- package/build/virtual/locale/server.d.cts.map +1 -0
- package/build/virtual/locale/server.d.mts +13 -0
- package/build/virtual/locale/server.d.mts.map +1 -0
- package/build/virtual/locale/server.mjs +13 -0
- package/build/virtual/locale/server.mjs.map +1 -0
- package/build/widget/lingo-dev-widget.cjs +228 -0
- package/build/widget/lingo-dev-widget.mjs +229 -0
- package/build/widget/lingo-dev-widget.mjs.map +1 -0
- package/package.json +189 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { logger } from "../../utils/logger.mjs";
|
|
4
|
+
import { fetchTranslations } from "./utils.mjs";
|
|
5
|
+
import { LingoContext } from "./LingoContext.mjs";
|
|
6
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
7
|
+
import { serverUrl, sourceLocale } from "@lingo.dev/compiler/virtual/config";
|
|
8
|
+
import { getClientLocale, persistLocale } from "@lingo.dev/compiler/virtual/locale/client";
|
|
9
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
|
|
11
|
+
//#region src/react/shared/LingoProvider.tsx
|
|
12
|
+
const noop = () => {};
|
|
13
|
+
const IS_DEV = process.env.NODE_ENV === "development";
|
|
14
|
+
const BATCH_DELAY = 200;
|
|
15
|
+
/**
|
|
16
|
+
* Translation Provider Component
|
|
17
|
+
*
|
|
18
|
+
* Wraps your app to provide translation context to all components.
|
|
19
|
+
* Handles locale switching and on-demand translation loading.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* // In your root layout
|
|
24
|
+
* import { LingoProvider } from '@lingo.dev/compiler-beta/react';
|
|
25
|
+
*
|
|
26
|
+
* export default function RootLayout({ children }) {
|
|
27
|
+
* return (
|
|
28
|
+
* <html>
|
|
29
|
+
* <body>
|
|
30
|
+
* <LingoProvider initialLocale="en">
|
|
31
|
+
* {children}
|
|
32
|
+
* </LingoProvider>
|
|
33
|
+
* </body>
|
|
34
|
+
* </html>
|
|
35
|
+
* );
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
const LingoProvider = IS_DEV ? LingoProvider__Dev : LingoProvider__Prod;
|
|
40
|
+
function LingoProvider__Prod({ initialLocale, initialTranslations = {}, router, children }) {
|
|
41
|
+
const [locale, setLocaleState] = useState(() => {
|
|
42
|
+
if (initialLocale) return initialLocale;
|
|
43
|
+
if (typeof window !== "undefined") return getClientLocale();
|
|
44
|
+
return sourceLocale;
|
|
45
|
+
});
|
|
46
|
+
const [translations, setTranslations] = useState(initialTranslations);
|
|
47
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
48
|
+
logger.debug(`LingoProvider initialized with locale: ${locale}`, initialTranslations);
|
|
49
|
+
/**
|
|
50
|
+
* Update HTML lang attribute when locale changes
|
|
51
|
+
* This ensures screen readers and SEO understand the page language
|
|
52
|
+
*/
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (typeof document !== "undefined") document.documentElement.lang = locale;
|
|
55
|
+
}, [locale]);
|
|
56
|
+
/**
|
|
57
|
+
* Load translations from public/translations/{locale}.json
|
|
58
|
+
* Lazy loads on-demand for SPAs
|
|
59
|
+
*/
|
|
60
|
+
const loadTranslations = useCallback(async (targetLocale) => {
|
|
61
|
+
if (Object.keys(initialTranslations).length > 0) return;
|
|
62
|
+
setIsLoading(true);
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(`/translations/${targetLocale}.json`);
|
|
65
|
+
if (!response.ok) throw new Error(`Failed to load translations for ${targetLocale}: ${response.statusText}`);
|
|
66
|
+
const data = await response.json();
|
|
67
|
+
setTranslations(data.entries || data);
|
|
68
|
+
logger.debug(`Loaded translations for ${targetLocale}:`, Object.keys(data.entries || data).length);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.error(`Failed to load translations for ${targetLocale}:`, error);
|
|
71
|
+
setTranslations({});
|
|
72
|
+
} finally {
|
|
73
|
+
setIsLoading(false);
|
|
74
|
+
}
|
|
75
|
+
}, [initialTranslations]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (Object.keys(initialTranslations).length === 0) loadTranslations(locale);
|
|
78
|
+
}, []);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (router) setTranslations(initialTranslations);
|
|
81
|
+
}, [initialTranslations, router]);
|
|
82
|
+
/**
|
|
83
|
+
* Change locale
|
|
84
|
+
* - For Next.js SSR: triggers server re-render via router.refresh()
|
|
85
|
+
* - For SPAs: lazy loads translations from /translations/{locale}.json
|
|
86
|
+
*/
|
|
87
|
+
const setLocale = useCallback(async (newLocale) => {
|
|
88
|
+
persistLocale(newLocale);
|
|
89
|
+
setLocaleState(newLocale);
|
|
90
|
+
if (router) router.refresh();
|
|
91
|
+
else await loadTranslations(newLocale);
|
|
92
|
+
}, [router, loadTranslations]);
|
|
93
|
+
return /* @__PURE__ */ jsx(LingoContext.Provider, {
|
|
94
|
+
value: {
|
|
95
|
+
locale,
|
|
96
|
+
setLocale,
|
|
97
|
+
translations,
|
|
98
|
+
registerHashes: noop,
|
|
99
|
+
isLoading,
|
|
100
|
+
sourceLocale
|
|
101
|
+
},
|
|
102
|
+
children
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function LingoProvider__Dev({ initialLocale, initialTranslations = {}, router, devWidget, children }) {
|
|
106
|
+
const [locale, setLocaleState] = useState(() => {
|
|
107
|
+
if (initialLocale) return initialLocale;
|
|
108
|
+
return getClientLocale();
|
|
109
|
+
});
|
|
110
|
+
const [translations, setTranslations] = useState(initialTranslations);
|
|
111
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
112
|
+
const [allSeenHashes, setAllSeenHashes] = useState(/* @__PURE__ */ new Set());
|
|
113
|
+
const registeredHashesRef = useRef(/* @__PURE__ */ new Set());
|
|
114
|
+
const pendingHashesRef = useRef(/* @__PURE__ */ new Set());
|
|
115
|
+
const erroredHashesRef = useRef(/* @__PURE__ */ new Set());
|
|
116
|
+
const batchTimerRef = useRef(null);
|
|
117
|
+
const translationsRef = useRef(initialTranslations);
|
|
118
|
+
const localeRef = useRef(locale);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
translationsRef.current = translations;
|
|
121
|
+
}, [translations]);
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
localeRef.current = locale;
|
|
124
|
+
}, [locale]);
|
|
125
|
+
/**
|
|
126
|
+
* Update HTML lang attribute when locale changes
|
|
127
|
+
* This ensures screen readers and SEO understand the page language
|
|
128
|
+
*/
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (typeof document !== "undefined") document.documentElement.lang = locale;
|
|
131
|
+
}, [locale]);
|
|
132
|
+
/**
|
|
133
|
+
* Register a hash as being used in a component
|
|
134
|
+
* Called during render - must not trigger state updates immediately
|
|
135
|
+
*/
|
|
136
|
+
const registerHashes = useCallback((hashes) => {
|
|
137
|
+
let wasNew = false;
|
|
138
|
+
hashes.forEach((hash) => {
|
|
139
|
+
wasNew = wasNew || !registeredHashesRef.current.has(hash);
|
|
140
|
+
registeredHashesRef.current.add(hash);
|
|
141
|
+
});
|
|
142
|
+
logger.debug(`Registering hashes: ${hashes.join(", ")}. Registered hashes: ${registeredHashesRef.current.values()}. wasNew: ${wasNew}`);
|
|
143
|
+
if (wasNew) setAllSeenHashes((prev) => {
|
|
144
|
+
const next = prev.union(new Set(hashes));
|
|
145
|
+
logger.debug(`New allSeenHashes: ${[...next.values()]}`);
|
|
146
|
+
return next;
|
|
147
|
+
});
|
|
148
|
+
}, []);
|
|
149
|
+
/**
|
|
150
|
+
* Check for missing translations and request them (batched)
|
|
151
|
+
* This runs when allSeenHashes changes (hot reload or new components mount)
|
|
152
|
+
*/
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
logger.debug(`LingoProvider checking translations for locale ${locale}, seen hashes: ${allSeenHashes.size}`);
|
|
155
|
+
const missingHashes = [];
|
|
156
|
+
logger.debug("allSeenHashes: ", [...allSeenHashes.values()], [...pendingHashesRef.current.values()]);
|
|
157
|
+
for (const hash of allSeenHashes) if (!translations[hash] && !pendingHashesRef.current.has(hash) && !erroredHashesRef.current.has(hash)) {
|
|
158
|
+
missingHashes.push(hash);
|
|
159
|
+
pendingHashesRef.current.add(hash);
|
|
160
|
+
}
|
|
161
|
+
logger.debug("Missing hashes: ", missingHashes.join(","));
|
|
162
|
+
if (missingHashes.length === 0 && localeRef.current == locale) return;
|
|
163
|
+
logger.debug(`Requesting translations for ${missingHashes.length} hashes in locale ${locale}`);
|
|
164
|
+
if (batchTimerRef.current) clearTimeout(batchTimerRef.current);
|
|
165
|
+
batchTimerRef.current = setTimeout(async () => {
|
|
166
|
+
const hashesToFetch = Array.from(pendingHashesRef.current);
|
|
167
|
+
pendingHashesRef.current.clear();
|
|
168
|
+
logger.debug(`Fetching translations for ${hashesToFetch.length} hashes`);
|
|
169
|
+
if (hashesToFetch.length === 0) return;
|
|
170
|
+
setIsLoading(true);
|
|
171
|
+
try {
|
|
172
|
+
const newTranslations = await fetchTranslations(localeRef.current, hashesToFetch, serverUrl);
|
|
173
|
+
logger.debug(`Fetched translations for ${hashesToFetch.length} hashes:`, newTranslations);
|
|
174
|
+
const receivedHashes = new Set(Object.keys(newTranslations));
|
|
175
|
+
const missingHashes$1 = hashesToFetch.filter((hash) => !receivedHashes.has(hash));
|
|
176
|
+
if (missingHashes$1.length > 0) {
|
|
177
|
+
logger.warn(`Server did not return translations for ${missingHashes$1.length} hashes: ${missingHashes$1.join(", ")}`);
|
|
178
|
+
for (const hash of missingHashes$1) erroredHashesRef.current.add(hash);
|
|
179
|
+
}
|
|
180
|
+
setTranslations((prev) => ({
|
|
181
|
+
...prev,
|
|
182
|
+
...newTranslations
|
|
183
|
+
}));
|
|
184
|
+
for (const hash of hashesToFetch) registeredHashesRef.current.add(hash);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
logger.warn(`Failed to fetch translations from translation server: ${error}.`);
|
|
187
|
+
for (const hash of hashesToFetch) {
|
|
188
|
+
pendingHashesRef.current.delete(hash);
|
|
189
|
+
erroredHashesRef.current.add(hash);
|
|
190
|
+
}
|
|
191
|
+
} finally {
|
|
192
|
+
setIsLoading(false);
|
|
193
|
+
}
|
|
194
|
+
}, BATCH_DELAY);
|
|
195
|
+
}, [
|
|
196
|
+
allSeenHashes,
|
|
197
|
+
locale,
|
|
198
|
+
translations
|
|
199
|
+
]);
|
|
200
|
+
/**
|
|
201
|
+
* Clear batch timer on unmount
|
|
202
|
+
*/
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
return () => {
|
|
205
|
+
if (batchTimerRef.current) clearTimeout(batchTimerRef.current);
|
|
206
|
+
};
|
|
207
|
+
}, []);
|
|
208
|
+
/**
|
|
209
|
+
* Change locale and load translations dynamically
|
|
210
|
+
*/
|
|
211
|
+
const setLocale = useCallback(async (newLocale) => {
|
|
212
|
+
persistLocale(newLocale);
|
|
213
|
+
setLocaleState(newLocale);
|
|
214
|
+
if (router) router.refresh();
|
|
215
|
+
setIsLoading(true);
|
|
216
|
+
const startTime = performance.now();
|
|
217
|
+
try {
|
|
218
|
+
logger.info(`Fetching translations for locale: ${newLocale}. Server url: ${serverUrl}`);
|
|
219
|
+
const translatedDict = await fetchTranslations(newLocale, [], serverUrl);
|
|
220
|
+
const endTime = performance.now();
|
|
221
|
+
logger.info(`Translation fetch complete for ${newLocale} in ${(endTime - startTime).toFixed(2)}ms`);
|
|
222
|
+
const allTranslations = translatedDict.entries || {};
|
|
223
|
+
logger.debug(`Translations loaded for ${newLocale}:`, allTranslations);
|
|
224
|
+
setTranslations(allTranslations);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
logger.error(`Failed to load translations for ${newLocale}:`, error);
|
|
227
|
+
setTranslations({});
|
|
228
|
+
} finally {
|
|
229
|
+
setIsLoading(false);
|
|
230
|
+
}
|
|
231
|
+
}, [router]);
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
if (devWidget?.enabled !== false) import("../../widget/lingo-dev-widget.mjs").catch((err) => {
|
|
234
|
+
logger.error("Failed to load dev widget:", err, err.message);
|
|
235
|
+
});
|
|
236
|
+
}, [devWidget?.enabled]);
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
if (typeof window !== "undefined" && devWidget?.enabled !== false) {
|
|
239
|
+
window.__LINGO_DEV_STATE__ = {
|
|
240
|
+
isLoading,
|
|
241
|
+
locale,
|
|
242
|
+
sourceLocale,
|
|
243
|
+
pendingCount: pendingHashesRef.current.size,
|
|
244
|
+
position: devWidget?.position || "bottom-left"
|
|
245
|
+
};
|
|
246
|
+
window.__LINGO_DEV_WS_URL__ = serverUrl;
|
|
247
|
+
window.__LINGO_DEV_UPDATE__?.();
|
|
248
|
+
}
|
|
249
|
+
}, [
|
|
250
|
+
isLoading,
|
|
251
|
+
locale,
|
|
252
|
+
sourceLocale,
|
|
253
|
+
devWidget
|
|
254
|
+
]);
|
|
255
|
+
return /* @__PURE__ */ jsx(LingoContext.Provider, {
|
|
256
|
+
value: {
|
|
257
|
+
locale,
|
|
258
|
+
setLocale,
|
|
259
|
+
translations,
|
|
260
|
+
registerHashes,
|
|
261
|
+
isLoading,
|
|
262
|
+
sourceLocale,
|
|
263
|
+
_devStats: {
|
|
264
|
+
pendingCount: pendingHashesRef.current.size,
|
|
265
|
+
totalRegisteredCount: registeredHashesRef.current.size
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
children
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
//#endregion
|
|
273
|
+
export { LingoProvider };
|
|
274
|
+
//# sourceMappingURL=LingoProvider.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LingoProvider.mjs","names":["missingHashes: string[]","missingHashes"],"sources":["../../../src/react/shared/LingoProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type PropsWithChildren,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { logger } from \"../../utils/logger\";\nimport type { LingoDevState } from \"../../widget/types\";\nimport { fetchTranslations } from \"./utils\";\nimport { serverUrl, sourceLocale } from \"@lingo.dev/compiler/virtual/config\";\nimport {\n getClientLocale,\n persistLocale,\n} from \"@lingo.dev/compiler/virtual/locale/client\";\nimport { LingoContext } from \"./LingoContext\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\n\nconst noop = () => {};\n\n/**\n * Translation provider props\n */\nexport type LingoProviderProps = PropsWithChildren<{\n /**\n * Initial locale to use\n */\n initialLocale?: LocaleCode;\n\n /**\n * Initial translations (pre-loaded)\n */\n initialTranslations?: Record<string, string>;\n\n /**\n * Optional router instance for Next.js integration\n * If provided, calls router.refresh() after locale change\n * This ensures Server Components re-render with new locale\n */\n router?: { refresh: () => void };\n\n /**\n * Development widget configuration\n */\n devWidget?: {\n /**\n * Enable/disable widget (default: true in dev mode)\n * Set to false to opt-out\n */\n enabled?: boolean;\n\n /**\n * Widget position on screen\n * @default 'bottom-left'\n */\n position?: \"bottom-left\" | \"bottom-right\" | \"top-left\" | \"top-right\";\n };\n}>;\n\nconst IS_DEV = process.env.NODE_ENV === \"development\";\nconst BATCH_DELAY = 200;\n\n/**\n * Translation Provider Component\n *\n * Wraps your app to provide translation context to all components.\n * Handles locale switching and on-demand translation loading.\n *\n * @example\n * ```tsx\n * // In your root layout\n * import { LingoProvider } from '@lingo.dev/compiler-beta/react';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <body>\n * <LingoProvider initialLocale=\"en\">\n * {children}\n * </LingoProvider>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\n// Export the appropriate provider directly\nexport const LingoProvider = IS_DEV ? LingoProvider__Dev : LingoProvider__Prod;\n\nfunction LingoProvider__Prod({\n initialLocale,\n initialTranslations = {},\n router,\n children,\n}: LingoProviderProps) {\n // Use client locale detection if no initialLocale provided\n const [locale, setLocaleState] = useState<LocaleCode>(() => {\n if (initialLocale) return initialLocale;\n // Only detect on client-side (not during SSR)\n if (typeof window !== \"undefined\") {\n return getClientLocale();\n }\n return sourceLocale;\n });\n const [translations, setTranslations] =\n useState<Record<string, string>>(initialTranslations);\n const [isLoading, setIsLoading] = useState(false);\n\n logger.debug(\n `LingoProvider initialized with locale: ${locale}`,\n initialTranslations,\n );\n\n /**\n * Update HTML lang attribute when locale changes\n * This ensures screen readers and SEO understand the page language\n */\n useEffect(() => {\n if (typeof document !== \"undefined\") {\n document.documentElement.lang = locale;\n }\n }, [locale]);\n\n /**\n * Load translations from public/translations/{locale}.json\n * Lazy loads on-demand for SPAs\n */\n const loadTranslations = useCallback(\n async (targetLocale: LocaleCode) => {\n // If we already have initialTranslations (Next.js SSR), don't fetch\n if (Object.keys(initialTranslations).length > 0) {\n return;\n }\n\n setIsLoading(true);\n try {\n const response = await fetch(`/translations/${targetLocale}.json`);\n if (!response.ok) {\n throw new Error(\n `Failed to load translations for ${targetLocale}: ${response.statusText}`,\n );\n }\n\n const data = await response.json();\n // Translation files have format: { version, locale, entries: {...} }\n setTranslations(data.entries || data);\n logger.debug(\n `Loaded translations for ${targetLocale}:`,\n Object.keys(data.entries || data).length,\n );\n } catch (error) {\n logger.error(`Failed to load translations for ${targetLocale}:`, error);\n // Fallback to empty translations\n setTranslations({});\n } finally {\n setIsLoading(false);\n }\n },\n [initialTranslations],\n );\n\n // Load translations on mount if not provided via initialTranslations\n useEffect(() => {\n if (Object.keys(initialTranslations).length === 0) {\n loadTranslations(locale);\n }\n }, []); // Only run on mount\n\n useEffect(() => {\n // TODO (AleksandrSl 08/12/2025): More elegant solution required.\n // This is used to update the client part when next app changes locale\n if (router) {\n setTranslations(initialTranslations);\n }\n }, [initialTranslations, router]);\n\n /**\n * Change locale\n * - For Next.js SSR: triggers server re-render via router.refresh()\n * - For SPAs: lazy loads translations from /translations/{locale}.json\n */\n const setLocale = useCallback(\n async (newLocale: LocaleCode) => {\n // 1. Persist to cookie so server can read it on next render\n persistLocale(newLocale);\n\n // 2. Update local state for immediate UI feedback\n setLocaleState(newLocale);\n\n // 3a. Next.js pattern: Trigger server re-render\n if (router) {\n router.refresh();\n }\n // 3b. SPA pattern: Lazy load translations\n else {\n await loadTranslations(newLocale);\n }\n },\n [router, loadTranslations],\n );\n\n return (\n <LingoContext.Provider\n value={{\n locale,\n setLocale,\n translations,\n registerHashes: noop,\n isLoading,\n sourceLocale,\n }}\n >\n {children}\n </LingoContext.Provider>\n );\n}\n\nfunction LingoProvider__Dev({\n initialLocale,\n initialTranslations = {},\n router,\n devWidget,\n children,\n}: LingoProviderProps) {\n // Use client locale detection if no initialLocale provided\n const [locale, setLocaleState] = useState(() => {\n if (initialLocale) {\n return initialLocale;\n }\n return getClientLocale();\n });\n const [translations, setTranslations] =\n useState<Record<string, string>>(initialTranslations);\n const [isLoading, setIsLoading] = useState(false);\n\n const [allSeenHashes, setAllSeenHashes] = useState<Set<string>>(new Set());\n const registeredHashesRef = useRef<Set<string>>(new Set());\n const pendingHashesRef = useRef<Set<string>>(new Set());\n const erroredHashesRef = useRef<Set<string>>(new Set());\n const batchTimerRef = useRef<NodeJS.Timeout | null>(null);\n\n // Use ref to track translations to avoid stale closures\n const translationsRef = useRef<Record<string, string>>(initialTranslations);\n const localeRef = useRef(locale);\n\n useEffect(() => {\n translationsRef.current = translations;\n }, [translations]);\n\n useEffect(() => {\n localeRef.current = locale;\n }, [locale]);\n\n /**\n * Update HTML lang attribute when locale changes\n * This ensures screen readers and SEO understand the page language\n */\n useEffect(() => {\n if (typeof document !== \"undefined\") {\n document.documentElement.lang = locale;\n }\n }, [locale]);\n\n /**\n * Register a hash as being used in a component\n * Called during render - must not trigger state updates immediately\n */\n const registerHashes = useCallback((hashes: string[]) => {\n let wasNew = false;\n hashes.forEach((hash) => {\n wasNew = wasNew || !registeredHashesRef.current.has(hash);\n registeredHashesRef.current.add(hash);\n });\n\n logger.debug(\n `Registering hashes: ${hashes.join(\", \")}. Registered hashes: ${registeredHashesRef.current.values()}. wasNew: ${wasNew}`,\n );\n\n // Schedule a state update for the next tick to track all hashes\n if (wasNew) {\n setAllSeenHashes((prev) => {\n const next = prev.union(new Set(hashes));\n // TODO (AleksandrSl 25/11/2025): Should be a cheaper solution\n logger.debug(`New allSeenHashes: ${[...next.values()]}`);\n return next;\n });\n }\n }, []);\n\n /**\n * Check for missing translations and request them (batched)\n * This runs when allSeenHashes changes (hot reload or new components mount)\n */\n useEffect(() => {\n logger.debug(\n `LingoProvider checking translations for locale ${locale}, seen hashes: ${allSeenHashes.size}`,\n );\n\n // Find hashes that are seen but not translated and not already pending\n const missingHashes: string[] = [];\n logger.debug(\n \"allSeenHashes: \",\n [...allSeenHashes.values()],\n [...pendingHashesRef.current.values()],\n );\n for (const hash of allSeenHashes) {\n if (\n !translations[hash] &&\n !pendingHashesRef.current.has(hash) &&\n !erroredHashesRef.current.has(hash)\n ) {\n missingHashes.push(hash);\n pendingHashesRef.current.add(hash);\n }\n }\n logger.debug(\"Missing hashes: \", missingHashes.join(\",\"));\n\n // If no missing hashes, nothing to do\n if (missingHashes.length === 0 && localeRef.current == locale) return;\n\n logger.debug(\n `Requesting translations for ${missingHashes.length} hashes in locale ${locale}`,\n );\n\n // Cancel existing timer\n if (batchTimerRef.current) {\n clearTimeout(batchTimerRef.current);\n }\n\n // Batch the request\n batchTimerRef.current = setTimeout(async () => {\n const hashesToFetch = Array.from(pendingHashesRef.current);\n pendingHashesRef.current.clear();\n\n logger.debug(`Fetching translations for ${hashesToFetch.length} hashes`);\n if (hashesToFetch.length === 0) return;\n\n setIsLoading(true);\n try {\n const newTranslations = await fetchTranslations(\n localeRef.current,\n hashesToFetch,\n serverUrl,\n );\n\n logger.debug(\n `Fetched translations for ${hashesToFetch.length} hashes:`,\n newTranslations,\n );\n\n const receivedHashes = new Set(Object.keys(newTranslations));\n const missingHashes = hashesToFetch.filter(\n (hash) => !receivedHashes.has(hash),\n );\n\n if (missingHashes.length > 0) {\n logger.warn(\n `Server did not return translations for ${missingHashes.length} hashes: ${missingHashes.join(\", \")}`,\n );\n for (const hash of missingHashes) {\n erroredHashesRef.current.add(hash);\n }\n }\n\n setTranslations((prev) => ({ ...prev, ...newTranslations }));\n for (const hash of hashesToFetch) {\n registeredHashesRef.current.add(hash);\n }\n } catch (error) {\n logger.warn(\n `Failed to fetch translations from translation server: ${error}.`,\n );\n // Remove from pending so they can be retried\n for (const hash of hashesToFetch) {\n pendingHashesRef.current.delete(hash);\n erroredHashesRef.current.add(hash);\n }\n } finally {\n setIsLoading(false);\n }\n }, BATCH_DELAY);\n }, [allSeenHashes, locale, translations]);\n\n /**\n * Clear batch timer on unmount\n */\n useEffect(() => {\n return () => {\n if (batchTimerRef.current) {\n clearTimeout(batchTimerRef.current);\n }\n };\n }, []);\n\n /**\n * Change locale and load translations dynamically\n */\n const setLocale = useCallback(\n async (newLocale: LocaleCode) => {\n // 1. Persist to cookie (unless disabled)\n persistLocale(newLocale);\n\n // 2. Update state\n setLocaleState(newLocale);\n\n // 3. Reload Server Components (if router provided)\n if (router) {\n router.refresh();\n }\n\n // Fetch translations from API endpoint\n setIsLoading(true);\n const startTime = performance.now();\n\n try {\n logger.info(\n `Fetching translations for locale: ${newLocale}. Server url: ${serverUrl}`,\n );\n\n // TODO (AleksandrSl 08/12/2025): We should be fetching the existing cached translations here.\n const translatedDict = await fetchTranslations(\n newLocale,\n [],\n serverUrl,\n );\n\n const endTime = performance.now();\n logger.info(\n `Translation fetch complete for ${newLocale} in ${(endTime - startTime).toFixed(2)}ms`,\n );\n\n // Extract all translations from a dictionary\n const allTranslations = translatedDict.entries || {};\n\n logger.debug(`Translations loaded for ${newLocale}:`, allTranslations);\n\n setTranslations(allTranslations);\n } catch (error) {\n logger.error(`Failed to load translations for ${newLocale}:`, error);\n // Clear translations on error - components will request individually\n setTranslations({});\n } finally {\n setIsLoading(false);\n }\n },\n [router],\n );\n\n // Load widget on client-side only (avoids SSR issues with HTMLElement)\n useEffect(() => {\n if (devWidget?.enabled !== false) {\n // Dynamic import ensures this only runs on the client\n import(\"../../widget/lingo-dev-widget\").catch((err) => {\n logger.error(\"Failed to load dev widget:\", err, err.message);\n });\n }\n }, [devWidget?.enabled]);\n\n // Publish state to window global for Web Component widget\n useEffect(() => {\n if (typeof window !== \"undefined\" && devWidget?.enabled !== false) {\n window.__LINGO_DEV_STATE__ = {\n isLoading,\n locale,\n sourceLocale,\n pendingCount: pendingHashesRef.current.size,\n position: devWidget?.position || \"bottom-left\",\n } satisfies LingoDevState;\n window.__LINGO_DEV_WS_URL__ = serverUrl;\n window.__LINGO_DEV_UPDATE__?.();\n }\n }, [isLoading, locale, sourceLocale, devWidget]);\n\n // TODO (AleksandrSl 24/11/2025): Should I memo the value?\n return (\n <LingoContext.Provider\n value={{\n locale,\n setLocale,\n translations,\n registerHashes,\n isLoading,\n sourceLocale,\n _devStats: {\n pendingCount: pendingHashesRef.current.size,\n totalRegisteredCount: registeredHashesRef.current.size,\n },\n }}\n >\n {children}\n </LingoContext.Provider>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
|
|
4
|
+
const require_LingoContext = require('./LingoContext.cjs');
|
|
5
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
|
|
7
|
+
//#region src/react/shared/LocaleSwitcher.tsx
|
|
8
|
+
/**
|
|
9
|
+
* LocaleSwitcher Component
|
|
10
|
+
*
|
|
11
|
+
* Provides a dropdown to switch between locales.
|
|
12
|
+
* Works with both Next.js and other React frameworks.
|
|
13
|
+
*
|
|
14
|
+
* **How it works:**
|
|
15
|
+
* 1. User selects new locale
|
|
16
|
+
* 2. LingoProvider automatically:
|
|
17
|
+
* - Updates cookie for persistence
|
|
18
|
+
* - Refreshes Server Components (if Next.js router provided to provider)
|
|
19
|
+
* - Loads translations for new locale
|
|
20
|
+
* 3. Client state is preserved throughout!
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* import { LocaleSwitcher } from '@lingo.dev/compiler/react';
|
|
25
|
+
*
|
|
26
|
+
* export function Header() {
|
|
27
|
+
* return (
|
|
28
|
+
* <header>
|
|
29
|
+
* <LocaleSwitcher locales={[
|
|
30
|
+
* { code: 'en', label: 'English' },
|
|
31
|
+
* { code: 'de', label: 'Deutsch' },
|
|
32
|
+
* { code: 'fr', label: 'Français' },
|
|
33
|
+
* ]} />
|
|
34
|
+
* </header>
|
|
35
|
+
* );
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function LocaleSwitcher({ locales, style, className = "lingo-locale-switcher", showLoadingState = true }) {
|
|
40
|
+
const { locale, setLocale, isLoading } = require_LingoContext.useLingoContext();
|
|
41
|
+
const loading = showLoadingState && isLoading;
|
|
42
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
43
|
+
value: locale,
|
|
44
|
+
onChange: (e) => setLocale(e.target.value),
|
|
45
|
+
disabled: loading,
|
|
46
|
+
className,
|
|
47
|
+
style: {
|
|
48
|
+
cursor: loading ? "wait" : "pointer",
|
|
49
|
+
...style
|
|
50
|
+
},
|
|
51
|
+
"aria-label": "Select language",
|
|
52
|
+
"data-testid": "lingo-locale-switcher",
|
|
53
|
+
children: locales.map((loc) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
54
|
+
value: loc.code,
|
|
55
|
+
children: loc.label
|
|
56
|
+
}, loc.code))
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
exports.LocaleSwitcher = LocaleSwitcher;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { LocaleCode } from "lingo.dev/spec";
|
|
2
|
+
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
|
3
|
+
import { CSSProperties } from "react";
|
|
4
|
+
|
|
5
|
+
//#region src/react/shared/LocaleSwitcher.d.ts
|
|
6
|
+
interface LocaleConfig {
|
|
7
|
+
code: LocaleCode;
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* LocaleSwitcher component props
|
|
12
|
+
*/
|
|
13
|
+
interface LocaleSwitcherProps {
|
|
14
|
+
/**
|
|
15
|
+
* Available locales, e.g. [{ code: 'en', label: 'English' }]
|
|
16
|
+
*/
|
|
17
|
+
locales: LocaleConfig[];
|
|
18
|
+
/**
|
|
19
|
+
* Custom styles for the select element
|
|
20
|
+
*/
|
|
21
|
+
style?: CSSProperties;
|
|
22
|
+
/**
|
|
23
|
+
* Custom class name
|
|
24
|
+
*/
|
|
25
|
+
className?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Show loading indicator
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
showLoadingState?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* LocaleSwitcher Component
|
|
34
|
+
*
|
|
35
|
+
* Provides a dropdown to switch between locales.
|
|
36
|
+
* Works with both Next.js and other React frameworks.
|
|
37
|
+
*
|
|
38
|
+
* **How it works:**
|
|
39
|
+
* 1. User selects new locale
|
|
40
|
+
* 2. LingoProvider automatically:
|
|
41
|
+
* - Updates cookie for persistence
|
|
42
|
+
* - Refreshes Server Components (if Next.js router provided to provider)
|
|
43
|
+
* - Loads translations for new locale
|
|
44
|
+
* 3. Client state is preserved throughout!
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { LocaleSwitcher } from '@lingo.dev/compiler/react';
|
|
49
|
+
*
|
|
50
|
+
* export function Header() {
|
|
51
|
+
* return (
|
|
52
|
+
* <header>
|
|
53
|
+
* <LocaleSwitcher locales={[
|
|
54
|
+
* { code: 'en', label: 'English' },
|
|
55
|
+
* { code: 'de', label: 'Deutsch' },
|
|
56
|
+
* { code: 'fr', label: 'Français' },
|
|
57
|
+
* ]} />
|
|
58
|
+
* </header>
|
|
59
|
+
* );
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function LocaleSwitcher({
|
|
64
|
+
locales,
|
|
65
|
+
style,
|
|
66
|
+
className,
|
|
67
|
+
showLoadingState
|
|
68
|
+
}: LocaleSwitcherProps): react_jsx_runtime1.JSX.Element;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { LocaleSwitcher };
|
|
71
|
+
//# sourceMappingURL=LocaleSwitcher.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocaleSwitcher.d.cts","names":[],"sources":["../../../src/react/shared/LocaleSwitcher.tsx"],"sourcesContent":[],"mappings":";;;;;UAMiB,YAAA;QACT;;AADR;AAQA;AAsDA;;AAEE,UAxDe,mBAAA,CAwDf;EACA;;;EAEoB,OAAA,EAvDX,YAuDW,EAAA;EAAA;;;UAlDZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6CM,cAAA;;;;;GAKb,sBAAmB,kBAAA,CAAA,GAAA,CAAA"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CSSProperties } from "react";
|
|
2
|
+
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
|
3
|
+
import { LocaleCode } from "lingo.dev/spec";
|
|
4
|
+
|
|
5
|
+
//#region src/react/shared/LocaleSwitcher.d.ts
|
|
6
|
+
interface LocaleConfig {
|
|
7
|
+
code: LocaleCode;
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* LocaleSwitcher component props
|
|
12
|
+
*/
|
|
13
|
+
interface LocaleSwitcherProps {
|
|
14
|
+
/**
|
|
15
|
+
* Available locales, e.g. [{ code: 'en', label: 'English' }]
|
|
16
|
+
*/
|
|
17
|
+
locales: LocaleConfig[];
|
|
18
|
+
/**
|
|
19
|
+
* Custom styles for the select element
|
|
20
|
+
*/
|
|
21
|
+
style?: CSSProperties;
|
|
22
|
+
/**
|
|
23
|
+
* Custom class name
|
|
24
|
+
*/
|
|
25
|
+
className?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Show loading indicator
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
showLoadingState?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* LocaleSwitcher Component
|
|
34
|
+
*
|
|
35
|
+
* Provides a dropdown to switch between locales.
|
|
36
|
+
* Works with both Next.js and other React frameworks.
|
|
37
|
+
*
|
|
38
|
+
* **How it works:**
|
|
39
|
+
* 1. User selects new locale
|
|
40
|
+
* 2. LingoProvider automatically:
|
|
41
|
+
* - Updates cookie for persistence
|
|
42
|
+
* - Refreshes Server Components (if Next.js router provided to provider)
|
|
43
|
+
* - Loads translations for new locale
|
|
44
|
+
* 3. Client state is preserved throughout!
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { LocaleSwitcher } from '@lingo.dev/compiler/react';
|
|
49
|
+
*
|
|
50
|
+
* export function Header() {
|
|
51
|
+
* return (
|
|
52
|
+
* <header>
|
|
53
|
+
* <LocaleSwitcher locales={[
|
|
54
|
+
* { code: 'en', label: 'English' },
|
|
55
|
+
* { code: 'de', label: 'Deutsch' },
|
|
56
|
+
* { code: 'fr', label: 'Français' },
|
|
57
|
+
* ]} />
|
|
58
|
+
* </header>
|
|
59
|
+
* );
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function LocaleSwitcher({
|
|
64
|
+
locales,
|
|
65
|
+
style,
|
|
66
|
+
className,
|
|
67
|
+
showLoadingState
|
|
68
|
+
}: LocaleSwitcherProps): react_jsx_runtime1.JSX.Element;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { LocaleSwitcher };
|
|
71
|
+
//# sourceMappingURL=LocaleSwitcher.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocaleSwitcher.d.mts","names":[],"sources":["../../../src/react/shared/LocaleSwitcher.tsx"],"sourcesContent":[],"mappings":";;;;;UAMiB,YAAA;QACT;;AADR;AAQA;AAsDA;;AAEE,UAxDe,mBAAA,CAwDf;EACA;;;EAEoB,OAAA,EAvDX,YAuDW,EAAA;EAAA;;;UAlDZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6CM,cAAA;;;;;GAKb,sBAAmB,kBAAA,CAAA,GAAA,CAAA"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useLingoContext } from "./LingoContext.mjs";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/react/shared/LocaleSwitcher.tsx
|
|
7
|
+
/**
|
|
8
|
+
* LocaleSwitcher Component
|
|
9
|
+
*
|
|
10
|
+
* Provides a dropdown to switch between locales.
|
|
11
|
+
* Works with both Next.js and other React frameworks.
|
|
12
|
+
*
|
|
13
|
+
* **How it works:**
|
|
14
|
+
* 1. User selects new locale
|
|
15
|
+
* 2. LingoProvider automatically:
|
|
16
|
+
* - Updates cookie for persistence
|
|
17
|
+
* - Refreshes Server Components (if Next.js router provided to provider)
|
|
18
|
+
* - Loads translations for new locale
|
|
19
|
+
* 3. Client state is preserved throughout!
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* import { LocaleSwitcher } from '@lingo.dev/compiler/react';
|
|
24
|
+
*
|
|
25
|
+
* export function Header() {
|
|
26
|
+
* return (
|
|
27
|
+
* <header>
|
|
28
|
+
* <LocaleSwitcher locales={[
|
|
29
|
+
* { code: 'en', label: 'English' },
|
|
30
|
+
* { code: 'de', label: 'Deutsch' },
|
|
31
|
+
* { code: 'fr', label: 'Français' },
|
|
32
|
+
* ]} />
|
|
33
|
+
* </header>
|
|
34
|
+
* );
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
function LocaleSwitcher({ locales, style, className = "lingo-locale-switcher", showLoadingState = true }) {
|
|
39
|
+
const { locale, setLocale, isLoading } = useLingoContext();
|
|
40
|
+
const loading = showLoadingState && isLoading;
|
|
41
|
+
return /* @__PURE__ */ jsx("select", {
|
|
42
|
+
value: locale,
|
|
43
|
+
onChange: (e) => setLocale(e.target.value),
|
|
44
|
+
disabled: loading,
|
|
45
|
+
className,
|
|
46
|
+
style: {
|
|
47
|
+
cursor: loading ? "wait" : "pointer",
|
|
48
|
+
...style
|
|
49
|
+
},
|
|
50
|
+
"aria-label": "Select language",
|
|
51
|
+
"data-testid": "lingo-locale-switcher",
|
|
52
|
+
children: locales.map((loc) => /* @__PURE__ */ jsx("option", {
|
|
53
|
+
value: loc.code,
|
|
54
|
+
children: loc.label
|
|
55
|
+
}, loc.code))
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
export { LocaleSwitcher };
|
|
61
|
+
//# sourceMappingURL=LocaleSwitcher.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocaleSwitcher.mjs","names":[],"sources":["../../../src/react/shared/LocaleSwitcher.tsx"],"sourcesContent":["\"use client\";\n\nimport type { CSSProperties } from \"react\";\nimport { useLingoContext } from \"./LingoContext\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\n\nexport interface LocaleConfig {\n code: LocaleCode;\n label: string;\n}\n\n/**\n * LocaleSwitcher component props\n */\nexport interface LocaleSwitcherProps {\n /**\n * Available locales, e.g. [{ code: 'en', label: 'English' }]\n */\n locales: LocaleConfig[];\n\n /**\n * Custom styles for the select element\n */\n style?: CSSProperties;\n\n /**\n * Custom class name\n */\n className?: string;\n\n /**\n * Show loading indicator\n * @default true\n */\n showLoadingState?: boolean;\n}\n\n/**\n * LocaleSwitcher Component\n *\n * Provides a dropdown to switch between locales.\n * Works with both Next.js and other React frameworks.\n *\n * **How it works:**\n * 1. User selects new locale\n * 2. LingoProvider automatically:\n * - Updates cookie for persistence\n * - Refreshes Server Components (if Next.js router provided to provider)\n * - Loads translations for new locale\n * 3. Client state is preserved throughout!\n *\n * @example\n * ```tsx\n * import { LocaleSwitcher } from '@lingo.dev/compiler/react';\n *\n * export function Header() {\n * return (\n * <header>\n * <LocaleSwitcher locales={[\n * { code: 'en', label: 'English' },\n * { code: 'de', label: 'Deutsch' },\n * { code: 'fr', label: 'Français' },\n * ]} />\n * </header>\n * );\n * }\n * ```\n */\nexport function LocaleSwitcher({\n locales,\n style,\n className = \"lingo-locale-switcher\",\n showLoadingState = true,\n}: LocaleSwitcherProps) {\n const { locale, setLocale, isLoading } = useLingoContext();\n\n const loading = showLoadingState && isLoading;\n\n return (\n <select\n value={locale}\n onChange={(e) => setLocale(e.target.value as LocaleCode)}\n disabled={loading}\n className={className}\n style={{\n cursor: loading ? \"wait\" : \"pointer\",\n ...style,\n }}\n aria-label=\"Select language\"\n data-testid=\"lingo-locale-switcher\"\n >\n {locales.map((loc) => (\n <option key={loc.code} value={loc.code}>\n {loc.label}\n </option>\n ))}\n </select>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,SAAgB,eAAe,EAC7B,SACA,OACA,YAAY,yBACZ,mBAAmB,QACG;CACtB,MAAM,EAAE,QAAQ,WAAW,cAAc,iBAAiB;CAE1D,MAAM,UAAU,oBAAoB;AAEpC,QACE,oBAAC;EACC,OAAO;EACP,WAAW,MAAM,UAAU,EAAE,OAAO,MAAoB;EACxD,UAAU;EACC;EACX,OAAO;GACL,QAAQ,UAAU,SAAS;GAC3B,GAAG;GACJ;EACD,cAAW;EACX,eAAY;YAEX,QAAQ,KAAK,QACZ,oBAAC;GAAsB,OAAO,IAAI;aAC/B,IAAI;KADM,IAAI,KAER,CACT;GACK"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_logger = require('../../utils/logger.cjs');
|
|
3
|
+
let react = require("react");
|
|
4
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
let intl_messageformat = require("intl-messageformat");
|
|
6
|
+
intl_messageformat = require_rolldown_runtime.__toESM(intl_messageformat);
|
|
7
|
+
|
|
8
|
+
//#region src/react/shared/render-rich-text.tsx
|
|
9
|
+
/**
|
|
10
|
+
* Wraps a FormatXMLElementFn to automatically assign keys to parts
|
|
11
|
+
*/
|
|
12
|
+
function assignUniqueKeysToParts(formatXMLElementFn) {
|
|
13
|
+
return function(parts) {
|
|
14
|
+
return formatXMLElementFn(parts);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Wraps all FormatXMLElementFn values in params with key assignment
|
|
19
|
+
*/
|
|
20
|
+
function assignUniqueKeysToParams(params) {
|
|
21
|
+
const result = {};
|
|
22
|
+
for (const [key, value] of Object.entries(params)) if (typeof value === "function") result[key] = assignUniqueKeysToParts(value);
|
|
23
|
+
else result[key] = value;
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse rich text translation string with placeholders (server-side version)
|
|
28
|
+
*
|
|
29
|
+
* Supports:
|
|
30
|
+
* - ICU MessageFormat: {count, plural, one {# item} other {# items}}
|
|
31
|
+
* - Variable placeholders: {name}
|
|
32
|
+
* - Component placeholders: <tag0>content</tag0>
|
|
33
|
+
*
|
|
34
|
+
* @param text - The translation string with placeholders
|
|
35
|
+
* @param params - Object with variable values and component renderers
|
|
36
|
+
* @param locale
|
|
37
|
+
* @returns React nodes or string
|
|
38
|
+
*/
|
|
39
|
+
function renderRichText(text, params, locale) {
|
|
40
|
+
try {
|
|
41
|
+
const formatter = new intl_messageformat.default(text, locale);
|
|
42
|
+
const keyedParams = assignUniqueKeysToParams(params);
|
|
43
|
+
const result = formatter.format(keyedParams);
|
|
44
|
+
if (Array.isArray(result)) return result.map((item, index) => {
|
|
45
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: item }, index);
|
|
46
|
+
});
|
|
47
|
+
return result;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
require_logger.logger.warn(`Error rendering rich text (${text}): ${error}`);
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
exports.renderRichText = renderRichText;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { LocaleCode } from "lingo.dev/spec";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
import { FormatXMLElementFn } from "intl-messageformat";
|
|
4
|
+
|
|
5
|
+
//#region src/react/shared/render-rich-text.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Component renderer function for rich text translation
|
|
9
|
+
*/
|
|
10
|
+
type ComponentRenderer = FormatXMLElementFn<ReactNode>;
|
|
11
|
+
/**
|
|
12
|
+
* Rich text parameters for translation
|
|
13
|
+
*/
|
|
14
|
+
type RichTextParams = Record<string, ReactNode | ComponentRenderer>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { RichTextParams };
|
|
17
|
+
//# sourceMappingURL=render-rich-text.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-rich-text.d.cts","names":[],"sources":["../../../src/react/shared/render-rich-text.tsx"],"sourcesContent":[],"mappings":";;;;;;;;AAQA;AAKY,KALA,iBAAA,GAAoB,kBAKN,CALyB,SAKzB,CAAA;;;;AAAS,KAAvB,cAAA,GAAiB,MAAM,CAAA,MAAA,EAAS,SAAT,GAAqB,iBAArB,CAAA"}
|