Skip to content

Commit 3a642f3

Browse files
authored
feat(compiler): useLingoLocale, setLingoLocale (#1134)
1 parent a35032e commit 3a642f3

8 files changed

Lines changed: 179 additions & 13 deletions

File tree

.changeset/plenty-tools-move.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@lingo.dev/_react": minor
3+
"lingo.dev": minor
4+
---
5+
6+
useLingoLocale, setLingoLocale

demo/next-app/src/components/hero-subtitle.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ export async function HeroSubtitle() {
66
<p className="text-lg sm:text-xl text-gray-600 mb-10 max-w-xl mx-auto leading-relaxed">
77
Localize your React app in every language in minutes.
88
<br />
9-
Scale to millions from day one.
9+
Scale to millions from day one
10+
<sup title="supports many popular frameworks">*</sup>.
1011
</p>
1112
);
1213
}

demo/next-app/src/lingo/dictionary.js

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,31 @@ export default {
55
entries: {
66
"0/declaration/body/0/argument": {
77
content: {
8-
ar: "قم بتوطين تطبيق React الخاص بك بكل لغة في دقائق.<element:br></element:br> قم بالتوسع إلى الملايين من اليوم الأول.",
9-
de: "Lokalisieren Sie Ihre React-App in jeder Sprache in Minuten.<element:br></element:br> Skalieren Sie von Anfang an auf Millionen.",
10-
en: "Localize your React app in every language in minutes.<element:br></element:br> Scale to millions from day one.",
11-
es: "Localiza tu aplicación React en cualquier idioma en minutos.<element:br></element:br> Escala a millones desde el primer día.",
12-
fr: "Localisez votre application React dans toutes les langues en quelques minutes.<element:br></element:br> Évoluez vers des millions dès le premier jour.",
13-
ja: "数分でReactアプリをすべての言語にローカライズします。<element:br></element:br> 最初の日から数百万にスケールします。",
14-
ko: "몇 분 만에 React 앱을 모든 언어로 로컬라이즈하세요.<element:br></element:br> 첫날부터 수백만 사용자에게 확장할 수 있습니다.",
15-
ru: "Локализуйте ваше приложение React на любом языке за считанные минуты.<element:br></element:br> Масштабируйте до миллионов с первого дня.",
16-
zh: "在几分钟内将您的React应用本地化为任何语言。<element:br></element:br> 从第一天开始扩展到数百万用户。",
8+
ar: "قم بترجمة تطبيق React الخاص بك إلى كل لغة في دقائق.<element:br></element:br> توسع إلى الملايين من اليوم الأول<element:sup>*</element:sup>.",
9+
de: "Lokalisieren Sie Ihre React-App in jeder Sprache in wenigen Minuten.<element:br></element:br> Skalieren Sie vom ersten Tag an auf Millionen<element:sup>*</element:sup>.",
10+
en: "Localize your React app in every language in minutes.<element:br></element:br> Scale to millions from day one<element:sup>*</element:sup>.",
11+
es: "Localiza tu aplicación React en todos los idiomas en minutos.<element:br></element:br> Escala a millones desde el primer día<element:sup>*</element:sup>.",
12+
fr: "Localisez votre application React dans toutes les langues en quelques minutes.<element:br></element:br> Adaptez-vous à des millions d'utilisateurs dès le premier jour<element:sup>*</element:sup>.",
13+
ja: "数分でReactアプリをあらゆる言語にローカライズ。<element:br></element:br> 初日から数百万規模に対応<element:sup>*</element:sup>。",
14+
ko: "몇 분 안에 React 앱을 모든 언어로 현지화하세요.<element:br></element:br> 첫날부터 수백만 명으로 확장하세요<element:sup>*</element:sup>.",
15+
ru: "Локализуйте своё React-приложение на любой язык за считанные минуты.<element:br></element:br> Масштабируйтесь до миллионов с первого дня<element:sup>*</element:sup>.",
16+
zh: "在几分钟内将您的 React 应用程序本地化为多种语言。<element:br></element:br> 从第一天起就支持扩展到数百万用户<element:sup>*</element:sup>。",
1717
},
18-
hash: "2b8865d21f308426a4da45852b27004d",
18+
hash: "75cc719ebad12413e5fa788174d4d4ec",
19+
},
20+
"0/declaration/body/0/argument/3-title": {
21+
content: {
22+
ar: "يدعم العديد من أطر العمل الشائعة",
23+
de: "unterstützt viele beliebte Frameworks",
24+
en: "supports many popular frameworks",
25+
es: "soporta muchos frameworks populares",
26+
fr: "prend en charge de nombreux frameworks populaires",
27+
ja: "多くの人気フレームワークをサポート",
28+
ko: "다양한 인기 프레임워크 지원",
29+
ru: "поддерживает многие популярные фреймворки",
30+
zh: "支持多种流行框架",
31+
},
32+
hash: "ce9b34049534ad201aef501f2a224a9d",
1933
},
2034
},
2135
},

demo/next-app/src/lingo/meta.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@
55
"scopes": {
66
"0/declaration/body/0/argument": {
77
"type": "element",
8-
"hash": "2b8865d21f308426a4da45852b27004d",
8+
"hash": "75cc719ebad12413e5fa788174d4d4ec",
99
"context": "",
1010
"skip": false,
1111
"overrides": {},
12-
"content": "Localize your React app in every language in minutes.<element:br></element:br> Scale to millions from day one."
12+
"content": "Localize your React app in every language in minutes.<element:br></element:br> Scale to millions from day one<element:sup>*</element:sup>."
13+
},
14+
"0/declaration/body/0/argument/3-title": {
15+
"type": "attribute",
16+
"hash": "ce9b34049534ad201aef501f2a224a9d",
17+
"context": "",
18+
"skip": false,
19+
"overrides": {},
20+
"content": "supports many popular frameworks"
1321
}
1422
}
1523
}

packages/react/src/client/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from "./provider";
44
export * from "./component";
55
export * from "./locale-switcher";
66
export * from "./attribute-component";
7+
export * from "./locale";
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest";
2+
import { renderHook, act } from "@testing-library/react";
3+
import { useLingoLocale, setLingoLocale } from "./locale";
4+
5+
// Mock the utils module
6+
vi.mock("./utils", async (orig) => {
7+
const actual = await orig();
8+
return {
9+
...(actual as any),
10+
getLocaleFromCookies: vi.fn(() => "en"),
11+
setLocaleInCookies: vi.fn(),
12+
};
13+
});
14+
15+
import { getLocaleFromCookies, setLocaleInCookies } from "./utils";
16+
17+
// Mock window.location.reload
18+
const mockReload = vi.fn();
19+
Object.defineProperty(window, "location", {
20+
value: { ...window.location, reload: mockReload },
21+
writable: true,
22+
});
23+
24+
describe("useLingoLocale", () => {
25+
beforeEach(() => {
26+
vi.clearAllMocks();
27+
});
28+
29+
it("returns the locale from cookies", () => {
30+
(getLocaleFromCookies as any).mockReturnValue("es");
31+
const { result } = renderHook(() => useLingoLocale());
32+
33+
expect(result.current).toBe("es");
34+
expect(getLocaleFromCookies).toHaveBeenCalled();
35+
});
36+
37+
it("returns null when no locale is set", () => {
38+
(getLocaleFromCookies as any).mockReturnValue(null);
39+
const { result } = renderHook(() => useLingoLocale());
40+
41+
expect(result.current).toBe(null);
42+
});
43+
});
44+
45+
describe("setLingoLocale", () => {
46+
beforeEach(() => {
47+
vi.clearAllMocks();
48+
mockReload.mockClear();
49+
});
50+
51+
it("sets locale in cookies and reloads page for valid locale", () => {
52+
act(() => {
53+
setLingoLocale("es");
54+
});
55+
56+
expect(setLocaleInCookies).toHaveBeenCalledWith("es");
57+
expect(mockReload).toHaveBeenCalled();
58+
});
59+
60+
it("accepts various locales", () => {
61+
const validLocales = [
62+
"en",
63+
"es",
64+
"fr",
65+
"de",
66+
"en-US",
67+
"es-ES",
68+
"fr-CA",
69+
"de-DE",
70+
];
71+
72+
validLocales.forEach((locale) => {
73+
expect(() => {
74+
act(() => {
75+
setLingoLocale(locale);
76+
});
77+
}).not.toThrow();
78+
79+
expect(setLocaleInCookies).toHaveBeenCalledWith(locale);
80+
});
81+
});
82+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { getLocaleFromCookies, setLocaleInCookies } from "./utils";
5+
6+
/**
7+
* Gets the current locale used by the Lingo compiler.
8+
*
9+
* @returns The current locale code, or `null` if no locale is set.
10+
*/
11+
export function useLingoLocale(): string | null {
12+
const [locale, setLocale] = useState<string | null>(null);
13+
useEffect(() => {
14+
setLocale(getLocaleFromCookies());
15+
}, []);
16+
return locale;
17+
}
18+
19+
/**
20+
* Sets the current locale used by the Lingo compiler.
21+
*
22+
* **Note:** This function triggers a full page reload to ensure all components
23+
* are re-rendered with the new locale. This is necessary because locale changes
24+
* affect the entire application state.
25+
*
26+
* @param locale - The locale code to set. Must be a valid locale code (e.g., "en", "es", "fr-CA").
27+
28+
*
29+
* @example Set the current locale
30+
* ```tsx
31+
* import { setLingoLocale } from "lingo.dev/react/client";
32+
*
33+
* export function LanguageSwitcher() {
34+
* const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
35+
* setLingoLocale(event.target.value);
36+
* };
37+
*
38+
* return (
39+
* <select onChange={handleChange}>
40+
* <option value="en">English</option>
41+
* <option value="es">Spanish</option>
42+
* </select>
43+
* );
44+
* }
45+
* ```
46+
*/
47+
export function setLingoLocale(locale: string) {
48+
setLocaleInCookies(locale);
49+
window.location.reload();
50+
}

packages/react/src/rsc/component.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export async function LingoComponent(props: LingoComponentProps) {
1515
const { $as, $fileKey, $entryKey, $loadDictionary, ...rest } = props;
1616
const dictionary = await loadDictionaryFromRequest($loadDictionary);
1717

18+
if ($as.name === "LingoAttributeComponent") {
19+
rest.$loadDictionary = $loadDictionary;
20+
}
21+
1822
return (
1923
<LingoCoreComponent
2024
{...rest}

0 commit comments

Comments
 (0)