Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions apps/console/src/providers/TranslatorProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import type { PropsWithChildren } from "react";
import { TranslatorProvider as ProboTranslatorProvider } from "@probo/i18n";
import {
TranslatorProvider as ProboTranslatorProvider,
type SupportedLang,
DEFAULT_LANG,
} from "@probo/i18n";

// TODO : implement a way to retrieve translations strings
const loader = () => {
return Promise.resolve({} as Record<string, string>);
const locales = import.meta.glob<Record<string, string>>("/locales/*.json", {
import: "default",
});

const enPath = "/locales/en.json";

const loader = async (lang: SupportedLang): Promise<Record<string, string>> => {
const langPath = `/locales/${lang}.json`;

const enTranslations = locales[enPath] ? await locales[enPath]() : {};

if (lang === DEFAULT_LANG) {
return enTranslations;
}

const langTranslations = locales[langPath] ? await locales[langPath]() : {};
return { ...enTranslations, ...langTranslations };
};

/**
* Provider for the translator
*/
export function TranslatorProvider({ children }: PropsWithChildren) {
return (
<ProboTranslatorProvider lang="en" loader={loader}>
{children}
</ProboTranslatorProvider>
<ProboTranslatorProvider loader={loader}>{children}</ProboTranslatorProvider>
);
}
3 changes: 3 additions & 0 deletions apps/console/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export default defineConfig({
"/permissions": fileURLToPath(
new URL("./src/permissions", import.meta.url),
),
"/locales": fileURLToPath(
new URL("../../packages/i18n/locales", import.meta.url),
),
},
},
});
33 changes: 23 additions & 10 deletions apps/trust/src/providers/TranslatorProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import type { PropsWithChildren } from "react";
import { TranslatorProvider as ProboTranslatorProvider } from "../../../../packages/i18n/TranslatorProvider";
import {
TranslatorProvider as ProboTranslatorProvider,
type SupportedLang,
DEFAULT_LANG,
} from "@probo/i18n";

// TODO : implement a way to retrieve translations strings
const loader = () => {
return Promise.resolve({} as Record<string, string>);
const locales = import.meta.glob<Record<string, string>>("/locales/*.json", {
import: "default",
});

const enPath = "/locales/en.json";

const loader = async (lang: SupportedLang): Promise<Record<string, string>> => {
const langPath = `/locales/${lang}.json`;

const enTranslations = locales[enPath] ? await locales[enPath]() : {};

if (lang === DEFAULT_LANG) {
return enTranslations;
}

const langTranslations = locales[langPath] ? await locales[langPath]() : {};
return { ...enTranslations, ...langTranslations };
};

/**
* Provider for the translator
*/
export function TranslatorProvider({ children }: PropsWithChildren) {
return (
<ProboTranslatorProvider lang="en" loader={loader}>
{children}
</ProboTranslatorProvider>
<ProboTranslatorProvider loader={loader}>{children}</ProboTranslatorProvider>
);
}
3 changes: 3 additions & 0 deletions apps/trust/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export default defineConfig({
"/pages": fileURLToPath(new URL("./src/pages", import.meta.url)),
"/routes": fileURLToPath(new URL("./src/routes", import.meta.url)),
"/providers": fileURLToPath(new URL("./src/providers", import.meta.url)),
"/locales": fileURLToPath(
new URL("../../packages/i18n/locales", import.meta.url)
),
},
},
});
76 changes: 59 additions & 17 deletions packages/i18n/TranslatorProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,83 @@ import {
useState,
} from "react";

const defaultValue = {
lang: "en" as "en" | "fr",
translations: {} as Record<string, string>,
export const SUPPORTED_LANGUAGES = ["en", "fr"] as const;
export type SupportedLang = (typeof SUPPORTED_LANGUAGES)[number];
export const DEFAULT_LANG: SupportedLang = "en";
const STORAGE_KEY = "probo-lang";

type TranslatorContextValue = {
lang: SupportedLang;
translations: Record<string, string>;
translate: (s: string) => string;
setLang: (lang: SupportedLang) => void;
};

const defaultValue: TranslatorContextValue = {
lang: DEFAULT_LANG,
translations: {},
translate: (s: string) => s,
setLang: () => {},
};

type Context = typeof defaultValue;
const TranslatorContext = createContext<TranslatorContextValue>(defaultValue);

function detectLanguage(): SupportedLang {
if (typeof window === "undefined") {
return DEFAULT_LANG;
}

const stored = localStorage.getItem(STORAGE_KEY);
if (stored && SUPPORTED_LANGUAGES.includes(stored as SupportedLang)) {
return stored as SupportedLang;
}

const browserLang = navigator.language.split("-")[0];
if (SUPPORTED_LANGUAGES.includes(browserLang as SupportedLang)) {
return browserLang as SupportedLang;
}

const TranslatorContext = createContext(defaultValue);
return DEFAULT_LANG;
}

type Props = {
lang: "en" | "fr";
loader: (lang: string) => Promise<Record<string, string>>;
loader: (lang: SupportedLang) => Promise<Record<string, string>>;
defaultLang?: SupportedLang;
};

export function TranslatorProvider({
lang,
loader,
defaultLang,
children,
}: PropsWithChildren<Props>) {
const [translations, setTranslations] = useState(
{} as Record<string, string>
);
const translate = useCallback<Context["translate"]>(
(s) => {
return translations[s] ? translations[s] : s;
const [lang, setLangState] = useState<SupportedLang>(() => {
return defaultLang ?? detectLanguage();
});

const [translations, setTranslations] = useState<Record<string, string>>({});

const translate = useCallback(
(s: string): string => {
return translations[s] ?? s;
},
[translations]
);

const setLang = useCallback((newLang: SupportedLang) => {
if (SUPPORTED_LANGUAGES.includes(newLang)) {
setLangState(newLang);
if (typeof window !== "undefined") {
localStorage.setItem(STORAGE_KEY, newLang);
}
}
}, []);

useEffect(() => {
loader(lang).then(setTranslations);
}, [lang]);
}, [lang, loader]);

return (
<TranslatorContext.Provider value={{ lang, translations, translate }}>
<TranslatorContext.Provider value={{ lang, translations, translate, setLang }}>
{children}
</TranslatorContext.Provider>
);
Expand All @@ -67,7 +108,7 @@ const relativeFormat = [
] as const;

export function useTranslate() {
const { translate, lang } = useContext(TranslatorContext);
const { translate, lang, setLang } = useContext(TranslatorContext);
const dateFormat = (
date: Date | string | null | undefined,
options: Intl.DateTimeFormatOptions = {
Expand Down Expand Up @@ -110,6 +151,7 @@ export function useTranslate() {

return {
lang,
setLang,
__: translate,
dateFormat: dateFormat,
relativeDateFormat,
Expand Down
7 changes: 7 additions & 0 deletions packages/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export {
TranslatorProvider,
useTranslate,
SUPPORTED_LANGUAGES,
DEFAULT_LANG,
type SupportedLang,
} from "./TranslatorProvider";
Loading