@onruntime/translations 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 ADDED
@@ -0,0 +1,285 @@
1
+ # @onruntime/translations
2
+
3
+ Lightweight i18n library for React, Next.js, and React Native.
4
+
5
+ ## Examples
6
+
7
+ - [Next.js App Router](https://github.com/onRuntime/onruntime/tree/master/examples/translations/next-app)
8
+ - [Next.js Pages Router](https://github.com/onRuntime/onruntime/tree/master/examples/translations/next-pages)
9
+ - [React + Vite](https://github.com/onRuntime/onruntime/tree/master/examples/translations/react)
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add @onruntime/translations
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Next.js App Router
20
+
21
+ #### 1. Create your translations config
22
+
23
+ ```typescript
24
+ // lib/translations.ts
25
+ import {
26
+ getTranslation as getTranslationCore,
27
+ type TranslationLoader,
28
+ } from "@onruntime/translations";
29
+
30
+ export const locales = ["en", "fr"];
31
+ export const defaultLocale = locales[0];
32
+
33
+ export const load: TranslationLoader = (locale, namespace) => {
34
+ try {
35
+ return require(`../locales/${locale}/${namespace}.json`);
36
+ } catch {
37
+ return undefined;
38
+ }
39
+ };
40
+
41
+ export const getTranslation = async (
42
+ params: Promise<{ lang: string }>,
43
+ namespace = "common",
44
+ ) => {
45
+ const { lang } = await params;
46
+ return getTranslationCore(load, lang, namespace);
47
+ };
48
+ ```
49
+
50
+ #### 2. Setup middleware
51
+
52
+ ```typescript
53
+ // middleware.ts
54
+ import { NextResponse } from "next/server";
55
+ import type { NextRequest } from "next/server";
56
+
57
+ import { locales, defaultLocale } from "@/lib/translations";
58
+
59
+ export function middleware(request: NextRequest) {
60
+ const { pathname } = request.nextUrl;
61
+
62
+ const pathnameLocale = locales.find(
63
+ (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`,
64
+ );
65
+
66
+ if (pathnameLocale === defaultLocale) {
67
+ const newPathname = pathname.replace(`/${defaultLocale}`, "") || "/";
68
+ return NextResponse.redirect(new URL(newPathname, request.url));
69
+ }
70
+
71
+ if (pathnameLocale) return;
72
+
73
+ request.nextUrl.pathname = `/${defaultLocale}${pathname}`;
74
+ return NextResponse.rewrite(request.nextUrl);
75
+ }
76
+
77
+ export const config = {
78
+ matcher: ["/((?!_next|favicon.ico).*)"],
79
+ };
80
+ ```
81
+
82
+ #### 3. Setup layout with provider
83
+
84
+ ```typescript
85
+ // app/[lang]/layout.tsx
86
+ import type { ReactNode } from "react";
87
+ import { AppTranslationProvider } from "@onruntime/translations/next";
88
+
89
+ import { load, locales } from "@/lib/translations";
90
+
91
+ export async function generateStaticParams() {
92
+ return locales.map((lang) => ({ lang }));
93
+ }
94
+
95
+ export default async function RootLayout({
96
+ children,
97
+ params,
98
+ }: {
99
+ children: ReactNode;
100
+ params: Promise<{ lang: string }>;
101
+ }) {
102
+ const { lang } = await params;
103
+
104
+ return (
105
+ <html lang={lang}>
106
+ <body>
107
+ <AppTranslationProvider locale={lang} locales={locales} load={load}>
108
+ {children}
109
+ </AppTranslationProvider>
110
+ </body>
111
+ </html>
112
+ );
113
+ }
114
+ ```
115
+
116
+ #### 4. Use in Server Components
117
+
118
+ ```typescript
119
+ // app/[lang]/page.tsx
120
+ import { Link } from "@onruntime/translations/next";
121
+
122
+ import { getTranslation } from "@/lib/translations";
123
+
124
+ export default async function Home({
125
+ params,
126
+ }: {
127
+ params: Promise<{ lang: string }>;
128
+ }) {
129
+ const { t, locale } = await getTranslation(params);
130
+
131
+ return (
132
+ <div>
133
+ <h1>{t("greeting", { name: "John" })}</h1>
134
+ <Link href="/about">{t("nav.about")}</Link>
135
+ </div>
136
+ );
137
+ }
138
+ ```
139
+
140
+ #### 5. Use in Client Components
141
+
142
+ ```typescript
143
+ // app/[lang]/about/page.tsx
144
+ "use client";
145
+
146
+ import { useTranslation, useLocale } from "@onruntime/translations/react";
147
+ import { Link } from "@onruntime/translations/next";
148
+
149
+ export default function About() {
150
+ const { t } = useTranslation();
151
+ const { locale } = useLocale();
152
+
153
+ return (
154
+ <div>
155
+ <h1>{t("about.title")}</h1>
156
+ <p>Current locale: {locale}</p>
157
+ <Link href="/">{t("nav.home")}</Link>
158
+ </div>
159
+ );
160
+ }
161
+ ```
162
+
163
+ ### Next.js Pages Router
164
+
165
+ #### 1. Configure i18n in next.config.js
166
+
167
+ ```javascript
168
+ // next.config.js
169
+ module.exports = {
170
+ i18n: {
171
+ locales: ["en", "fr"],
172
+ defaultLocale: "en",
173
+ },
174
+ };
175
+ ```
176
+
177
+ #### 2. Setup provider in _app.tsx
178
+
179
+ ```typescript
180
+ // pages/_app.tsx
181
+ import type { AppProps } from "next/app";
182
+ import { TranslationProvider } from "@onruntime/translations/next";
183
+
184
+ export default function App({ Component, pageProps }: AppProps) {
185
+ return (
186
+ <TranslationProvider
187
+ load={(locale, ns) => {
188
+ try {
189
+ return require(`@/locales/${locale}/${ns}.json`);
190
+ } catch {
191
+ return undefined;
192
+ }
193
+ }}
194
+ >
195
+ <Component {...pageProps} />
196
+ </TranslationProvider>
197
+ );
198
+ }
199
+ ```
200
+
201
+ #### 3. Use in pages
202
+
203
+ ```typescript
204
+ // pages/index.tsx
205
+ import Link from "next/link";
206
+ import { useTranslation } from "@onruntime/translations/react";
207
+
208
+ export default function Home() {
209
+ const { t, locale } = useTranslation();
210
+
211
+ return (
212
+ <div>
213
+ <h1>{t("greeting", { name: "John" })}</h1>
214
+ <Link href="/about" locale="fr">
215
+ Switch to French
216
+ </Link>
217
+ </div>
218
+ );
219
+ }
220
+ ```
221
+
222
+ ### React + Vite
223
+
224
+ #### 1. Setup provider with Vite loader
225
+
226
+ ```typescript
227
+ // src/app.tsx
228
+ import { TranslationProvider } from "@onruntime/translations/react";
229
+ import { createViteLoader } from "@onruntime/translations/vite";
230
+
231
+ const modules = import.meta.glob("./locales/**/*.json", { eager: true });
232
+ const load = createViteLoader(modules);
233
+
234
+ const App = () => {
235
+ return (
236
+ <TranslationProvider defaultLocale="en" locales={["en", "fr"]} load={load}>
237
+ <Demo />
238
+ </TranslationProvider>
239
+ );
240
+ };
241
+
242
+ export default App;
243
+ ```
244
+
245
+ #### 2. Use translations
246
+
247
+ ```typescript
248
+ // src/components/demo.tsx
249
+ import { useTranslation, useLocale } from "@onruntime/translations/react";
250
+
251
+ export const Demo = () => {
252
+ const { t, locale } = useTranslation();
253
+ const { setLocale } = useLocale();
254
+
255
+ return (
256
+ <div>
257
+ <h1>{t("greeting", { name: "John" })}</h1>
258
+ <p>Current locale: {locale}</p>
259
+ <button onClick={() => setLocale(locale === "en" ? "fr" : "en")}>
260
+ Switch language
261
+ </button>
262
+ </div>
263
+ );
264
+ };
265
+ ```
266
+
267
+ ### Translation files
268
+
269
+ ```json
270
+ // locales/en/common.json
271
+ {
272
+ "greeting": "Hello, {name}!",
273
+ "nav": {
274
+ "home": "Home",
275
+ "about": "About"
276
+ },
277
+ "about": {
278
+ "title": "About Us"
279
+ }
280
+ }
281
+ ```
282
+
283
+ ## License
284
+
285
+ MIT
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/react/contexts/translation-context.tsx
8
+ var TranslationContext = react.createContext(
9
+ null
10
+ );
11
+ var TranslationProvider = ({
12
+ children,
13
+ locales,
14
+ defaultLocale,
15
+ load,
16
+ keySplit = true
17
+ }) => {
18
+ if (locales.length === 0) {
19
+ throw new Error("TranslationProvider: locales array must not be empty");
20
+ }
21
+ const resolvedDefaultLocale = defaultLocale ?? locales[0];
22
+ const [locale, setLocale] = react.useState(resolvedDefaultLocale);
23
+ const value = react.useMemo(
24
+ () => ({
25
+ locale,
26
+ locales,
27
+ defaultLocale: resolvedDefaultLocale,
28
+ setLocale,
29
+ load,
30
+ keySplit
31
+ }),
32
+ [locale, locales, resolvedDefaultLocale, load, keySplit]
33
+ );
34
+ return /* @__PURE__ */ jsxRuntime.jsx(TranslationContext.Provider, { value, children });
35
+ };
36
+ var useTranslationContext = () => {
37
+ const context = react.useContext(TranslationContext);
38
+ if (!context) {
39
+ throw new Error(
40
+ "useTranslationContext must be used within a TranslationProvider"
41
+ );
42
+ }
43
+ return context;
44
+ };
45
+
46
+ exports.TranslationContext = TranslationContext;
47
+ exports.TranslationProvider = TranslationProvider;
48
+ exports.useTranslationContext = useTranslationContext;
@@ -0,0 +1,44 @@
1
+ "use client";
2
+ import { createContext, useState, useMemo, useContext } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ // src/react/contexts/translation-context.tsx
6
+ var TranslationContext = createContext(
7
+ null
8
+ );
9
+ var TranslationProvider = ({
10
+ children,
11
+ locales,
12
+ defaultLocale,
13
+ load,
14
+ keySplit = true
15
+ }) => {
16
+ if (locales.length === 0) {
17
+ throw new Error("TranslationProvider: locales array must not be empty");
18
+ }
19
+ const resolvedDefaultLocale = defaultLocale ?? locales[0];
20
+ const [locale, setLocale] = useState(resolvedDefaultLocale);
21
+ const value = useMemo(
22
+ () => ({
23
+ locale,
24
+ locales,
25
+ defaultLocale: resolvedDefaultLocale,
26
+ setLocale,
27
+ load,
28
+ keySplit
29
+ }),
30
+ [locale, locales, resolvedDefaultLocale, load, keySplit]
31
+ );
32
+ return /* @__PURE__ */ jsx(TranslationContext.Provider, { value, children });
33
+ };
34
+ var useTranslationContext = () => {
35
+ const context = useContext(TranslationContext);
36
+ if (!context) {
37
+ throw new Error(
38
+ "useTranslationContext must be used within a TranslationProvider"
39
+ );
40
+ }
41
+ return context;
42
+ };
43
+
44
+ export { TranslationContext, TranslationProvider, useTranslationContext };
package/dist/index.cjs ADDED
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ // src/core/translate.ts
4
+ var createTranslate = (translation, keySplit = true) => {
5
+ return (key, variables) => {
6
+ const keyList = keySplit ? key.split(".") : [key];
7
+ let parent = translation;
8
+ for (const k of keyList) {
9
+ if (parent && typeof parent === "object" && k in parent) {
10
+ parent = parent[k];
11
+ } else {
12
+ return key;
13
+ }
14
+ }
15
+ if (typeof parent === "string" && variables) {
16
+ return parent.replace(
17
+ /\{(\w+)\}/g,
18
+ (_, name) => String(variables[name] ?? `{${name}}`)
19
+ );
20
+ }
21
+ return typeof parent === "string" ? parent : key;
22
+ };
23
+ };
24
+
25
+ // src/core/loader.ts
26
+ var getTranslation = (load, locale, namespace = "common") => {
27
+ const dictionary = load(locale, namespace);
28
+ const t = createTranslate(dictionary);
29
+ return { t, locale };
30
+ };
31
+
32
+ exports.createTranslate = createTranslate;
33
+ exports.getTranslation = getTranslation;
@@ -0,0 +1,36 @@
1
+ type TranslationValue = string | {
2
+ [key: string]: TranslationValue;
3
+ };
4
+ type TranslationDictionary = {
5
+ [key: string]: TranslationValue;
6
+ };
7
+ type TranslationVariables = Record<string, string | number>;
8
+ type TranslateFunction = (key: string, variables?: TranslationVariables) => string;
9
+ type TranslationLoader = (locale: string, namespace: string) => TranslationDictionary | undefined;
10
+ type TranslationConfig = {
11
+ locale: string;
12
+ fallbackLocale?: string;
13
+ keySplit?: boolean;
14
+ };
15
+
16
+ /**
17
+ * Create a translation function for a given dictionary
18
+ */
19
+ declare const createTranslate: (translation: TranslationDictionary | undefined, keySplit?: boolean) => TranslateFunction;
20
+
21
+ /**
22
+ * Get translation function for server components or static usage
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * // Server Component
27
+ * const { t } = getTranslation(load, "en");
28
+ * return <h1>{t("greeting", { name: "John" })}</h1>;
29
+ * ```
30
+ */
31
+ declare const getTranslation: (load: TranslationLoader, locale: string, namespace?: string) => {
32
+ t: TranslateFunction;
33
+ locale: string;
34
+ };
35
+
36
+ export { type TranslateFunction, type TranslationConfig, type TranslationDictionary, type TranslationLoader, type TranslationValue, type TranslationVariables, createTranslate, getTranslation };
@@ -0,0 +1,36 @@
1
+ type TranslationValue = string | {
2
+ [key: string]: TranslationValue;
3
+ };
4
+ type TranslationDictionary = {
5
+ [key: string]: TranslationValue;
6
+ };
7
+ type TranslationVariables = Record<string, string | number>;
8
+ type TranslateFunction = (key: string, variables?: TranslationVariables) => string;
9
+ type TranslationLoader = (locale: string, namespace: string) => TranslationDictionary | undefined;
10
+ type TranslationConfig = {
11
+ locale: string;
12
+ fallbackLocale?: string;
13
+ keySplit?: boolean;
14
+ };
15
+
16
+ /**
17
+ * Create a translation function for a given dictionary
18
+ */
19
+ declare const createTranslate: (translation: TranslationDictionary | undefined, keySplit?: boolean) => TranslateFunction;
20
+
21
+ /**
22
+ * Get translation function for server components or static usage
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * // Server Component
27
+ * const { t } = getTranslation(load, "en");
28
+ * return <h1>{t("greeting", { name: "John" })}</h1>;
29
+ * ```
30
+ */
31
+ declare const getTranslation: (load: TranslationLoader, locale: string, namespace?: string) => {
32
+ t: TranslateFunction;
33
+ locale: string;
34
+ };
35
+
36
+ export { type TranslateFunction, type TranslationConfig, type TranslationDictionary, type TranslationLoader, type TranslationValue, type TranslationVariables, createTranslate, getTranslation };
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ // src/core/translate.ts
2
+ var createTranslate = (translation, keySplit = true) => {
3
+ return (key, variables) => {
4
+ const keyList = keySplit ? key.split(".") : [key];
5
+ let parent = translation;
6
+ for (const k of keyList) {
7
+ if (parent && typeof parent === "object" && k in parent) {
8
+ parent = parent[k];
9
+ } else {
10
+ return key;
11
+ }
12
+ }
13
+ if (typeof parent === "string" && variables) {
14
+ return parent.replace(
15
+ /\{(\w+)\}/g,
16
+ (_, name) => String(variables[name] ?? `{${name}}`)
17
+ );
18
+ }
19
+ return typeof parent === "string" ? parent : key;
20
+ };
21
+ };
22
+
23
+ // src/core/loader.ts
24
+ var getTranslation = (load, locale, namespace = "common") => {
25
+ const dictionary = load(locale, namespace);
26
+ const t = createTranslate(dictionary);
27
+ return { t, locale };
28
+ };
29
+
30
+ export { createTranslate, getTranslation };
@@ -0,0 +1,96 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var chunk5W5CCQME_cjs = require('../chunk-5W5CCQME.cjs');
5
+ var react = require('react');
6
+ var router = require('next/router');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+ var navigation = require('next/navigation');
9
+ var NextLink = require('next/link');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var NextLink__default = /*#__PURE__*/_interopDefault(NextLink);
14
+
15
+ var NextTranslationProvider = ({
16
+ children,
17
+ load,
18
+ keySplit = true
19
+ }) => {
20
+ const router$1 = router.useRouter();
21
+ const locale = router$1.locale ?? "en";
22
+ const locales = router$1.locales ?? ["en"];
23
+ const defaultLocale = router$1.defaultLocale ?? "en";
24
+ const value = react.useMemo(
25
+ () => ({
26
+ locale,
27
+ locales,
28
+ defaultLocale,
29
+ setLocale: (newLocale) => {
30
+ router$1.push(router$1.pathname, router$1.asPath, { locale: newLocale });
31
+ },
32
+ load,
33
+ keySplit
34
+ }),
35
+ [locale, locales, defaultLocale, router$1, load, keySplit]
36
+ );
37
+ return /* @__PURE__ */ jsxRuntime.jsx(chunk5W5CCQME_cjs.TranslationContext.Provider, { value, children });
38
+ };
39
+ var AppTranslationProvider = ({
40
+ children,
41
+ locale,
42
+ locales,
43
+ defaultLocale,
44
+ load,
45
+ keySplit = true
46
+ }) => {
47
+ if (locales.length === 0) {
48
+ throw new Error("AppTranslationProvider: locales array must not be empty");
49
+ }
50
+ const router = navigation.useRouter();
51
+ const pathname = navigation.usePathname();
52
+ const value = react.useMemo(
53
+ () => ({
54
+ locale,
55
+ locales,
56
+ defaultLocale: defaultLocale ?? locales[0],
57
+ setLocale: (newLocale) => {
58
+ const segments = pathname.split("/");
59
+ segments[1] = newLocale;
60
+ router.push(segments.join("/"));
61
+ },
62
+ load,
63
+ keySplit
64
+ }),
65
+ [locale, locales, defaultLocale, pathname, router, load, keySplit]
66
+ );
67
+ return /* @__PURE__ */ jsxRuntime.jsx(chunk5W5CCQME_cjs.TranslationContext.Provider, { value, children });
68
+ };
69
+ var Link = ({ href, locale, ...props }) => {
70
+ const { locale: currentLocale, defaultLocale } = chunk5W5CCQME_cjs.useTranslationContext();
71
+ const targetLocale = locale ?? currentLocale;
72
+ if (typeof href === "string") {
73
+ if (href.startsWith("http") || href.startsWith("//")) {
74
+ return /* @__PURE__ */ jsxRuntime.jsx(NextLink__default.default, { href, ...props });
75
+ }
76
+ if (targetLocale === defaultLocale) {
77
+ const cleanHref = href.startsWith("/") ? href : `/${href}`;
78
+ return /* @__PURE__ */ jsxRuntime.jsx(NextLink__default.default, { href: cleanHref, ...props });
79
+ }
80
+ const localizedHref2 = href.startsWith("/") ? `/${targetLocale}${href}` : `/${targetLocale}/${href}`;
81
+ return /* @__PURE__ */ jsxRuntime.jsx(NextLink__default.default, { href: localizedHref2, ...props });
82
+ }
83
+ const pathname = href.pathname ?? "/";
84
+ if (targetLocale === defaultLocale) {
85
+ return /* @__PURE__ */ jsxRuntime.jsx(NextLink__default.default, { href, ...props });
86
+ }
87
+ const localizedHref = {
88
+ ...href,
89
+ pathname: pathname.startsWith("/") ? `/${targetLocale}${pathname}` : `/${targetLocale}/${pathname}`
90
+ };
91
+ return /* @__PURE__ */ jsxRuntime.jsx(NextLink__default.default, { href: localizedHref, ...props });
92
+ };
93
+
94
+ exports.AppTranslationProvider = AppTranslationProvider;
95
+ exports.Link = Link;
96
+ exports.TranslationProvider = NextTranslationProvider;
@@ -0,0 +1,38 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode, ComponentProps } from 'react';
3
+ import { T as TranslationLoader } from '../types-BpDtTtSF.cjs';
4
+ import NextLink from 'next/link';
5
+
6
+ type NextTranslationProviderProps = {
7
+ children: ReactNode;
8
+ load: TranslationLoader;
9
+ keySplit?: boolean;
10
+ };
11
+ /**
12
+ * Translation provider for Next.js applications
13
+ * Automatically uses locale info from Next.js router
14
+ */
15
+ declare const NextTranslationProvider: ({ children, load, keySplit, }: NextTranslationProviderProps) => react_jsx_runtime.JSX.Element;
16
+
17
+ type AppTranslationProviderProps = {
18
+ children: ReactNode;
19
+ locale: string;
20
+ locales: readonly string[];
21
+ defaultLocale?: string;
22
+ load: TranslationLoader;
23
+ keySplit?: boolean;
24
+ };
25
+ /**
26
+ * Translation provider for Next.js App Router
27
+ * Locale is passed from route params [lang]
28
+ */
29
+ declare const AppTranslationProvider: ({ children, locale, locales, defaultLocale, load, keySplit, }: AppTranslationProviderProps) => react_jsx_runtime.JSX.Element;
30
+
31
+ type LinkProps = ComponentProps<typeof NextLink>;
32
+ /**
33
+ * Link component that automatically prefixes href with the current locale.
34
+ * For the default locale, no prefix is added (Pages Router behavior).
35
+ */
36
+ declare const Link: ({ href, locale, ...props }: LinkProps) => react_jsx_runtime.JSX.Element;
37
+
38
+ export { AppTranslationProvider, type AppTranslationProviderProps, Link, type LinkProps, NextTranslationProvider as TranslationProvider, type NextTranslationProviderProps as TranslationProviderProps };
@@ -0,0 +1,38 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode, ComponentProps } from 'react';
3
+ import { T as TranslationLoader } from '../types-BpDtTtSF.js';
4
+ import NextLink from 'next/link';
5
+
6
+ type NextTranslationProviderProps = {
7
+ children: ReactNode;
8
+ load: TranslationLoader;
9
+ keySplit?: boolean;
10
+ };
11
+ /**
12
+ * Translation provider for Next.js applications
13
+ * Automatically uses locale info from Next.js router
14
+ */
15
+ declare const NextTranslationProvider: ({ children, load, keySplit, }: NextTranslationProviderProps) => react_jsx_runtime.JSX.Element;
16
+
17
+ type AppTranslationProviderProps = {
18
+ children: ReactNode;
19
+ locale: string;
20
+ locales: readonly string[];
21
+ defaultLocale?: string;
22
+ load: TranslationLoader;
23
+ keySplit?: boolean;
24
+ };
25
+ /**
26
+ * Translation provider for Next.js App Router
27
+ * Locale is passed from route params [lang]
28
+ */
29
+ declare const AppTranslationProvider: ({ children, locale, locales, defaultLocale, load, keySplit, }: AppTranslationProviderProps) => react_jsx_runtime.JSX.Element;
30
+
31
+ type LinkProps = ComponentProps<typeof NextLink>;
32
+ /**
33
+ * Link component that automatically prefixes href with the current locale.
34
+ * For the default locale, no prefix is added (Pages Router behavior).
35
+ */
36
+ declare const Link: ({ href, locale, ...props }: LinkProps) => react_jsx_runtime.JSX.Element;
37
+
38
+ export { AppTranslationProvider, type AppTranslationProviderProps, Link, type LinkProps, NextTranslationProvider as TranslationProvider, type NextTranslationProviderProps as TranslationProviderProps };
@@ -0,0 +1,88 @@
1
+ "use client";
2
+ import { TranslationContext, useTranslationContext } from '../chunk-X3HAZ4DT.js';
3
+ import { useMemo } from 'react';
4
+ import { useRouter } from 'next/router';
5
+ import { jsx } from 'react/jsx-runtime';
6
+ import { useRouter as useRouter$1, usePathname } from 'next/navigation';
7
+ import NextLink from 'next/link';
8
+
9
+ var NextTranslationProvider = ({
10
+ children,
11
+ load,
12
+ keySplit = true
13
+ }) => {
14
+ const router = useRouter();
15
+ const locale = router.locale ?? "en";
16
+ const locales = router.locales ?? ["en"];
17
+ const defaultLocale = router.defaultLocale ?? "en";
18
+ const value = useMemo(
19
+ () => ({
20
+ locale,
21
+ locales,
22
+ defaultLocale,
23
+ setLocale: (newLocale) => {
24
+ router.push(router.pathname, router.asPath, { locale: newLocale });
25
+ },
26
+ load,
27
+ keySplit
28
+ }),
29
+ [locale, locales, defaultLocale, router, load, keySplit]
30
+ );
31
+ return /* @__PURE__ */ jsx(TranslationContext.Provider, { value, children });
32
+ };
33
+ var AppTranslationProvider = ({
34
+ children,
35
+ locale,
36
+ locales,
37
+ defaultLocale,
38
+ load,
39
+ keySplit = true
40
+ }) => {
41
+ if (locales.length === 0) {
42
+ throw new Error("AppTranslationProvider: locales array must not be empty");
43
+ }
44
+ const router = useRouter$1();
45
+ const pathname = usePathname();
46
+ const value = useMemo(
47
+ () => ({
48
+ locale,
49
+ locales,
50
+ defaultLocale: defaultLocale ?? locales[0],
51
+ setLocale: (newLocale) => {
52
+ const segments = pathname.split("/");
53
+ segments[1] = newLocale;
54
+ router.push(segments.join("/"));
55
+ },
56
+ load,
57
+ keySplit
58
+ }),
59
+ [locale, locales, defaultLocale, pathname, router, load, keySplit]
60
+ );
61
+ return /* @__PURE__ */ jsx(TranslationContext.Provider, { value, children });
62
+ };
63
+ var Link = ({ href, locale, ...props }) => {
64
+ const { locale: currentLocale, defaultLocale } = useTranslationContext();
65
+ const targetLocale = locale ?? currentLocale;
66
+ if (typeof href === "string") {
67
+ if (href.startsWith("http") || href.startsWith("//")) {
68
+ return /* @__PURE__ */ jsx(NextLink, { href, ...props });
69
+ }
70
+ if (targetLocale === defaultLocale) {
71
+ const cleanHref = href.startsWith("/") ? href : `/${href}`;
72
+ return /* @__PURE__ */ jsx(NextLink, { href: cleanHref, ...props });
73
+ }
74
+ const localizedHref2 = href.startsWith("/") ? `/${targetLocale}${href}` : `/${targetLocale}/${href}`;
75
+ return /* @__PURE__ */ jsx(NextLink, { href: localizedHref2, ...props });
76
+ }
77
+ const pathname = href.pathname ?? "/";
78
+ if (targetLocale === defaultLocale) {
79
+ return /* @__PURE__ */ jsx(NextLink, { href, ...props });
80
+ }
81
+ const localizedHref = {
82
+ ...href,
83
+ pathname: pathname.startsWith("/") ? `/${targetLocale}${pathname}` : `/${targetLocale}/${pathname}`
84
+ };
85
+ return /* @__PURE__ */ jsx(NextLink, { href: localizedHref, ...props });
86
+ };
87
+
88
+ export { AppTranslationProvider, Link, NextTranslationProvider as TranslationProvider };
@@ -0,0 +1,72 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var chunk5W5CCQME_cjs = require('../chunk-5W5CCQME.cjs');
5
+ var react = require('react');
6
+
7
+ // src/react/hooks/use-locale.ts
8
+ var useLocale = () => {
9
+ const { locale, locales, setLocale } = chunk5W5CCQME_cjs.useTranslationContext();
10
+ return { locale, locales, setLocale };
11
+ };
12
+
13
+ // src/core/translate.ts
14
+ var createTranslate = (translation, keySplit = true) => {
15
+ return (key, variables) => {
16
+ const keyList = keySplit ? key.split(".") : [key];
17
+ let parent = translation;
18
+ for (const k of keyList) {
19
+ if (parent && typeof parent === "object" && k in parent) {
20
+ parent = parent[k];
21
+ } else {
22
+ return key;
23
+ }
24
+ }
25
+ if (typeof parent === "string" && variables) {
26
+ return parent.replace(
27
+ /\{(\w+)\}/g,
28
+ (_, name) => String(variables[name] ?? `{${name}}`)
29
+ );
30
+ }
31
+ return typeof parent === "string" ? parent : key;
32
+ };
33
+ };
34
+
35
+ // src/react/hooks/use-translation-loader.ts
36
+ var useTranslationLoader = (namespace) => {
37
+ const { locale, defaultLocale, load, keySplit } = chunk5W5CCQME_cjs.useTranslationContext();
38
+ const translation = react.useMemo(() => {
39
+ let dict = load(locale, namespace);
40
+ if (!dict && defaultLocale !== locale) {
41
+ dict = load(defaultLocale, namespace);
42
+ }
43
+ return dict;
44
+ }, [locale, defaultLocale, namespace, load]);
45
+ const t = react.useMemo(
46
+ () => createTranslate(translation, keySplit),
47
+ [translation, keySplit]
48
+ );
49
+ return { translation, t, locale };
50
+ };
51
+
52
+ // src/react/hooks/use-translation.ts
53
+ var useTranslation = (namespace = "common") => {
54
+ const { t, locale } = useTranslationLoader(namespace);
55
+ return { t, locale };
56
+ };
57
+
58
+ Object.defineProperty(exports, "TranslationContext", {
59
+ enumerable: true,
60
+ get: function () { return chunk5W5CCQME_cjs.TranslationContext; }
61
+ });
62
+ Object.defineProperty(exports, "TranslationProvider", {
63
+ enumerable: true,
64
+ get: function () { return chunk5W5CCQME_cjs.TranslationProvider; }
65
+ });
66
+ Object.defineProperty(exports, "useTranslationContext", {
67
+ enumerable: true,
68
+ get: function () { return chunk5W5CCQME_cjs.useTranslationContext; }
69
+ });
70
+ exports.useLocale = useLocale;
71
+ exports.useTranslation = useTranslation;
72
+ exports.useTranslationLoader = useTranslationLoader;
@@ -0,0 +1,44 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
3
+ import { ReactNode } from 'react';
4
+ import { T as TranslationLoader, a as TranslationDictionary, b as TranslateFunction } from '../types-BpDtTtSF.cjs';
5
+
6
+ type TranslationContextValue = {
7
+ locale: string;
8
+ locales: readonly string[];
9
+ defaultLocale: string;
10
+ setLocale: (locale: string) => void;
11
+ load: TranslationLoader;
12
+ keySplit?: boolean;
13
+ };
14
+ declare const TranslationContext: react.Context<TranslationContextValue | null>;
15
+ type TranslationProviderProps = {
16
+ children: ReactNode;
17
+ locales: readonly string[];
18
+ defaultLocale?: string;
19
+ load: TranslationLoader;
20
+ keySplit?: boolean;
21
+ };
22
+ declare const TranslationProvider: ({ children, locales, defaultLocale, load, keySplit, }: TranslationProviderProps) => react_jsx_runtime.JSX.Element;
23
+
24
+ declare const useTranslationContext: () => TranslationContextValue;
25
+
26
+ declare const useLocale: () => {
27
+ locale: string;
28
+ locales: readonly string[];
29
+ setLocale: (locale: string) => void;
30
+ };
31
+
32
+ declare const useTranslationLoader: (namespace: string) => {
33
+ translation: TranslationDictionary | undefined;
34
+ t: TranslateFunction;
35
+ locale: string;
36
+ };
37
+
38
+ type UseTranslationResult = {
39
+ t: TranslateFunction;
40
+ locale: string;
41
+ };
42
+ declare const useTranslation: (namespace?: string) => UseTranslationResult;
43
+
44
+ export { TranslationContext, type TranslationContextValue, TranslationProvider, type TranslationProviderProps, type UseTranslationResult, useLocale, useTranslation, useTranslationContext, useTranslationLoader };
@@ -0,0 +1,44 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
3
+ import { ReactNode } from 'react';
4
+ import { T as TranslationLoader, a as TranslationDictionary, b as TranslateFunction } from '../types-BpDtTtSF.js';
5
+
6
+ type TranslationContextValue = {
7
+ locale: string;
8
+ locales: readonly string[];
9
+ defaultLocale: string;
10
+ setLocale: (locale: string) => void;
11
+ load: TranslationLoader;
12
+ keySplit?: boolean;
13
+ };
14
+ declare const TranslationContext: react.Context<TranslationContextValue | null>;
15
+ type TranslationProviderProps = {
16
+ children: ReactNode;
17
+ locales: readonly string[];
18
+ defaultLocale?: string;
19
+ load: TranslationLoader;
20
+ keySplit?: boolean;
21
+ };
22
+ declare const TranslationProvider: ({ children, locales, defaultLocale, load, keySplit, }: TranslationProviderProps) => react_jsx_runtime.JSX.Element;
23
+
24
+ declare const useTranslationContext: () => TranslationContextValue;
25
+
26
+ declare const useLocale: () => {
27
+ locale: string;
28
+ locales: readonly string[];
29
+ setLocale: (locale: string) => void;
30
+ };
31
+
32
+ declare const useTranslationLoader: (namespace: string) => {
33
+ translation: TranslationDictionary | undefined;
34
+ t: TranslateFunction;
35
+ locale: string;
36
+ };
37
+
38
+ type UseTranslationResult = {
39
+ t: TranslateFunction;
40
+ locale: string;
41
+ };
42
+ declare const useTranslation: (namespace?: string) => UseTranslationResult;
43
+
44
+ export { TranslationContext, type TranslationContextValue, TranslationProvider, type TranslationProviderProps, type UseTranslationResult, useLocale, useTranslation, useTranslationContext, useTranslationLoader };
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { useTranslationContext } from '../chunk-X3HAZ4DT.js';
3
+ export { TranslationContext, TranslationProvider, useTranslationContext } from '../chunk-X3HAZ4DT.js';
4
+ import { useMemo } from 'react';
5
+
6
+ // src/react/hooks/use-locale.ts
7
+ var useLocale = () => {
8
+ const { locale, locales, setLocale } = useTranslationContext();
9
+ return { locale, locales, setLocale };
10
+ };
11
+
12
+ // src/core/translate.ts
13
+ var createTranslate = (translation, keySplit = true) => {
14
+ return (key, variables) => {
15
+ const keyList = keySplit ? key.split(".") : [key];
16
+ let parent = translation;
17
+ for (const k of keyList) {
18
+ if (parent && typeof parent === "object" && k in parent) {
19
+ parent = parent[k];
20
+ } else {
21
+ return key;
22
+ }
23
+ }
24
+ if (typeof parent === "string" && variables) {
25
+ return parent.replace(
26
+ /\{(\w+)\}/g,
27
+ (_, name) => String(variables[name] ?? `{${name}}`)
28
+ );
29
+ }
30
+ return typeof parent === "string" ? parent : key;
31
+ };
32
+ };
33
+
34
+ // src/react/hooks/use-translation-loader.ts
35
+ var useTranslationLoader = (namespace) => {
36
+ const { locale, defaultLocale, load, keySplit } = useTranslationContext();
37
+ const translation = useMemo(() => {
38
+ let dict = load(locale, namespace);
39
+ if (!dict && defaultLocale !== locale) {
40
+ dict = load(defaultLocale, namespace);
41
+ }
42
+ return dict;
43
+ }, [locale, defaultLocale, namespace, load]);
44
+ const t = useMemo(
45
+ () => createTranslate(translation, keySplit),
46
+ [translation, keySplit]
47
+ );
48
+ return { translation, t, locale };
49
+ };
50
+
51
+ // src/react/hooks/use-translation.ts
52
+ var useTranslation = (namespace = "common") => {
53
+ const { t, locale } = useTranslationLoader(namespace);
54
+ return { t, locale };
55
+ };
56
+
57
+ export { useLocale, useTranslation, useTranslationLoader };
@@ -0,0 +1,11 @@
1
+ type TranslationValue = string | {
2
+ [key: string]: TranslationValue;
3
+ };
4
+ type TranslationDictionary = {
5
+ [key: string]: TranslationValue;
6
+ };
7
+ type TranslationVariables = Record<string, string | number>;
8
+ type TranslateFunction = (key: string, variables?: TranslationVariables) => string;
9
+ type TranslationLoader = (locale: string, namespace: string) => TranslationDictionary | undefined;
10
+
11
+ export type { TranslationLoader as T, TranslationDictionary as a, TranslateFunction as b };
@@ -0,0 +1,11 @@
1
+ type TranslationValue = string | {
2
+ [key: string]: TranslationValue;
3
+ };
4
+ type TranslationDictionary = {
5
+ [key: string]: TranslationValue;
6
+ };
7
+ type TranslationVariables = Record<string, string | number>;
8
+ type TranslateFunction = (key: string, variables?: TranslationVariables) => string;
9
+ type TranslationLoader = (locale: string, namespace: string) => TranslationDictionary | undefined;
10
+
11
+ export type { TranslationLoader as T, TranslationDictionary as a, TranslateFunction as b };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ // src/vite/loader.ts
4
+ var createViteLoader = (modules, basePath = "./locales") => {
5
+ return (locale, namespace) => {
6
+ const path = `${basePath}/${locale}/${namespace}.json`;
7
+ const module = modules[path];
8
+ if (!module) {
9
+ return void 0;
10
+ }
11
+ if (typeof module === "object" && "default" in module) {
12
+ return module.default;
13
+ }
14
+ return module;
15
+ };
16
+ };
17
+
18
+ exports.createViteLoader = createViteLoader;
@@ -0,0 +1,20 @@
1
+ type TranslationValue = string | {
2
+ [key: string]: TranslationValue;
3
+ };
4
+ type TranslationDictionary = {
5
+ [key: string]: TranslationValue;
6
+ };
7
+ type TranslationLoader = (locale: string, namespace: string) => TranslationDictionary | undefined;
8
+
9
+ /**
10
+ * Create a translation loader from Vite's import.meta.glob result
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const modules = import.meta.glob('./locales/*.json', { eager: true });
15
+ * const load = createViteLoader(modules);
16
+ * ```
17
+ */
18
+ declare const createViteLoader: (modules: Record<string, unknown>, basePath?: string) => TranslationLoader;
19
+
20
+ export { createViteLoader };
@@ -0,0 +1,20 @@
1
+ type TranslationValue = string | {
2
+ [key: string]: TranslationValue;
3
+ };
4
+ type TranslationDictionary = {
5
+ [key: string]: TranslationValue;
6
+ };
7
+ type TranslationLoader = (locale: string, namespace: string) => TranslationDictionary | undefined;
8
+
9
+ /**
10
+ * Create a translation loader from Vite's import.meta.glob result
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const modules = import.meta.glob('./locales/*.json', { eager: true });
15
+ * const load = createViteLoader(modules);
16
+ * ```
17
+ */
18
+ declare const createViteLoader: (modules: Record<string, unknown>, basePath?: string) => TranslationLoader;
19
+
20
+ export { createViteLoader };
@@ -0,0 +1,16 @@
1
+ // src/vite/loader.ts
2
+ var createViteLoader = (modules, basePath = "./locales") => {
3
+ return (locale, namespace) => {
4
+ const path = `${basePath}/${locale}/${namespace}.json`;
5
+ const module = modules[path];
6
+ if (!module) {
7
+ return void 0;
8
+ }
9
+ if (typeof module === "object" && "default" in module) {
10
+ return module.default;
11
+ }
12
+ return module;
13
+ };
14
+ };
15
+
16
+ export { createViteLoader };
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@onruntime/translations",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight i18n library for React, Next.js, and React Native",
5
+ "author": "onRuntime Studio <contact@onruntime.com>",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/onRuntime/onruntime.git",
9
+ "directory": "packages/translations"
10
+ },
11
+ "homepage": "https://github.com/onRuntime/onruntime/tree/master/packages/translations#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/onRuntime/onruntime/issues"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "type": "module",
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js",
25
+ "require": "./dist/index.cjs"
26
+ },
27
+ "./react": {
28
+ "types": "./dist/react/index.d.ts",
29
+ "import": "./dist/react/index.js",
30
+ "require": "./dist/react/index.cjs"
31
+ },
32
+ "./next": {
33
+ "types": "./dist/next/index.d.ts",
34
+ "import": "./dist/next/index.js",
35
+ "require": "./dist/next/index.cjs"
36
+ },
37
+ "./vite": {
38
+ "types": "./dist/vite/index.d.ts",
39
+ "import": "./dist/vite/index.js",
40
+ "require": "./dist/vite/index.cjs"
41
+ }
42
+ },
43
+ "files": [
44
+ "dist"
45
+ ],
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "dev": "tsup --watch",
49
+ "type-check": "tsc --noEmit"
50
+ },
51
+ "peerDependencies": {
52
+ "react": ">=18.0.0"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "next": {
56
+ "optional": true
57
+ },
58
+ "react-native": {
59
+ "optional": true
60
+ }
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^25.0.3",
64
+ "@types/react": "^19",
65
+ "next": "^16.1.1",
66
+ "react": "^19.2.3",
67
+ "tsup": "^8",
68
+ "typescript": "^5"
69
+ },
70
+ "keywords": [
71
+ "i18n",
72
+ "translations",
73
+ "react",
74
+ "nextjs",
75
+ "react-native",
76
+ "internationalization"
77
+ ],
78
+ "license": "MIT"
79
+ }