alabjs 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/dist/adapters/cloudflare.d.ts +31 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +30 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/deno.d.ts +22 -0
- package/dist/adapters/deno.d.ts.map +1 -0
- package/dist/adapters/deno.js +21 -0
- package/dist/adapters/deno.js.map +1 -0
- package/dist/adapters/web.d.ts +47 -0
- package/dist/adapters/web.d.ts.map +1 -0
- package/dist/adapters/web.js +212 -0
- package/dist/adapters/web.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/hooks.d.ts +119 -0
- package/dist/client/hooks.d.ts.map +1 -0
- package/dist/client/hooks.js +220 -0
- package/dist/client/hooks.js.map +1 -0
- package/dist/client/hooks.test.d.ts +2 -0
- package/dist/client/hooks.test.d.ts.map +1 -0
- package/dist/client/hooks.test.js +45 -0
- package/dist/client/hooks.test.js.map +1 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +4 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/offline.d.ts +52 -0
- package/dist/client/offline.d.ts.map +1 -0
- package/dist/client/offline.js +90 -0
- package/dist/client/offline.js.map +1 -0
- package/dist/client/provider.d.ts +12 -0
- package/dist/client/provider.d.ts.map +1 -0
- package/dist/client/provider.js +10 -0
- package/dist/client/provider.js.map +1 -0
- package/dist/commands/build.d.ts +18 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +173 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/dev.d.ts +8 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +447 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/info.d.ts +6 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +92 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/ssg.d.ts +8 -0
- package/dist/commands/ssg.d.ts.map +1 -0
- package/dist/commands/ssg.js +124 -0
- package/dist/commands/ssg.js.map +1 -0
- package/dist/commands/start.d.ts +7 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +26 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/test.d.ts +24 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +87 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/components/ErrorBoundary.d.ts +38 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.js +46 -0
- package/dist/components/ErrorBoundary.js.map +1 -0
- package/dist/components/Font.d.ts +57 -0
- package/dist/components/Font.d.ts.map +1 -0
- package/dist/components/Font.js +33 -0
- package/dist/components/Font.js.map +1 -0
- package/dist/components/Image.d.ts +74 -0
- package/dist/components/Image.d.ts.map +1 -0
- package/dist/components/Image.js +85 -0
- package/dist/components/Image.js.map +1 -0
- package/dist/components/Link.d.ts +23 -0
- package/dist/components/Link.d.ts.map +1 -0
- package/dist/components/Link.js +48 -0
- package/dist/components/Link.js.map +1 -0
- package/dist/components/Script.d.ts +37 -0
- package/dist/components/Script.d.ts.map +1 -0
- package/dist/components/Script.js +70 -0
- package/dist/components/Script.js.map +1 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +6 -0
- package/dist/components/index.js.map +1 -0
- package/dist/i18n/i18n.test.d.ts +2 -0
- package/dist/i18n/i18n.test.d.ts.map +1 -0
- package/dist/i18n/i18n.test.js +132 -0
- package/dist/i18n/i18n.test.js.map +1 -0
- package/dist/i18n/index.d.ts +135 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +189 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/router/code-router.d.ts +204 -0
- package/dist/router/code-router.d.ts.map +1 -0
- package/dist/router/code-router.js +258 -0
- package/dist/router/code-router.js.map +1 -0
- package/dist/router/code-router.test.d.ts +2 -0
- package/dist/router/code-router.test.d.ts.map +1 -0
- package/dist/router/code-router.test.js +128 -0
- package/dist/router/code-router.test.js.map +1 -0
- package/dist/router/index.d.ts +4 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +2 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/manifest.d.ts +12 -0
- package/dist/router/manifest.d.ts.map +1 -0
- package/dist/router/manifest.js +2 -0
- package/dist/router/manifest.js.map +1 -0
- package/dist/server/app.d.ts +13 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +407 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/cache.d.ts +99 -0
- package/dist/server/cache.d.ts.map +1 -0
- package/dist/server/cache.js +161 -0
- package/dist/server/cache.js.map +1 -0
- package/dist/server/cache.test.d.ts +2 -0
- package/dist/server/cache.test.d.ts.map +1 -0
- package/dist/server/cache.test.js +150 -0
- package/dist/server/cache.test.js.map +1 -0
- package/dist/server/csrf.d.ts +28 -0
- package/dist/server/csrf.d.ts.map +1 -0
- package/dist/server/csrf.js +66 -0
- package/dist/server/csrf.js.map +1 -0
- package/dist/server/csrf.test.d.ts +2 -0
- package/dist/server/csrf.test.d.ts.map +1 -0
- package/dist/server/csrf.test.js +154 -0
- package/dist/server/csrf.test.js.map +1 -0
- package/dist/server/image.d.ts +18 -0
- package/dist/server/image.d.ts.map +1 -0
- package/dist/server/image.js +97 -0
- package/dist/server/image.js.map +1 -0
- package/dist/server/index.d.ts +57 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +58 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware.d.ts +53 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +80 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/middleware.test.d.ts +2 -0
- package/dist/server/middleware.test.d.ts.map +1 -0
- package/dist/server/middleware.test.js +125 -0
- package/dist/server/middleware.test.js.map +1 -0
- package/dist/server/revalidate.d.ts +49 -0
- package/dist/server/revalidate.d.ts.map +1 -0
- package/dist/server/revalidate.js +62 -0
- package/dist/server/revalidate.js.map +1 -0
- package/dist/server/revalidate.test.d.ts +2 -0
- package/dist/server/revalidate.test.d.ts.map +1 -0
- package/dist/server/revalidate.test.js +93 -0
- package/dist/server/revalidate.test.js.map +1 -0
- package/dist/server/server-fn.test.d.ts +2 -0
- package/dist/server/server-fn.test.d.ts.map +1 -0
- package/dist/server/server-fn.test.js +105 -0
- package/dist/server/server-fn.test.js.map +1 -0
- package/dist/server/sitemap.d.ts +9 -0
- package/dist/server/sitemap.d.ts.map +1 -0
- package/dist/server/sitemap.js +26 -0
- package/dist/server/sitemap.js.map +1 -0
- package/dist/server/sitemap.test.d.ts +2 -0
- package/dist/server/sitemap.test.d.ts.map +1 -0
- package/dist/server/sitemap.test.js +61 -0
- package/dist/server/sitemap.test.js.map +1 -0
- package/dist/server/sse.d.ts +59 -0
- package/dist/server/sse.d.ts.map +1 -0
- package/dist/server/sse.js +91 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/server/sse.test.d.ts +2 -0
- package/dist/server/sse.test.d.ts.map +1 -0
- package/dist/server/sse.test.js +68 -0
- package/dist/server/sse.test.js.map +1 -0
- package/dist/signals/index.d.ts +101 -0
- package/dist/signals/index.d.ts.map +1 -0
- package/dist/signals/index.js +149 -0
- package/dist/signals/index.js.map +1 -0
- package/dist/signals/signals.test.d.ts +2 -0
- package/dist/signals/signals.test.d.ts.map +1 -0
- package/dist/signals/signals.test.js +146 -0
- package/dist/signals/signals.test.js.map +1 -0
- package/dist/ssr/html.d.ts +27 -0
- package/dist/ssr/html.d.ts.map +1 -0
- package/dist/ssr/html.js +107 -0
- package/dist/ssr/html.js.map +1 -0
- package/dist/ssr/html.test.d.ts +2 -0
- package/dist/ssr/html.test.d.ts.map +1 -0
- package/dist/ssr/html.test.js +178 -0
- package/dist/ssr/html.test.js.map +1 -0
- package/dist/ssr/render.d.ts +46 -0
- package/dist/ssr/render.d.ts.map +1 -0
- package/dist/ssr/render.js +87 -0
- package/dist/ssr/render.js.map +1 -0
- package/dist/ssr/router-dev.d.ts +60 -0
- package/dist/ssr/router-dev.d.ts.map +1 -0
- package/dist/ssr/router-dev.js +205 -0
- package/dist/ssr/router-dev.js.map +1 -0
- package/dist/ssr/router-dev.test.d.ts +2 -0
- package/dist/ssr/router-dev.test.d.ts.map +1 -0
- package/dist/ssr/router-dev.test.js +189 -0
- package/dist/ssr/router-dev.test.js.map +1 -0
- package/dist/test/index.d.ts +93 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +146 -0
- package/dist/test/index.js.map +1 -0
- package/dist/types/index.d.ts +117 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/napi.d.ts +15 -0
- package/dist/types/napi.d.ts.map +1 -0
- package/dist/types/napi.js +2 -0
- package/dist/types/napi.js.map +1 -0
- package/package.json +107 -0
- package/src/adapters/cloudflare.ts +30 -0
- package/src/adapters/deno.ts +21 -0
- package/src/adapters/web.ts +259 -0
- package/src/cli.ts +68 -0
- package/src/client/hooks.test.ts +54 -0
- package/src/client/hooks.ts +329 -0
- package/src/client/index.ts +5 -0
- package/src/client/offline-sw.ts +191 -0
- package/src/client/offline.ts +114 -0
- package/src/client/provider.tsx +14 -0
- package/src/commands/build.ts +201 -0
- package/src/commands/dev.ts +509 -0
- package/src/commands/info.ts +111 -0
- package/src/commands/ssg.ts +177 -0
- package/src/commands/start.ts +32 -0
- package/src/commands/test.ts +102 -0
- package/src/components/ErrorBoundary.tsx +73 -0
- package/src/components/Font.tsx +100 -0
- package/src/components/Image.tsx +141 -0
- package/src/components/Link.tsx +64 -0
- package/src/components/Script.tsx +97 -0
- package/src/components/index.ts +9 -0
- package/src/i18n/i18n.test.tsx +169 -0
- package/src/i18n/index.tsx +256 -0
- package/src/index.ts +10 -0
- package/src/router/code-router.test.ts +146 -0
- package/src/router/code-router.tsx +459 -0
- package/src/router/index.ts +18 -0
- package/src/router/manifest.ts +13 -0
- package/src/server/app.ts +466 -0
- package/src/server/cache.test.ts +192 -0
- package/src/server/cache.ts +195 -0
- package/src/server/csrf.test.ts +199 -0
- package/src/server/csrf.ts +80 -0
- package/src/server/image.ts +112 -0
- package/src/server/index.ts +144 -0
- package/src/server/middleware.test.ts +151 -0
- package/src/server/middleware.ts +95 -0
- package/src/server/revalidate.test.ts +106 -0
- package/src/server/revalidate.ts +75 -0
- package/src/server/server-fn.test.ts +127 -0
- package/src/server/sitemap.test.ts +68 -0
- package/src/server/sitemap.ts +30 -0
- package/src/server/sse.test.ts +81 -0
- package/src/server/sse.ts +110 -0
- package/src/signals/index.ts +177 -0
- package/src/signals/signals.test.ts +164 -0
- package/src/ssr/html.test.ts +200 -0
- package/src/ssr/html.ts +140 -0
- package/src/ssr/render.ts +144 -0
- package/src/ssr/router-dev.test.ts +230 -0
- package/src/ssr/router-dev.ts +229 -0
- package/src/test/index.ts +206 -0
- package/src/types/compiler.d.ts +25 -0
- package/src/types/index.ts +147 -0
- package/src/types/napi.ts +20 -0
- package/src/types/plugins.d.ts +3 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +32 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alab i18n — locale routing with zero runtime overhead.
|
|
3
|
+
*
|
|
4
|
+
* Locale is detected once per request (from URL prefix → cookie → Accept-Language)
|
|
5
|
+
* and injected into a React context. Pages are served at `/:locale/path`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* // i18n.ts (project root)
|
|
10
|
+
* import { createI18nConfig } from "alabjs/i18n";
|
|
11
|
+
*
|
|
12
|
+
* export const i18n = createI18nConfig({
|
|
13
|
+
* locales: ["en", "fil", "es"],
|
|
14
|
+
* defaultLocale: "en",
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* // middleware.ts — redirect bare paths to locale prefix
|
|
20
|
+
* import { i18n } from "./i18n.js";
|
|
21
|
+
* import { redirect } from "alabjs/middleware";
|
|
22
|
+
*
|
|
23
|
+
* export async function middleware(req: Request) {
|
|
24
|
+
* const locale = i18n.detectLocale(req);
|
|
25
|
+
* const { pathname } = new URL(req.url);
|
|
26
|
+
*
|
|
27
|
+
* // Already has a locale prefix — pass through
|
|
28
|
+
* if (i18n.hasLocalePrefix(pathname)) return;
|
|
29
|
+
*
|
|
30
|
+
* // Redirect /about → /en/about
|
|
31
|
+
* return redirect(`/${locale}${pathname}`);
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // app/[locale]/layout.tsx
|
|
37
|
+
* import { LocaleProvider } from "alabjs/i18n";
|
|
38
|
+
*
|
|
39
|
+
* export default function LocaleLayout({ params, children }) {
|
|
40
|
+
* return <LocaleProvider locale={params.locale}>{children}</LocaleProvider>;
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
import { type ReactNode } from "react";
|
|
45
|
+
export interface I18nConfig {
|
|
46
|
+
/** All supported locale codes, e.g. `["en", "fil", "es"]`. */
|
|
47
|
+
locales: string[];
|
|
48
|
+
/** Locale used when no match is found. Must be in `locales`. */
|
|
49
|
+
defaultLocale: string;
|
|
50
|
+
}
|
|
51
|
+
export interface I18nInstance extends I18nConfig {
|
|
52
|
+
/**
|
|
53
|
+
* Detect the best locale for an incoming request.
|
|
54
|
+
*
|
|
55
|
+
* Priority order:
|
|
56
|
+
* 1. URL pathname prefix (`/en/`, `/fil/`)
|
|
57
|
+
* 2. `locale` cookie
|
|
58
|
+
* 3. `Accept-Language` header (first matching locale)
|
|
59
|
+
* 4. `defaultLocale`
|
|
60
|
+
*/
|
|
61
|
+
detectLocale(req: Request): string;
|
|
62
|
+
/** Return true if the pathname already starts with a supported locale prefix. */
|
|
63
|
+
hasLocalePrefix(pathname: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Strip the locale prefix from a pathname.
|
|
66
|
+
* `/en/about` → `/about`, `/about` → `/about`
|
|
67
|
+
*/
|
|
68
|
+
stripLocale(pathname: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Build a locale-prefixed path.
|
|
71
|
+
* `localePath("fil", "/about")` → `/fil/about`
|
|
72
|
+
*/
|
|
73
|
+
localePath(locale: string, path: string): string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Create an i18n configuration instance.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* export const i18n = createI18nConfig({ locales: ["en", "fil"], defaultLocale: "en" });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function createI18nConfig(config: I18nConfig): I18nInstance;
|
|
84
|
+
export interface LocaleProviderProps {
|
|
85
|
+
locale: string;
|
|
86
|
+
children: ReactNode;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Provide the current locale to all child components.
|
|
90
|
+
* Place this in your `app/[locale]/layout.tsx`.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* export default function LocaleLayout({ params, children }) {
|
|
95
|
+
* return <LocaleProvider locale={params.locale}>{children}</LocaleProvider>;
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function LocaleProvider({ locale, children }: LocaleProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
100
|
+
/**
|
|
101
|
+
* Read the current locale from context.
|
|
102
|
+
* Must be used inside a `<LocaleProvider>`.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```tsx
|
|
106
|
+
* const locale = useLocale(); // "en" | "fil" | "es"
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export declare function useLocale(): string;
|
|
110
|
+
import type { AnchorHTMLAttributes } from "react";
|
|
111
|
+
export interface LocaleLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
112
|
+
href: string;
|
|
113
|
+
/**
|
|
114
|
+
* Override the locale for this link. Defaults to the current locale from context.
|
|
115
|
+
* Pass `false` to emit the href with no locale prefix.
|
|
116
|
+
*/
|
|
117
|
+
locale?: string | false;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A `<Link>`-compatible anchor that automatically prefixes the `href` with
|
|
121
|
+
* the current (or specified) locale.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```tsx
|
|
125
|
+
* // Current locale is "en"
|
|
126
|
+
* <LocaleLink href="/about">About</LocaleLink>
|
|
127
|
+
* // renders: <a href="/en/about">About</a>
|
|
128
|
+
*
|
|
129
|
+
* // Switch to Filipino
|
|
130
|
+
* <LocaleLink href="/about" locale="fil">Filipino</LocaleLink>
|
|
131
|
+
* // renders: <a href="/fil/about">Filipino</a>
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare function LocaleLink({ href, locale, children, onClick, ...rest }: LocaleLinkProps): import("react/jsx-runtime").JSX.Element;
|
|
135
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/i18n/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlE,MAAM,WAAW,UAAU;IACzB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C;;;;;;;;OAQG;IACH,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC;IACnC,iFAAiF;IACjF,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3C;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CAClD;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,CAgDjE;AAMD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,mBAAmB,2CAEvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAID,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAElD,MAAM,WAAW,eAAgB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,EAAE,eAAe,2CAmBvF"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Alab i18n — locale routing with zero runtime overhead.
|
|
4
|
+
*
|
|
5
|
+
* Locale is detected once per request (from URL prefix → cookie → Accept-Language)
|
|
6
|
+
* and injected into a React context. Pages are served at `/:locale/path`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* // i18n.ts (project root)
|
|
11
|
+
* import { createI18nConfig } from "alabjs/i18n";
|
|
12
|
+
*
|
|
13
|
+
* export const i18n = createI18nConfig({
|
|
14
|
+
* locales: ["en", "fil", "es"],
|
|
15
|
+
* defaultLocale: "en",
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* // middleware.ts — redirect bare paths to locale prefix
|
|
21
|
+
* import { i18n } from "./i18n.js";
|
|
22
|
+
* import { redirect } from "alabjs/middleware";
|
|
23
|
+
*
|
|
24
|
+
* export async function middleware(req: Request) {
|
|
25
|
+
* const locale = i18n.detectLocale(req);
|
|
26
|
+
* const { pathname } = new URL(req.url);
|
|
27
|
+
*
|
|
28
|
+
* // Already has a locale prefix — pass through
|
|
29
|
+
* if (i18n.hasLocalePrefix(pathname)) return;
|
|
30
|
+
*
|
|
31
|
+
* // Redirect /about → /en/about
|
|
32
|
+
* return redirect(`/${locale}${pathname}`);
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* ```tsx
|
|
37
|
+
* // app/[locale]/layout.tsx
|
|
38
|
+
* import { LocaleProvider } from "alabjs/i18n";
|
|
39
|
+
*
|
|
40
|
+
* export default function LocaleLayout({ params, children }) {
|
|
41
|
+
* return <LocaleProvider locale={params.locale}>{children}</LocaleProvider>;
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
import { createContext, useContext } from "react";
|
|
46
|
+
/**
|
|
47
|
+
* Create an i18n configuration instance.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* export const i18n = createI18nConfig({ locales: ["en", "fil"], defaultLocale: "en" });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function createI18nConfig(config) {
|
|
55
|
+
const { locales, defaultLocale } = config;
|
|
56
|
+
if (!locales.includes(defaultLocale)) {
|
|
57
|
+
throw new Error(`[alabjs/i18n] defaultLocale "${defaultLocale}" must be in the locales array`);
|
|
58
|
+
}
|
|
59
|
+
const localeSet = new Set(locales);
|
|
60
|
+
function detectLocale(req) {
|
|
61
|
+
const url = new URL(req.url);
|
|
62
|
+
// 1. URL prefix
|
|
63
|
+
const firstSegment = url.pathname.split("/")[1] ?? "";
|
|
64
|
+
if (localeSet.has(firstSegment))
|
|
65
|
+
return firstSegment;
|
|
66
|
+
// 2. Cookie
|
|
67
|
+
const cookieHeader = req.headers.get("cookie") ?? "";
|
|
68
|
+
const localeCookie = parseCookieLocale(cookieHeader, locales);
|
|
69
|
+
if (localeCookie)
|
|
70
|
+
return localeCookie;
|
|
71
|
+
// 3. Accept-Language
|
|
72
|
+
const acceptLang = req.headers.get("accept-language") ?? "";
|
|
73
|
+
const detected = parseAcceptLanguage(acceptLang, locales);
|
|
74
|
+
if (detected)
|
|
75
|
+
return detected;
|
|
76
|
+
return defaultLocale;
|
|
77
|
+
}
|
|
78
|
+
function hasLocalePrefix(pathname) {
|
|
79
|
+
const first = pathname.split("/")[1] ?? "";
|
|
80
|
+
return localeSet.has(first);
|
|
81
|
+
}
|
|
82
|
+
function stripLocale(pathname) {
|
|
83
|
+
const first = pathname.split("/")[1] ?? "";
|
|
84
|
+
if (localeSet.has(first)) {
|
|
85
|
+
return pathname.slice(first.length + 1) || "/";
|
|
86
|
+
}
|
|
87
|
+
return pathname;
|
|
88
|
+
}
|
|
89
|
+
function localePath(locale, path) {
|
|
90
|
+
const clean = path.startsWith("/") ? path : `/${path}`;
|
|
91
|
+
return `/${locale}${clean}`;
|
|
92
|
+
}
|
|
93
|
+
return { locales, defaultLocale, detectLocale, hasLocalePrefix, stripLocale, localePath };
|
|
94
|
+
}
|
|
95
|
+
// ─── React context ────────────────────────────────────────────────────────────
|
|
96
|
+
const LocaleCtx = createContext("en");
|
|
97
|
+
/**
|
|
98
|
+
* Provide the current locale to all child components.
|
|
99
|
+
* Place this in your `app/[locale]/layout.tsx`.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```tsx
|
|
103
|
+
* export default function LocaleLayout({ params, children }) {
|
|
104
|
+
* return <LocaleProvider locale={params.locale}>{children}</LocaleProvider>;
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export function LocaleProvider({ locale, children }) {
|
|
109
|
+
return _jsx(LocaleCtx.Provider, { value: locale, children: children });
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Read the current locale from context.
|
|
113
|
+
* Must be used inside a `<LocaleProvider>`.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```tsx
|
|
117
|
+
* const locale = useLocale(); // "en" | "fil" | "es"
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function useLocale() {
|
|
121
|
+
return useContext(LocaleCtx);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A `<Link>`-compatible anchor that automatically prefixes the `href` with
|
|
125
|
+
* the current (or specified) locale.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```tsx
|
|
129
|
+
* // Current locale is "en"
|
|
130
|
+
* <LocaleLink href="/about">About</LocaleLink>
|
|
131
|
+
* // renders: <a href="/en/about">About</a>
|
|
132
|
+
*
|
|
133
|
+
* // Switch to Filipino
|
|
134
|
+
* <LocaleLink href="/about" locale="fil">Filipino</LocaleLink>
|
|
135
|
+
* // renders: <a href="/fil/about">Filipino</a>
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export function LocaleLink({ href, locale, children, onClick, ...rest }) {
|
|
139
|
+
const currentLocale = useLocale();
|
|
140
|
+
const targetLocale = locale === false ? null : (locale ?? currentLocale);
|
|
141
|
+
const resolvedHref = targetLocale ? `/${targetLocale}${href.startsWith("/") ? href : `/${href}`}` : href;
|
|
142
|
+
const handleClick = (e) => {
|
|
143
|
+
onClick?.(e);
|
|
144
|
+
if (e.defaultPrevented)
|
|
145
|
+
return;
|
|
146
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
|
|
147
|
+
return;
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
if (typeof window !== "undefined" && "__alabjs_navigate" in window) {
|
|
150
|
+
window.__alabjs_navigate(resolvedHref);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
window.location.href = resolvedHref;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return _jsx("a", { href: resolvedHref, onClick: handleClick, ...rest, children: children });
|
|
157
|
+
}
|
|
158
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
159
|
+
function parseCookieLocale(cookieHeader, locales) {
|
|
160
|
+
for (const part of cookieHeader.split(";")) {
|
|
161
|
+
const [key, val] = part.trim().split("=");
|
|
162
|
+
if (key?.trim() === "locale" && val && locales.includes(val.trim())) {
|
|
163
|
+
return val.trim();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function parseAcceptLanguage(header, locales) {
|
|
169
|
+
// Parse "en-US,en;q=0.9,fil;q=0.8" → [["en-US", 1], ["en", 0.9], ["fil", 0.8]]
|
|
170
|
+
const entries = header
|
|
171
|
+
.split(",")
|
|
172
|
+
.map((part) => {
|
|
173
|
+
const [lang, q] = part.trim().split(";q=");
|
|
174
|
+
return { lang: lang?.trim() ?? "", q: parseFloat(q ?? "1") };
|
|
175
|
+
})
|
|
176
|
+
.sort((a, b) => b.q - a.q);
|
|
177
|
+
for (const { lang } of entries) {
|
|
178
|
+
// Exact match first (e.g. "fil")
|
|
179
|
+
if (locales.includes(lang))
|
|
180
|
+
return lang;
|
|
181
|
+
// Language-only match (e.g. "en" matches "en-US")
|
|
182
|
+
const base = lang.split("-")[0] ?? "";
|
|
183
|
+
const match = locales.find((l) => l === base || l.startsWith(base + "-"));
|
|
184
|
+
if (match)
|
|
185
|
+
return match;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/i18n/index.tsx"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EACV,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAChB,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AASnD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alab Code-Based Router — type-safe client-side navigation.
|
|
3
|
+
*
|
|
4
|
+
* The file-system router stays the default (zero config). This module is
|
|
5
|
+
* opt-in for large apps that need IDE searchability, typed `href` props,
|
|
6
|
+
* search param schemas, and co-located loaders.
|
|
7
|
+
*
|
|
8
|
+
* Inspired by TanStack Router.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // routes.ts
|
|
13
|
+
* import { createRoute, createRouter } from "alabjs/router";
|
|
14
|
+
* import { z } from "zod";
|
|
15
|
+
* import UserPage from "./app/users/[id]/page.js";
|
|
16
|
+
* import UserError from "./app/users/[id]/error.js";
|
|
17
|
+
*
|
|
18
|
+
* export const userRoute = createRoute({
|
|
19
|
+
* path: "/users/$id",
|
|
20
|
+
* search: z.object({ tab: z.enum(["posts", "about"]).optional() }),
|
|
21
|
+
* loader: ({ params }) => getUser(params.id),
|
|
22
|
+
* component: UserPage,
|
|
23
|
+
* errorComponent: UserError,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* export const router = createRouter([userRoute]);
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ```tsx
|
|
30
|
+
* // app/layout.tsx
|
|
31
|
+
* import { RouterProvider } from "alabjs/router";
|
|
32
|
+
* import { router } from "../routes.js";
|
|
33
|
+
*
|
|
34
|
+
* export default function RootLayout({ children }) {
|
|
35
|
+
* return <RouterProvider router={router}>{children}</RouterProvider>;
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
import { type ComponentType, type ReactNode } from "react";
|
|
40
|
+
/** Extract `$param` names from a path string. */
|
|
41
|
+
type ExtractRouteParams<Path extends string> = Path extends `${string}$${infer Param}/${infer Rest}` ? Param | ExtractRouteParams<`/${Rest}`> : Path extends `${string}$${infer Param}` ? Param : never;
|
|
42
|
+
/** Typed record of path params for a route. */
|
|
43
|
+
export type RouteParams<Path extends string> = [
|
|
44
|
+
ExtractRouteParams<Path>
|
|
45
|
+
] extends [never] ? Record<string, never> : {
|
|
46
|
+
readonly [K in ExtractRouteParams<Path>]: string;
|
|
47
|
+
};
|
|
48
|
+
interface SchemaLike<T> {
|
|
49
|
+
parse(input: unknown): T;
|
|
50
|
+
safeParse(input: unknown): {
|
|
51
|
+
success: true;
|
|
52
|
+
data: T;
|
|
53
|
+
} | {
|
|
54
|
+
success: false;
|
|
55
|
+
error: unknown;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
type InferSchema<S> = S extends SchemaLike<infer T> ? T : Record<string, string>;
|
|
59
|
+
export interface RouteConfig<Path extends string, Search extends SchemaLike<unknown> | undefined, LoaderData> {
|
|
60
|
+
/** URL path using `$param` syntax: `"/users/$id"`, `"/posts/$slug/edit"`. */
|
|
61
|
+
path: Path;
|
|
62
|
+
/**
|
|
63
|
+
* Zod (or any schema) that validates + parses search params.
|
|
64
|
+
* Parsed value is available via `useSearch(route)`.
|
|
65
|
+
*/
|
|
66
|
+
search?: Search;
|
|
67
|
+
/**
|
|
68
|
+
* Runs before the component mounts — blocks render until resolved.
|
|
69
|
+
* Data is available via `useLoaderData(route)`.
|
|
70
|
+
*/
|
|
71
|
+
loader?: (ctx: {
|
|
72
|
+
params: RouteParams<Path>;
|
|
73
|
+
search: Search extends SchemaLike<unknown> ? InferSchema<Search> : Record<string, string>;
|
|
74
|
+
}) => Promise<LoaderData>;
|
|
75
|
+
/** The page component for this route. */
|
|
76
|
+
component: ComponentType<{
|
|
77
|
+
params: RouteParams<Path>;
|
|
78
|
+
search: Search extends SchemaLike<unknown> ? InferSchema<Search> : Record<string, string>;
|
|
79
|
+
loaderData: LoaderData;
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Rendered when the loader throws or component throws during render.
|
|
83
|
+
* Equivalent to `error.tsx` in the file-system router.
|
|
84
|
+
*/
|
|
85
|
+
errorComponent?: ComponentType<{
|
|
86
|
+
error: Error;
|
|
87
|
+
reset: () => void;
|
|
88
|
+
}>;
|
|
89
|
+
/**
|
|
90
|
+
* Rendered while the loader is running.
|
|
91
|
+
* Equivalent to `loading.tsx` in the file-system router.
|
|
92
|
+
*/
|
|
93
|
+
pendingComponent?: ComponentType;
|
|
94
|
+
}
|
|
95
|
+
export interface RouteDescriptor<Path extends string = string, Search extends SchemaLike<unknown> | undefined = undefined, LoaderData = undefined> extends RouteConfig<Path, Search, LoaderData> {
|
|
96
|
+
/** @internal Compiled regex for matching this route's path. */
|
|
97
|
+
_regex: RegExp;
|
|
98
|
+
/** @internal Ordered param names extracted from the path. */
|
|
99
|
+
_paramNames: string[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Define a type-safe route.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* export const postRoute = createRoute({
|
|
107
|
+
* path: "/posts/$id",
|
|
108
|
+
* loader: ({ params }) => fetchPost(params.id),
|
|
109
|
+
* component: PostPage,
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare function createRoute<Path extends string, Search extends SchemaLike<unknown> | undefined = undefined, LoaderData = undefined>(config: RouteConfig<Path, Search, LoaderData>): RouteDescriptor<Path, Search, LoaderData>;
|
|
114
|
+
export interface Router {
|
|
115
|
+
routes: RouteDescriptor[];
|
|
116
|
+
}
|
|
117
|
+
/** Assemble multiple routes into a router instance. */
|
|
118
|
+
export declare function createRouter(routes: RouteDescriptor[]): Router;
|
|
119
|
+
export interface RouterProviderProps {
|
|
120
|
+
router: Router;
|
|
121
|
+
children?: ReactNode;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Wrap your root layout with `RouterProvider` to enable typed navigation.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```tsx
|
|
128
|
+
* import { RouterProvider } from "alabjs/router";
|
|
129
|
+
* import { router } from "../routes.js";
|
|
130
|
+
*
|
|
131
|
+
* export default function RootLayout({ children }) {
|
|
132
|
+
* return <RouterProvider router={router}>{children}</RouterProvider>;
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export declare function RouterProvider({ router, children }: RouterProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
137
|
+
/**
|
|
138
|
+
* Access typed path params for a specific route.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```tsx
|
|
142
|
+
* const params = useParams(userRoute);
|
|
143
|
+
* params.id; // string ✅
|
|
144
|
+
* params.foo; // TS error ✅
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export declare function useParams<Path extends string, S extends SchemaLike<unknown> | undefined, L>(_route: RouteDescriptor<Path, S, L>): RouteParams<Path>;
|
|
148
|
+
/**
|
|
149
|
+
* Access typed, schema-validated search params for a specific route.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```tsx
|
|
153
|
+
* const search = useSearch(postRoute);
|
|
154
|
+
* search.tab; // "posts" | "about" | undefined ✅
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
export declare function useSearch<Path extends string, S extends SchemaLike<unknown> | undefined, L>(_route: RouteDescriptor<Path, S, L>): S extends SchemaLike<unknown> ? InferSchema<S> : Record<string, string>;
|
|
158
|
+
/**
|
|
159
|
+
* Access the data returned by the route's `loader` function.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```tsx
|
|
163
|
+
* const user = useLoaderData(userRoute);
|
|
164
|
+
* user.name; // typed from loader return ✅
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export declare function useLoaderData<Path extends string, S extends SchemaLike<unknown> | undefined, L>(_route: RouteDescriptor<Path, S, L>): L;
|
|
168
|
+
/**
|
|
169
|
+
* Returns a typed `navigate` function.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```tsx
|
|
173
|
+
* const nav = useNavigate();
|
|
174
|
+
* nav("/users/42");
|
|
175
|
+
* nav("/users/42?tab=posts");
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export declare function useNavigate(): (href: string) => void;
|
|
179
|
+
type LinkToRoute<Path extends string, S extends SchemaLike<unknown> | undefined, L> = {
|
|
180
|
+
/** Destination route descriptor (replaces `href`). */
|
|
181
|
+
to: RouteDescriptor<Path, S, L>;
|
|
182
|
+
/** Path params — required when the route has dynamic segments. */
|
|
183
|
+
params?: RouteParams<Path>;
|
|
184
|
+
/** Search params to append. */
|
|
185
|
+
search?: S extends SchemaLike<infer T> ? Partial<T & Record<string, string>> : Record<string, string>;
|
|
186
|
+
children?: ReactNode;
|
|
187
|
+
className?: string;
|
|
188
|
+
style?: React.CSSProperties;
|
|
189
|
+
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* A type-safe `<Link>` bound to a route descriptor.
|
|
193
|
+
* Compile-time error if required params are missing or wrong type.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```tsx
|
|
197
|
+
* <RouteLink to={userRoute} params={{ id: "42" }}>
|
|
198
|
+
* View user
|
|
199
|
+
* </RouteLink>
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
export declare function RouteLink<Path extends string, S extends SchemaLike<unknown> | undefined, L>({ to, params, search, children, onClick, ...rest }: LinkToRoute<Path, S, L>): import("react/jsx-runtime").JSX.Element;
|
|
203
|
+
export {};
|
|
204
|
+
//# sourceMappingURL=code-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-router.d.ts","sourceRoot":"","sources":["../../src/router/code-router.tsx"],"names":[],"mappings":"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"}
|