tanstack-start-intl 0.0.0-alpha.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 +122 -0
- package/config.d.ts +15 -0
- package/dist/esm/development/config.js +5 -0
- package/dist/esm/development/index.js +113 -0
- package/dist/esm/development/routing.js +25 -0
- package/dist/esm/development/server.js +180 -0
- package/dist/esm/development/utils-UUs5JKte.js +136 -0
- package/dist/esm/production/config.js +1 -0
- package/dist/esm/production/index.js +1 -0
- package/dist/esm/production/routing.js +1 -0
- package/dist/esm/production/server.js +1 -0
- package/dist/esm/production/utils-Y3ZAPIbE.js +1 -0
- package/dist/types/src/NextIntlClientProvider.d.ts +9 -0
- package/dist/types/src/config.d.ts +1 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/navigation/createNavigation.d.ts +20 -0
- package/dist/types/src/routing/config.d.ts +50 -0
- package/dist/types/src/routing/defineRouting.d.ts +3 -0
- package/dist/types/src/routing/index.d.ts +3 -0
- package/dist/types/src/routing/types.d.ts +22 -0
- package/dist/types/src/routing.d.ts +3 -0
- package/dist/types/src/server/RequestLocale.d.ts +7 -0
- package/dist/types/src/server/RequestLocaleCache.d.ts +3 -0
- package/dist/types/src/server/createRequestConfig.d.ts +3 -0
- package/dist/types/src/server/getConfig.d.ts +13 -0
- package/dist/types/src/server/getConfigNow.d.ts +4 -0
- package/dist/types/src/server/getDefaultNow.d.ts +3 -0
- package/dist/types/src/server/getFormatter.d.ts +4 -0
- package/dist/types/src/server/getLocale.d.ts +4 -0
- package/dist/types/src/server/getMessages.d.ts +6 -0
- package/dist/types/src/server/getNow.d.ts +4 -0
- package/dist/types/src/server/getRequestConfig.d.ts +18 -0
- package/dist/types/src/server/getServerFormatter.d.ts +25 -0
- package/dist/types/src/server/getServerTranslator.d.ts +4 -0
- package/dist/types/src/server/getTimeZone.d.ts +4 -0
- package/dist/types/src/server/getTranslations.d.ts +8 -0
- package/dist/types/src/server/validateLocale.d.ts +1 -0
- package/dist/types/src/server.d.ts +9 -0
- package/dist/types/src/shared/utils.d.ts +21 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# tanstack-start-intl
|
|
2
|
+
|
|
3
|
+
Internationalization (i18n) for [TanStack Start](https://tanstack.com/start), built on [use-intl](https://github.com/amannn/next-intl/tree/main/packages/use-intl) and aligned with [next-intl](https://next-intl.dev) semantics.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### 1. Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add tanstack-start-intl use-intl
|
|
11
|
+
# peer: @tanstack/react-router, react
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### 2. Config alias
|
|
15
|
+
|
|
16
|
+
In your Vite or TanStack Start config, add an alias so the package can load your request config:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// e.g. app.config.ts or vite.config.ts
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
resolve: {
|
|
22
|
+
alias: {
|
|
23
|
+
'tanstack-start-intl/config': resolve(__dirname, 'src/i18n/request.ts')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 3. Request config
|
|
30
|
+
|
|
31
|
+
Create `src/i18n/request.ts` (or your chosen path):
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { getRequestConfig } from 'tanstack-start-intl/server';
|
|
35
|
+
import { hasLocale } from 'tanstack-start-intl';
|
|
36
|
+
import { routing } from './routing';
|
|
37
|
+
|
|
38
|
+
export default getRequestConfig(async ({ requestLocale }) => {
|
|
39
|
+
const requested = await requestLocale;
|
|
40
|
+
const locale = hasLocale(routing.locales, requested)
|
|
41
|
+
? requested
|
|
42
|
+
: routing.defaultLocale;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
locale,
|
|
46
|
+
messages: (await import(`../../messages/${locale}.json`)).default
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 4. Routing config
|
|
52
|
+
|
|
53
|
+
Create `src/i18n/routing.ts`:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { defineRouting } from 'tanstack-start-intl/routing';
|
|
57
|
+
|
|
58
|
+
export const routing = defineRouting({
|
|
59
|
+
locales: ['en', 'fr'],
|
|
60
|
+
defaultLocale: 'en',
|
|
61
|
+
pathnames: {
|
|
62
|
+
'/': '/',
|
|
63
|
+
'/about': { en: '/about', fr: '/a-propos' }
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 5. Locale layout
|
|
69
|
+
|
|
70
|
+
In your locale route (e.g. `src/routes/$locale.tsx`), set the request locale and wrap with the provider:
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
74
|
+
import { setRequestLocale, getRequestConfig } from 'tanstack-start-intl/server';
|
|
75
|
+
import { NextIntlClientProvider, hasLocale } from 'tanstack-start-intl';
|
|
76
|
+
import { routing } from '@/i18n/routing';
|
|
77
|
+
|
|
78
|
+
export const Route = createFileRoute('/$locale')({
|
|
79
|
+
component: LocaleLayout
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
async function LocaleLayout() {
|
|
83
|
+
const { locale } = Route.useParams();
|
|
84
|
+
if (!hasLocale(routing.locales, locale)) {
|
|
85
|
+
throw new Error('Invalid locale');
|
|
86
|
+
}
|
|
87
|
+
setRequestLocale(locale);
|
|
88
|
+
const config = await getRequestConfig({
|
|
89
|
+
requestLocale: Promise.resolve(locale)
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<NextIntlClientProvider locale={config.locale} messages={config.messages}>
|
|
94
|
+
<Outlet />
|
|
95
|
+
</NextIntlClientProvider>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 6. Navigation (optional)
|
|
101
|
+
|
|
102
|
+
If you use pathnames and locale prefix, create navigation helpers:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// src/i18n/navigation.ts
|
|
106
|
+
import { createNavigation } from 'tanstack-start-intl';
|
|
107
|
+
import { routing } from './routing';
|
|
108
|
+
|
|
109
|
+
export const { Link, redirect, usePathname, useRouter, getPathname } =
|
|
110
|
+
createNavigation(routing);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## API
|
|
114
|
+
|
|
115
|
+
- **From `tanstack-start-intl`**: `NextIntlClientProvider`, `createNavigation`, `hasLocale`, `useTranslations`, `useFormatter`, `useLocale`, `useMessages`, `useNow`, `useTimeZone`
|
|
116
|
+
- **From `tanstack-start-intl/server`**: `getRequestConfig`, `setRequestLocale`, `getTranslations`, `getLocale`, `getMessages`, `getFormatter`, `getTimeZone`, `getNow`
|
|
117
|
+
- **From `tanstack-start-intl/routing`**: `defineRouting`, routing types
|
|
118
|
+
|
|
119
|
+
## Notes
|
|
120
|
+
|
|
121
|
+
- Locale is **only** taken from `setRequestLocale(locale)` in your locale layout. There is no header-based detection; call `setRequestLocale` before any server API use.
|
|
122
|
+
- Configure the alias `tanstack-start-intl/config` to your request file so server APIs can load messages and locale.
|
package/config.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This module is resolved at build time via your Vite/Start alias to your
|
|
3
|
+
* i18n request file (e.g. src/i18n/request.ts). The default export should
|
|
4
|
+
* be the result of getRequestConfig().
|
|
5
|
+
*/
|
|
6
|
+
declare module 'tanstack-start-intl/config' {
|
|
7
|
+
import type {
|
|
8
|
+
GetRequestConfigParams,
|
|
9
|
+
RequestConfig
|
|
10
|
+
} from 'tanstack-start-intl/server';
|
|
11
|
+
const getRequestConfig: (
|
|
12
|
+
params: GetRequestConfigParams
|
|
13
|
+
) => RequestConfig | Promise<RequestConfig>;
|
|
14
|
+
export default getRequestConfig;
|
|
15
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { IntlProvider } from 'use-intl/react';
|
|
2
|
+
export { useFormatter, useLocale, useMessages, useNow, useTimeZone, useTranslations } from 'use-intl/react';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { useRouter, useLocation, Link, redirect } from '@tanstack/react-router';
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import { useLocale } from 'use-intl';
|
|
7
|
+
import { u as unprefixPathname, g as getRoute, a as applyPathnamePrefix, b as getLocalePrefix } from './utils-UUs5JKte.js';
|
|
8
|
+
export { hasLocale } from 'use-intl/core';
|
|
9
|
+
|
|
10
|
+
function NextIntlClientProvider({
|
|
11
|
+
locale,
|
|
12
|
+
...rest
|
|
13
|
+
}) {
|
|
14
|
+
if (!locale) {
|
|
15
|
+
throw new Error('Missing `locale` prop on NextIntlClientProvider. Pass the locale from your layout (e.g. from getRequestConfig or route params).' );
|
|
16
|
+
}
|
|
17
|
+
return /*#__PURE__*/jsx(IntlProvider, {
|
|
18
|
+
locale: locale,
|
|
19
|
+
...rest
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Cookie config for locale persistence (framework-agnostic). */
|
|
24
|
+
|
|
25
|
+
function receiveRoutingConfig(input) {
|
|
26
|
+
return {
|
|
27
|
+
...input,
|
|
28
|
+
localePrefix: receiveLocalePrefixConfig(input.localePrefix),
|
|
29
|
+
localeCookie: receiveLocaleCookie(input.localeCookie),
|
|
30
|
+
localeDetection: input.localeDetection ?? true,
|
|
31
|
+
alternateLinks: input.alternateLinks ?? true
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function receiveLocaleCookie(localeCookie) {
|
|
35
|
+
return localeCookie ?? true ? {
|
|
36
|
+
name: 'TANSTACK_LOCALE',
|
|
37
|
+
sameSite: 'lax',
|
|
38
|
+
...(typeof localeCookie === 'object' && localeCookie)
|
|
39
|
+
} : false;
|
|
40
|
+
}
|
|
41
|
+
function receiveLocalePrefixConfig(localePrefix) {
|
|
42
|
+
return typeof localePrefix === 'object' ? localePrefix : {
|
|
43
|
+
mode: localePrefix || 'always'
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createNavigation(routing) {
|
|
48
|
+
const config = receiveRoutingConfig(routing || {});
|
|
49
|
+
function usePathname() {
|
|
50
|
+
const location = useLocation();
|
|
51
|
+
const pathname = location.pathname || '/';
|
|
52
|
+
const locale = useLocale();
|
|
53
|
+
const prefix = getLocalePrefix(locale, config.localePrefix);
|
|
54
|
+
const unprefixed = prefix ? unprefixPathname(pathname, prefix) : pathname;
|
|
55
|
+
if (!config.pathnames) return unprefixed;
|
|
56
|
+
return getRoute(locale, unprefixed, config.pathnames);
|
|
57
|
+
}
|
|
58
|
+
function getPathname(pathnameOrPathnames, locale, opts) {
|
|
59
|
+
const pathname = typeof pathnameOrPathnames === 'string' ? pathnameOrPathnames : pathnameOrPathnames[locale] || pathnameOrPathnames[config.defaultLocale] || '/';
|
|
60
|
+
return applyPathnamePrefix(pathname, locale, config, opts?.force);
|
|
61
|
+
}
|
|
62
|
+
function Link$1({
|
|
63
|
+
href,
|
|
64
|
+
locale: localeProp,
|
|
65
|
+
...rest
|
|
66
|
+
}) {
|
|
67
|
+
const currentLocale = useLocale();
|
|
68
|
+
const locale = localeProp ?? currentLocale;
|
|
69
|
+
const to = useMemo(() => {
|
|
70
|
+
const pathname = typeof href === 'object' ? href.pathname : href;
|
|
71
|
+
const params = typeof href === 'object' ? href.params : undefined;
|
|
72
|
+
const pathnames = config.pathnames;
|
|
73
|
+
const pathnameConfig = pathnames ? pathnames[pathname] : pathname;
|
|
74
|
+
let resolvedPathname;
|
|
75
|
+
if (pathnameConfig === undefined || pathnameConfig === null) {
|
|
76
|
+
resolvedPathname = pathname;
|
|
77
|
+
} else if (typeof pathnameConfig === 'string') {
|
|
78
|
+
resolvedPathname = pathnameConfig;
|
|
79
|
+
} else {
|
|
80
|
+
const localizedPathnames = pathnameConfig;
|
|
81
|
+
resolvedPathname = localizedPathnames[locale] ?? localizedPathnames[config.defaultLocale] ?? pathname;
|
|
82
|
+
}
|
|
83
|
+
const localized = getPathname(resolvedPathname, locale);
|
|
84
|
+
if (params && Object.keys(params).length > 0) {
|
|
85
|
+
return {
|
|
86
|
+
pathname: localized,
|
|
87
|
+
search: params
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return localized;
|
|
91
|
+
}, [href, locale]);
|
|
92
|
+
return /*#__PURE__*/jsx(Link, {
|
|
93
|
+
to: to,
|
|
94
|
+
...rest
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function redirect$1(pathname, locale) {
|
|
98
|
+
const loc = locale ?? config.defaultLocale;
|
|
99
|
+
const target = getPathname(pathname, loc);
|
|
100
|
+
throw redirect({
|
|
101
|
+
to: target
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
Link: Link$1,
|
|
106
|
+
redirect: redirect$1,
|
|
107
|
+
usePathname,
|
|
108
|
+
useRouter,
|
|
109
|
+
getPathname
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { NextIntlClientProvider, createNavigation };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function defineRouting(config) {
|
|
2
|
+
if (config.domains) {
|
|
3
|
+
validateUniqueLocalesPerDomain(config.domains);
|
|
4
|
+
}
|
|
5
|
+
return config;
|
|
6
|
+
}
|
|
7
|
+
function validateUniqueLocalesPerDomain(domains) {
|
|
8
|
+
const domainsByLocale = new Map();
|
|
9
|
+
for (const {
|
|
10
|
+
domain,
|
|
11
|
+
locales
|
|
12
|
+
} of domains) {
|
|
13
|
+
for (const locale of locales) {
|
|
14
|
+
const localeDomains = domainsByLocale.get(locale) || new Set();
|
|
15
|
+
localeDomains.add(domain);
|
|
16
|
+
domainsByLocale.set(locale, localeDomains);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const duplicateLocaleMessages = Array.from(domainsByLocale.entries()).filter(([, localeDomains]) => localeDomains.size > 1).map(([locale, localeDomains]) => `- "${locale}" is used by: ${Array.from(localeDomains).join(', ')}`);
|
|
20
|
+
if (duplicateLocaleMessages.length > 0) {
|
|
21
|
+
console.warn('Locales are expected to be unique per domain, but found overlap:\n' + duplicateLocaleMessages.join('\n'));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { defineRouting };
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { cache } from 'react';
|
|
2
|
+
import { initializeConfig, _createIntlFormatters, _createCache, createTranslator, createFormatter } from 'use-intl/core';
|
|
3
|
+
import { i as isPromise } from './utils-UUs5JKte.js';
|
|
4
|
+
import getRuntimeConfig from 'tanstack-start-intl/config';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Use in your i18n request file (e.g. `src/i18n/request.ts`) to create
|
|
8
|
+
* the configuration for the current request. Configure your Vite/Start
|
|
9
|
+
* alias: `'tanstack-start-intl/config'` -> `'./src/i18n/request'`.
|
|
10
|
+
*/
|
|
11
|
+
function getRequestConfig(createRequestConfig) {
|
|
12
|
+
return createRequestConfig;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getCacheImpl() {
|
|
16
|
+
const value = {
|
|
17
|
+
locale: undefined
|
|
18
|
+
};
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
const getCache$1 = cache(getCacheImpl);
|
|
22
|
+
function getCachedRequestLocale() {
|
|
23
|
+
return getCache$1().locale;
|
|
24
|
+
}
|
|
25
|
+
function setCachedRequestLocale(locale) {
|
|
26
|
+
getCache$1().locale = locale;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns the request locale. In TanStack Start, locale must be set via
|
|
31
|
+
* `setRequestLocale(locale)` in your locale layout (e.g. in `$locale.tsx`)
|
|
32
|
+
* before any server APIs are used.
|
|
33
|
+
*/
|
|
34
|
+
async function getRequestLocale() {
|
|
35
|
+
return getCachedRequestLocale();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function validateLocale(locale) {
|
|
39
|
+
try {
|
|
40
|
+
const constructed = new Intl.Locale(locale);
|
|
41
|
+
if (!constructed.language) {
|
|
42
|
+
throw new Error('Language is required');
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
console.error(`An invalid locale was provided: "${locale}". Please use a valid Unicode locale identifier (e.g. "en-US").`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getDefaultTimeZoneImpl() {
|
|
50
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
51
|
+
}
|
|
52
|
+
const getDefaultTimeZone = cache(getDefaultTimeZoneImpl);
|
|
53
|
+
async function receiveRuntimeConfigImpl(getConfig, localeOverride) {
|
|
54
|
+
if (typeof getConfig !== 'function') {
|
|
55
|
+
throw new Error(`Invalid i18n request configuration.
|
|
56
|
+
|
|
57
|
+
Please:
|
|
58
|
+
1. Add a Vite/Start alias: 'tanstack-start-intl/config' -> './src/i18n/request' (or your request file path).
|
|
59
|
+
2. Ensure your i18n request file has a default export that calls getRequestConfig().
|
|
60
|
+
|
|
61
|
+
See: https://github.com/amannn/next-intl#tanstack-start`);
|
|
62
|
+
}
|
|
63
|
+
const params = {
|
|
64
|
+
locale: localeOverride,
|
|
65
|
+
get requestLocale() {
|
|
66
|
+
return localeOverride ? Promise.resolve(localeOverride) : getRequestLocale();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
let result = getConfig(params);
|
|
70
|
+
if (isPromise(result)) {
|
|
71
|
+
result = await result;
|
|
72
|
+
}
|
|
73
|
+
if (!result.locale) {
|
|
74
|
+
throw new Error('No locale was returned from `getRequestConfig`. Ensure your i18n request file returns { locale, messages, ... }.');
|
|
75
|
+
}
|
|
76
|
+
{
|
|
77
|
+
validateLocale(result.locale);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
const receiveRuntimeConfig = cache(receiveRuntimeConfigImpl);
|
|
82
|
+
const getFormatters = cache(_createIntlFormatters);
|
|
83
|
+
const getCache = cache(_createCache);
|
|
84
|
+
async function getConfigImpl(localeOverride) {
|
|
85
|
+
const runtimeConfig = await receiveRuntimeConfig(getRuntimeConfig, localeOverride);
|
|
86
|
+
return {
|
|
87
|
+
...initializeConfig(runtimeConfig),
|
|
88
|
+
_formatters: getFormatters(getCache()),
|
|
89
|
+
timeZone: runtimeConfig.timeZone || getDefaultTimeZone()
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const getConfig = cache(getConfigImpl);
|
|
93
|
+
|
|
94
|
+
function getServerTranslatorImpl(config, namespace) {
|
|
95
|
+
return createTranslator({
|
|
96
|
+
...config,
|
|
97
|
+
namespace
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
var getServerTranslator = cache(getServerTranslatorImpl);
|
|
101
|
+
|
|
102
|
+
async function getTranslations(namespaceOrOpts) {
|
|
103
|
+
let namespace;
|
|
104
|
+
let locale;
|
|
105
|
+
if (typeof namespaceOrOpts === 'string') {
|
|
106
|
+
namespace = namespaceOrOpts;
|
|
107
|
+
} else if (namespaceOrOpts) {
|
|
108
|
+
locale = namespaceOrOpts.locale;
|
|
109
|
+
namespace = namespaceOrOpts.namespace;
|
|
110
|
+
}
|
|
111
|
+
const config = await getConfig(locale);
|
|
112
|
+
return getServerTranslator(config, namespace);
|
|
113
|
+
}
|
|
114
|
+
var getTranslations_default = cache(getTranslations);
|
|
115
|
+
|
|
116
|
+
async function getLocaleCachedImpl() {
|
|
117
|
+
const config = await getConfig();
|
|
118
|
+
return config.locale;
|
|
119
|
+
}
|
|
120
|
+
const getLocaleCached = cache(getLocaleCachedImpl);
|
|
121
|
+
|
|
122
|
+
function getMessagesFromConfig(config) {
|
|
123
|
+
if (!config.messages) {
|
|
124
|
+
throw new Error('No messages found. Have you configured them in getRequestConfig?');
|
|
125
|
+
}
|
|
126
|
+
return config.messages;
|
|
127
|
+
}
|
|
128
|
+
async function getMessagesCachedImpl(locale) {
|
|
129
|
+
const config = await getConfig(locale);
|
|
130
|
+
return getMessagesFromConfig(config);
|
|
131
|
+
}
|
|
132
|
+
const getMessagesCached = cache(getMessagesCachedImpl);
|
|
133
|
+
async function getMessages(opts) {
|
|
134
|
+
return getMessagesCached(opts?.locale);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function defaultNow() {
|
|
138
|
+
return new Date();
|
|
139
|
+
}
|
|
140
|
+
const getDefaultNow = cache(defaultNow);
|
|
141
|
+
|
|
142
|
+
function getFormatterCachedImpl$1(config) {
|
|
143
|
+
return createFormatter({
|
|
144
|
+
...config,
|
|
145
|
+
get now() {
|
|
146
|
+
return config.now ?? getDefaultNow();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const getFormatterCached$1 = cache(getFormatterCachedImpl$1);
|
|
151
|
+
|
|
152
|
+
async function getFormatterCachedImpl(locale) {
|
|
153
|
+
const config = await getConfig(locale);
|
|
154
|
+
return getFormatterCached$1(config);
|
|
155
|
+
}
|
|
156
|
+
const getFormatterCached = cache(getFormatterCachedImpl);
|
|
157
|
+
async function getFormatter(opts) {
|
|
158
|
+
return getFormatterCached(opts?.locale);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function getTimeZoneCachedImpl(locale) {
|
|
162
|
+
const config = await getConfig(locale);
|
|
163
|
+
return config.timeZone;
|
|
164
|
+
}
|
|
165
|
+
const getTimeZoneCached = cache(getTimeZoneCachedImpl);
|
|
166
|
+
async function getTimeZone(opts) {
|
|
167
|
+
return getTimeZoneCached(opts?.locale);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function getConfigNowImpl(locale) {
|
|
171
|
+
const config = await getConfig(locale);
|
|
172
|
+
return config.now;
|
|
173
|
+
}
|
|
174
|
+
const getConfigNow = cache(getConfigNowImpl);
|
|
175
|
+
|
|
176
|
+
async function getNow(opts) {
|
|
177
|
+
return (await getConfigNow(opts?.locale)) ?? getDefaultNow();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export { getFormatter, getLocaleCached as getLocale, getMessages, getMessagesFromConfig, getNow, getRequestConfig, getTimeZone, getTranslations_default as getTranslations, setCachedRequestLocale as setRequestLocale };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
function isRelativeHref(href) {
|
|
2
|
+
const pathname = typeof href === 'object' ? href.pathname : href;
|
|
3
|
+
return typeof pathname === 'string' && !pathname.startsWith('/');
|
|
4
|
+
}
|
|
5
|
+
function isLocalHref(href) {
|
|
6
|
+
if (typeof href === 'object') {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
const hasProtocol = /^[a-z]+:/i.test(href);
|
|
10
|
+
return !hasProtocol;
|
|
11
|
+
}
|
|
12
|
+
function isLocalizableHref(href) {
|
|
13
|
+
return isLocalHref(href) && !isRelativeHref(href);
|
|
14
|
+
}
|
|
15
|
+
function unprefixPathname(pathname, prefix) {
|
|
16
|
+
return pathname.replace(new RegExp(`^${prefix}`), '') || '/';
|
|
17
|
+
}
|
|
18
|
+
function prefixPathname(prefix, pathname) {
|
|
19
|
+
let localizedHref = prefix;
|
|
20
|
+
if (/^\/(\?.*)?$/.test(pathname)) {
|
|
21
|
+
pathname = pathname.slice(1);
|
|
22
|
+
}
|
|
23
|
+
localizedHref += pathname;
|
|
24
|
+
return localizedHref;
|
|
25
|
+
}
|
|
26
|
+
function getLocalizedTemplate(pathnameConfig, locale, internalTemplate) {
|
|
27
|
+
return typeof pathnameConfig === 'string' ? pathnameConfig : pathnameConfig[locale] || internalTemplate;
|
|
28
|
+
}
|
|
29
|
+
function normalizeTrailingSlash(pathname, trailingSlash = false) {
|
|
30
|
+
const [path, ...hashParts] = pathname.split('#');
|
|
31
|
+
const hash = hashParts.join('#');
|
|
32
|
+
let normalizedPath = path;
|
|
33
|
+
if (normalizedPath !== '/') {
|
|
34
|
+
const pathnameEndsWithSlash = normalizedPath.endsWith('/');
|
|
35
|
+
if (trailingSlash && !pathnameEndsWithSlash) {
|
|
36
|
+
normalizedPath += '/';
|
|
37
|
+
} else if (!trailingSlash && pathnameEndsWithSlash) {
|
|
38
|
+
normalizedPath = normalizedPath.slice(0, -1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (hash) {
|
|
42
|
+
normalizedPath += '#' + hash;
|
|
43
|
+
}
|
|
44
|
+
return normalizedPath;
|
|
45
|
+
}
|
|
46
|
+
function matchesPathname(template, pathname) {
|
|
47
|
+
const normalizedTemplate = normalizeTrailingSlash(template);
|
|
48
|
+
const normalizedPathname = normalizeTrailingSlash(pathname);
|
|
49
|
+
const regex = templateToRegex(normalizedTemplate);
|
|
50
|
+
return regex.test(normalizedPathname);
|
|
51
|
+
}
|
|
52
|
+
function getLocalePrefix(locale, localePrefix) {
|
|
53
|
+
return localePrefix.mode !== 'never' && localePrefix.prefixes?.[locale] || getLocaleAsPrefix(locale);
|
|
54
|
+
}
|
|
55
|
+
function getLocaleAsPrefix(locale) {
|
|
56
|
+
return '/' + locale;
|
|
57
|
+
}
|
|
58
|
+
function templateToRegex(template) {
|
|
59
|
+
const regexPattern = template.replace(/\/\[\[(\.\.\.[^\]]+)\]\]/g, '(?:/(.*))?').replace(/\[\[(\.\.\.[^\]]+)\]\]/g, '(?:/(.*))?').replace(/\[(\.\.\.[^\]]+)\]/g, '(.+)').replace(/\[([^\]]+)\]/g, '([^/]+)');
|
|
60
|
+
return new RegExp(`^${regexPattern}$`);
|
|
61
|
+
}
|
|
62
|
+
function isOptionalCatchAllSegment(pathname) {
|
|
63
|
+
return pathname.includes('[[...');
|
|
64
|
+
}
|
|
65
|
+
function isCatchAllSegment(pathname) {
|
|
66
|
+
return pathname.includes('[...');
|
|
67
|
+
}
|
|
68
|
+
function isDynamicSegment(pathname) {
|
|
69
|
+
return pathname.includes('[');
|
|
70
|
+
}
|
|
71
|
+
function comparePathnamePairs(a, b) {
|
|
72
|
+
const pathA = a.split('/');
|
|
73
|
+
const pathB = b.split('/');
|
|
74
|
+
const maxLength = Math.max(pathA.length, pathB.length);
|
|
75
|
+
for (let i = 0; i < maxLength; i++) {
|
|
76
|
+
const segmentA = pathA[i];
|
|
77
|
+
const segmentB = pathB[i];
|
|
78
|
+
if (!segmentA && segmentB) return -1;
|
|
79
|
+
if (segmentA && !segmentB) return 1;
|
|
80
|
+
if (!segmentA && !segmentB) continue;
|
|
81
|
+
if (!isDynamicSegment(segmentA) && isDynamicSegment(segmentB)) return -1;
|
|
82
|
+
if (isDynamicSegment(segmentA) && !isDynamicSegment(segmentB)) return 1;
|
|
83
|
+
if (!isCatchAllSegment(segmentA) && isCatchAllSegment(segmentB)) return -1;
|
|
84
|
+
if (isCatchAllSegment(segmentA) && !isCatchAllSegment(segmentB)) return 1;
|
|
85
|
+
if (!isOptionalCatchAllSegment(segmentA) && isOptionalCatchAllSegment(segmentB)) {
|
|
86
|
+
return -1;
|
|
87
|
+
}
|
|
88
|
+
if (isOptionalCatchAllSegment(segmentA) && !isOptionalCatchAllSegment(segmentB)) {
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
if (segmentA === segmentB) continue;
|
|
92
|
+
}
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
function getSortedPathnames(pathnames) {
|
|
96
|
+
return pathnames.sort(comparePathnamePairs);
|
|
97
|
+
}
|
|
98
|
+
function isPromise(value) {
|
|
99
|
+
return typeof value.then === 'function';
|
|
100
|
+
}
|
|
101
|
+
function getRoute(locale, pathname, pathnames) {
|
|
102
|
+
const sortedPathnames = getSortedPathnames(Object.keys(pathnames));
|
|
103
|
+
const decoded = decodeURI(pathname);
|
|
104
|
+
for (const internalPathname of sortedPathnames) {
|
|
105
|
+
const localizedPathnamesOrPathname = pathnames[internalPathname];
|
|
106
|
+
if (typeof localizedPathnamesOrPathname === 'string') {
|
|
107
|
+
const localizedPathname = localizedPathnamesOrPathname;
|
|
108
|
+
if (matchesPathname(localizedPathname, decoded)) {
|
|
109
|
+
return internalPathname;
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
if (matchesPathname(getLocalizedTemplate(localizedPathnamesOrPathname, locale, internalPathname), decoded)) {
|
|
113
|
+
return internalPathname;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return pathname;
|
|
118
|
+
}
|
|
119
|
+
function applyPathnamePrefix(pathname, locale, routing, force) {
|
|
120
|
+
const {
|
|
121
|
+
mode
|
|
122
|
+
} = routing.localePrefix;
|
|
123
|
+
let shouldPrefix;
|
|
124
|
+
if (force !== undefined) {
|
|
125
|
+
shouldPrefix = force;
|
|
126
|
+
} else if (isLocalizableHref(pathname)) {
|
|
127
|
+
if (mode === 'always') {
|
|
128
|
+
shouldPrefix = true;
|
|
129
|
+
} else if (mode === 'as-needed') {
|
|
130
|
+
shouldPrefix = routing.domains ? !routing.domains.some(cur => cur.defaultLocale === locale) : locale !== routing.defaultLocale;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return shouldPrefix ? prefixPathname(getLocalePrefix(locale, routing.localePrefix), pathname) : pathname;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { applyPathnamePrefix as a, getLocalePrefix as b, getRoute as g, isPromise as i, unprefixPathname as u };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function t(){throw new Error("Could not find tanstack-start-intl config. Add a Vite/Start alias: 'tanstack-start-intl/config' -> './src/i18n/request' (or your request file).")}export{t as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{IntlProvider as e}from"use-intl/react";export{useFormatter,useLocale,useMessages,useNow,useTimeZone,useTranslations}from"use-intl/react";import{jsx as t}from"react/jsx-runtime";import{useRouter as o,useLocation as a,Link as r,redirect as n}from"@tanstack/react-router";import{useMemo as s}from"react";import{useLocale as c}from"use-intl";import{u as l,g as i,a as u,b as f}from"./utils-Y3ZAPIbE.js";export{hasLocale}from"use-intl/core";function m({locale:o,...a}){if(!o)throw new Error(void 0);return t(e,{locale:o,...a})}function p(e){const m=(p=e||{},{...p,localePrefix:(L=p.localePrefix,"object"==typeof L?L:{mode:L||"always"}),localeCookie:(h=p.localeCookie,!!(h??1)&&{name:"TANSTACK_LOCALE",sameSite:"lax",..."object"==typeof h&&h}),localeDetection:p.localeDetection??!0,alternateLinks:p.alternateLinks??!0});var p,h,L;function x(e,t,o){const a="string"==typeof e?e:e[t]||e[m.defaultLocale]||"/";return u(a,t,m,o?.force)}return{Link:function({href:e,locale:o,...a}){const n=c(),l=o??n,i=s((()=>{const t="object"==typeof e?e.pathname:e,o="object"==typeof e?e.params:void 0,a=m.pathnames,r=a?a[t]:t;let n;if(null==r)n=t;else if("string"==typeof r)n=r;else{const e=r;n=e[l]??e[m.defaultLocale]??t}const s=x(n,l);return o&&Object.keys(o).length>0?{pathname:s,search:o}:s}),[e,l]);return t(r,{to:i,...a})},redirect:function(e,t){const o=x(e,t??m.defaultLocale);throw n({to:o})},usePathname:function(){const e=a().pathname||"/",t=c(),o=f(t,m.localePrefix),r=o?l(e,o):e;return m.pathnames?i(t,r,m.pathnames):r},useRouter:o,getPathname:x}}export{m as NextIntlClientProvider,p as createNavigation};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function n(n){return n}export{n as defineRouting};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cache as n}from"react";import{initializeConfig as t,_createIntlFormatters as e,_createCache as o,createTranslator as r,createFormatter as a}from"use-intl/core";import{i as c}from"./utils-Y3ZAPIbE.js";import s from"tanstack-start-intl/config";function i(n){return n}const u=n((function(){return{locale:void 0}}));function l(n){u().locale=n}async function f(){return u().locale}const m=n((function(){return Intl.DateTimeFormat().resolvedOptions().timeZone}));const w=n((async function(n,t){let e=n({locale:t,get requestLocale(){return t?Promise.resolve(t):f()}});if(c(e)&&(e=await e),!e.locale)throw new Error("No locale was returned from `getRequestConfig`. Ensure your i18n request file returns { locale, messages, ... }.");return e})),y=n(e),g=n(o);const p=n((async function(n){const e=await w(s,n);return{...t(e),_formatters:y(g()),timeZone:e.timeZone||m()}}));var v=n((function(n,t){return r({...n,namespace:t})}));var d=n((async function(n){let t,e;"string"==typeof n?t=n:n&&(e=n.locale,t=n.namespace);const o=await p(e);return v(o,t)}));const q=n((async function(){return(await p()).locale}));function Z(n){if(!n.messages)throw new Error("No messages found. Have you configured them in getRequestConfig?");return n.messages}const h=n((async function(n){return Z(await p(n))}));async function E(n){return h(n?.locale)}const L=n((function(){return new Date}));const R=n((function(n){return a({...n,get now(){return n.now??L()}})}));const C=n((async function(n){const t=await p(n);return R(t)}));async function D(n){return C(n?.locale)}const N=n((async function(n){return(await p(n)).timeZone}));async function T(n){return N(n?.locale)}const j=n((async function(n){return(await p(n)).now}));async function k(n){return await j(n?.locale)??L()}export{D as getFormatter,q as getLocale,E as getMessages,Z as getMessagesFromConfig,k as getNow,i as getRequestConfig,T as getTimeZone,d as getTranslations,l as setRequestLocale};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function t(t,n){return t.replace(new RegExp(`^${n}`),"")||"/"}function n(t,n,e){return"string"==typeof t?t:t[n]||e}function e(t,n=!1){const[e,...r]=t.split("#"),o=r.join("#");let i=e;if("/"!==i){const t=i.endsWith("/");n&&!t?i+="/":!n&&t&&(i=i.slice(0,-1))}return o&&(i+="#"+o),i}function r(t,n){const r=e(t),o=e(n),i=function(t){const n=t.replace(/\/\[\[(\.\.\.[^\]]+)\]\]/g,"(?:/(.*))?").replace(/\[\[(\.\.\.[^\]]+)\]\]/g,"(?:/(.*))?").replace(/\[(\.\.\.[^\]]+)\]/g,"(.+)").replace(/\[([^\]]+)\]/g,"([^/]+)");return new RegExp(`^${n}$`)}(r);return i.test(o)}function o(t,n){return"never"!==n.mode&&n.prefixes?.[t]||function(t){return"/"+t}(t)}function i(t){return t.includes("[[...")}function u(t){return t.includes("[...")}function c(t){return t.includes("[")}function f(t,n){const e=t.split("/"),r=n.split("/"),o=Math.max(e.length,r.length);for(let t=0;t<o;t++){const n=e[t],o=r[t];if(!n&&o)return-1;if(n&&!o)return 1;if(n||o){if(!c(n)&&c(o))return-1;if(c(n)&&!c(o))return 1;if(!u(n)&&u(o))return-1;if(u(n)&&!u(o))return 1;if(!i(n)&&i(o))return-1;if(i(n)&&!i(o))return 1}}return 0}function s(t){return"function"==typeof t.then}function a(t,e,o){const i=function(t){return t.sort(f)}(Object.keys(o)),u=decodeURI(e);for(const e of i){const i=o[e];if("string"==typeof i){if(r(i,u))return e}else if(r(n(i,t,e),u))return e}return e}function l(t,n,e,r){const{mode:i}=e.localePrefix;let u;var c;return void 0!==r?u=r:function(t){return"object"==typeof t||!/^[a-z]+:/i.test(t)}(c=t)&&!function(t){const n="object"==typeof t?t.pathname:t;return"string"==typeof n&&!n.startsWith("/")}(c)&&("always"===i?u=!0:"as-needed"===i&&(u=e.domains?!e.domains.some((t=>t.defaultLocale===n)):n!==e.defaultLocale)),u?function(t,n){let e=t;return/^\/(\?.*)?$/.test(n)&&(n=n.slice(1)),e+=n,e}(o(n,e.localePrefix),t):t}export{l as a,o as b,a as g,s as i,t as u};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
import type { Locale } from 'use-intl';
|
|
3
|
+
import { IntlProvider } from 'use-intl/react';
|
|
4
|
+
type Props = Omit<ComponentProps<typeof IntlProvider>, 'locale'> & {
|
|
5
|
+
/** Pass explicitly when rendering in layout; required when not from a Server Component that set request locale. */
|
|
6
|
+
locale?: Locale;
|
|
7
|
+
};
|
|
8
|
+
export default function NextIntlClientProvider({ locale, ...rest }: Props): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function getConfig(): void;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as NextIntlClientProvider } from './NextIntlClientProvider.js';
|
|
2
|
+
export { default as createNavigation } from './navigation/createNavigation.js';
|
|
3
|
+
export { hasLocale } from 'use-intl/core';
|
|
4
|
+
export { useTranslations, useFormatter, useLocale, useMessages, useNow, useTimeZone } from 'use-intl/react';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Link as RouterLink, useRouter } from '@tanstack/react-router';
|
|
2
|
+
import type { ComponentProps } from 'react';
|
|
3
|
+
import type { Locale } from 'use-intl';
|
|
4
|
+
import type { RoutingConfigLocalizedNavigation, RoutingConfigSharedNavigation } from '../routing/config.js';
|
|
5
|
+
import type { DomainsConfig, LocalePrefixMode, Locales, Pathnames } from '../routing/types.js';
|
|
6
|
+
export default function createNavigation<const AppLocales extends Locales, const AppLocalePrefixMode extends LocalePrefixMode = 'always', const AppPathnames extends Pathnames<AppLocales> = never, const AppDomains extends DomainsConfig<AppLocales> = never>(routing?: [AppPathnames] extends [never] ? RoutingConfigSharedNavigation<AppLocales, AppLocalePrefixMode, AppDomains> | undefined : RoutingConfigLocalizedNavigation<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>): {
|
|
7
|
+
Link: <Pathname extends keyof AppPathnames = never>({ href, locale: localeProp, ...rest }: Omit<ComponentProps<typeof RouterLink>, "to"> & {
|
|
8
|
+
href: [AppPathnames] extends [never] ? string : Pathname | {
|
|
9
|
+
pathname: Pathname;
|
|
10
|
+
params?: Record<string, string>;
|
|
11
|
+
};
|
|
12
|
+
locale?: Locale;
|
|
13
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
redirect: (pathname: string, locale?: Locale) => void;
|
|
15
|
+
usePathname: () => [AppPathnames] extends [never] ? string : keyof AppPathnames;
|
|
16
|
+
useRouter: typeof useRouter;
|
|
17
|
+
getPathname: (pathnameOrPathnames: string | Record<string, string>, locale: Locale, opts?: {
|
|
18
|
+
force?: boolean;
|
|
19
|
+
}) => string;
|
|
20
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { DomainsConfig, LocalePrefix, LocalePrefixConfigVerbose, LocalePrefixMode, Locales, Pathnames } from './types.js';
|
|
2
|
+
/** Cookie config for locale persistence (framework-agnostic). */
|
|
3
|
+
export type LocaleCookieAttributes = {
|
|
4
|
+
maxAge?: number;
|
|
5
|
+
domain?: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
8
|
+
secure?: boolean;
|
|
9
|
+
name?: string;
|
|
10
|
+
};
|
|
11
|
+
export type RoutingConfig<AppLocales extends Locales, AppLocalePrefixMode extends LocalePrefixMode, AppPathnames extends Pathnames<AppLocales> | undefined, AppDomains extends DomainsConfig<AppLocales> | undefined> = {
|
|
12
|
+
locales: AppLocales;
|
|
13
|
+
defaultLocale: AppLocales[number];
|
|
14
|
+
localePrefix?: LocalePrefix<AppLocales, AppLocalePrefixMode>;
|
|
15
|
+
domains?: AppDomains;
|
|
16
|
+
localeCookie?: boolean | LocaleCookieAttributes;
|
|
17
|
+
alternateLinks?: boolean;
|
|
18
|
+
localeDetection?: boolean;
|
|
19
|
+
} & ([AppPathnames] extends [never] ? {} : {
|
|
20
|
+
pathnames: AppPathnames;
|
|
21
|
+
});
|
|
22
|
+
export type RoutingConfigSharedNavigation<AppLocales extends Locales, AppLocalePrefixMode extends LocalePrefixMode, AppDomains extends DomainsConfig<AppLocales> = never> = Omit<RoutingConfig<AppLocales, AppLocalePrefixMode, never, AppDomains>, 'defaultLocale' | 'locales' | 'pathnames'> & Partial<Pick<RoutingConfig<AppLocales, never, never, AppDomains>, 'defaultLocale' | 'locales'>>;
|
|
23
|
+
export type RoutingConfigLocalizedNavigation<AppLocales extends Locales, AppLocalePrefixMode extends LocalePrefixMode, AppPathnames extends Pathnames<AppLocales>, AppDomains extends DomainsConfig<AppLocales> = never> = Omit<RoutingConfig<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>, 'defaultLocale' | 'pathnames'> & Partial<Pick<RoutingConfig<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>, 'defaultLocale'>> & {
|
|
24
|
+
pathnames: AppPathnames;
|
|
25
|
+
};
|
|
26
|
+
export type InitializedLocaleCookieConfig = false | {
|
|
27
|
+
name: string;
|
|
28
|
+
sameSite: 'lax' | 'strict' | 'none';
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
};
|
|
31
|
+
export type ResolvedRoutingConfig<AppLocales extends Locales, AppLocalePrefixMode extends LocalePrefixMode, AppPathnames extends Pathnames<AppLocales> | undefined, AppDomains extends DomainsConfig<AppLocales> | undefined> = Omit<RoutingConfig<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>, 'localePrefix' | 'localeCookie' | 'alternateLinks' | 'localeDetection'> & {
|
|
32
|
+
localePrefix: LocalePrefixConfigVerbose<AppLocales, AppLocalePrefixMode>;
|
|
33
|
+
localeCookie: InitializedLocaleCookieConfig;
|
|
34
|
+
alternateLinks: boolean;
|
|
35
|
+
localeDetection: boolean;
|
|
36
|
+
};
|
|
37
|
+
export declare function receiveRoutingConfig<AppLocales extends Locales, AppLocalePrefixMode extends LocalePrefixMode, AppPathnames extends Pathnames<AppLocales> | undefined, AppDomains extends DomainsConfig<AppLocales> | undefined, Config extends Partial<RoutingConfig<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>>>(input: Config): Omit<Config, "localePrefix" | "localeCookie" | "alternateLinks" | "localeDetection"> & {
|
|
38
|
+
localePrefix: {
|
|
39
|
+
mode: "never";
|
|
40
|
+
} | {
|
|
41
|
+
mode: "always";
|
|
42
|
+
prefixes?: Partial<Record<AppLocales[number], string>> | undefined;
|
|
43
|
+
} | {
|
|
44
|
+
mode: "as-needed";
|
|
45
|
+
prefixes?: Partial<Record<AppLocales[number], string>> | undefined;
|
|
46
|
+
};
|
|
47
|
+
localeCookie: InitializedLocaleCookieConfig;
|
|
48
|
+
localeDetection: boolean | NonNullable<RoutingConfig<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>["localeDetection"]>;
|
|
49
|
+
alternateLinks: boolean | NonNullable<RoutingConfig<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>["alternateLinks"]>;
|
|
50
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { RoutingConfig } from './config.js';
|
|
2
|
+
import type { DomainsConfig, LocalePrefixMode, Locales, Pathnames } from './types.js';
|
|
3
|
+
export default function defineRouting<const AppLocales extends Locales, const AppLocalePrefixMode extends LocalePrefixMode = 'always', const AppPathnames extends Pathnames<AppLocales> = never, const AppDomains extends DomainsConfig<AppLocales> = never>(config: RoutingConfig<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>): RoutingConfig<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type Locales = ReadonlyArray<string>;
|
|
2
|
+
export type LocalePrefixMode = 'always' | 'as-needed' | 'never';
|
|
3
|
+
type Pathname = string;
|
|
4
|
+
export type LocalePrefixes<AppLocales extends Locales> = Partial<Record<AppLocales[number], Pathname>>;
|
|
5
|
+
export type LocalePrefixConfigVerbose<AppLocales extends Locales, AppLocalePrefixMode extends LocalePrefixMode> = AppLocalePrefixMode extends 'always' ? {
|
|
6
|
+
mode: 'always';
|
|
7
|
+
prefixes?: LocalePrefixes<AppLocales>;
|
|
8
|
+
} : AppLocalePrefixMode extends 'as-needed' ? {
|
|
9
|
+
mode: 'as-needed';
|
|
10
|
+
prefixes?: LocalePrefixes<AppLocales>;
|
|
11
|
+
} : {
|
|
12
|
+
mode: 'never';
|
|
13
|
+
};
|
|
14
|
+
export type LocalePrefix<AppLocales extends Locales = [], AppLocalePrefixMode extends LocalePrefixMode = 'always'> = AppLocalePrefixMode | LocalePrefixConfigVerbose<AppLocales, AppLocalePrefixMode>;
|
|
15
|
+
export type Pathnames<AppLocales extends Locales> = Record<Pathname, Partial<Record<AppLocales[number], Pathname>> | Pathname>;
|
|
16
|
+
export type DomainConfig<AppLocales extends Locales> = {
|
|
17
|
+
defaultLocale: AppLocales[number];
|
|
18
|
+
domain: string;
|
|
19
|
+
locales: Array<AppLocales[number]>;
|
|
20
|
+
};
|
|
21
|
+
export type DomainsConfig<AppLocales extends Locales> = Array<DomainConfig<AppLocales>>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Locale } from 'use-intl';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the request locale. In TanStack Start, locale must be set via
|
|
4
|
+
* `setRequestLocale(locale)` in your locale layout (e.g. in `$locale.tsx`)
|
|
5
|
+
* before any server APIs are used.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getRequestLocale(): Promise<Locale | undefined>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type IntlConfig, type Locale, _createIntlFormatters } from 'use-intl/core';
|
|
2
|
+
declare function getConfigImpl(localeOverride?: Locale): Promise<{
|
|
3
|
+
locale: IntlConfig['locale'];
|
|
4
|
+
formats?: NonNullable<IntlConfig['formats']>;
|
|
5
|
+
timeZone: NonNullable<IntlConfig['timeZone']>;
|
|
6
|
+
onError: NonNullable<IntlConfig['onError']>;
|
|
7
|
+
getMessageFallback: NonNullable<IntlConfig['getMessageFallback']>;
|
|
8
|
+
messages?: NonNullable<IntlConfig['messages']>;
|
|
9
|
+
now?: NonNullable<IntlConfig['now']>;
|
|
10
|
+
_formatters: ReturnType<typeof _createIntlFormatters>;
|
|
11
|
+
}>;
|
|
12
|
+
declare const getConfig: typeof getConfigImpl;
|
|
13
|
+
export default getConfig;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Locale, useMessages as useMessagesType } from 'use-intl';
|
|
2
|
+
import getConfig from './getConfig.js';
|
|
3
|
+
export declare function getMessagesFromConfig(config: Awaited<ReturnType<typeof getConfig>>): ReturnType<typeof useMessagesType>;
|
|
4
|
+
export default function getMessages(opts?: {
|
|
5
|
+
locale?: Locale;
|
|
6
|
+
}): Promise<ReturnType<typeof useMessagesType>>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IntlConfig, Locale } from 'use-intl/core';
|
|
2
|
+
export type RequestConfig = Omit<IntlConfig, 'locale'> & {
|
|
3
|
+
locale: IntlConfig['locale'];
|
|
4
|
+
};
|
|
5
|
+
export type GetRequestConfigParams = {
|
|
6
|
+
locale?: Locale;
|
|
7
|
+
/**
|
|
8
|
+
* The locale from the current route (e.g. `$locale` param).
|
|
9
|
+
* Set via `setRequestLocale(locale)` in your locale layout.
|
|
10
|
+
*/
|
|
11
|
+
requestLocale: Promise<string | undefined>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Use in your i18n request file (e.g. `src/i18n/request.ts`) to create
|
|
15
|
+
* the configuration for the current request. Configure your Vite/Start
|
|
16
|
+
* alias: `'tanstack-start-intl/config'` -> `'./src/i18n/request'`.
|
|
17
|
+
*/
|
|
18
|
+
export default function getRequestConfig(createRequestConfig: (params: GetRequestConfigParams) => RequestConfig | Promise<RequestConfig>): (params: GetRequestConfigParams) => RequestConfig | Promise<RequestConfig>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createFormatter } from 'use-intl/core';
|
|
2
|
+
declare function getFormatterCachedImpl(config: Parameters<typeof createFormatter>[0]): {
|
|
3
|
+
dateTime: {
|
|
4
|
+
(value: Date | number, options?: import("use-intl/core").DateTimeFormatOptions): string;
|
|
5
|
+
(value: Date | number, format?: import("node_modules/use-intl/dist/types/core/AppConfig.js").FormatNames["dateTime"], options?: import("use-intl/core").DateTimeFormatOptions): string;
|
|
6
|
+
};
|
|
7
|
+
number: {
|
|
8
|
+
(value: number | bigint, options?: import("use-intl/core").NumberFormatOptions): string;
|
|
9
|
+
(value: number | bigint, format?: import("node_modules/use-intl/dist/types/core/AppConfig.js").FormatNames["number"], options?: import("use-intl/core").NumberFormatOptions): string;
|
|
10
|
+
};
|
|
11
|
+
relativeTime: {
|
|
12
|
+
(date: number | Date, now?: import("use-intl/core").RelativeTimeFormatOptions["now"]): string;
|
|
13
|
+
(date: number | Date, options?: import("use-intl/core").RelativeTimeFormatOptions): string;
|
|
14
|
+
};
|
|
15
|
+
list: {
|
|
16
|
+
<Value extends string | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>>(value: Iterable<Value>, options?: Intl.ListFormatOptions): Value extends string ? string : Iterable<import("react").ReactElement>;
|
|
17
|
+
<Value extends string | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>>(value: Iterable<Value>, format?: import("node_modules/use-intl/dist/types/core/AppConfig.js").FormatNames["list"], options?: Intl.ListFormatOptions): Value extends string ? string : Iterable<import("react").ReactElement>;
|
|
18
|
+
};
|
|
19
|
+
dateTimeRange: {
|
|
20
|
+
(start: Date | number, end: Date | number, options?: import("use-intl/core").DateTimeFormatOptions): string;
|
|
21
|
+
(start: Date | number, end: Date | number, format?: import("node_modules/use-intl/dist/types/core/AppConfig.js").FormatNames["dateTime"], options?: import("use-intl/core").DateTimeFormatOptions): string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
declare const getFormatterCached: typeof getFormatterCachedImpl;
|
|
25
|
+
export default getFormatterCached;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Messages, type NamespaceKeys, type NestedKeyOf, createTranslator } from 'use-intl/core';
|
|
2
|
+
declare function getServerTranslatorImpl<NestedKey extends NamespaceKeys<Messages, NestedKeyOf<Messages>> = never>(config: Parameters<typeof createTranslator>[0], namespace?: NestedKey): ReturnType<typeof createTranslator<Messages, NestedKey>>;
|
|
3
|
+
declare const _default: typeof getServerTranslatorImpl;
|
|
4
|
+
export default _default;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Locale, Messages, NamespaceKeys, NestedKeyOf, createTranslator } from 'use-intl/core';
|
|
2
|
+
declare function getTranslations<NestedKey extends NamespaceKeys<Messages, NestedKeyOf<Messages>> = never>(namespace?: NestedKey): Promise<ReturnType<typeof createTranslator<Messages, NestedKey>>>;
|
|
3
|
+
declare function getTranslations<NestedKey extends NamespaceKeys<Messages, NestedKeyOf<Messages>> = never>(opts?: {
|
|
4
|
+
locale: Locale;
|
|
5
|
+
namespace?: NestedKey;
|
|
6
|
+
}): Promise<ReturnType<typeof createTranslator<Messages, NestedKey>>>;
|
|
7
|
+
declare const _default: typeof getTranslations;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function validateLocale(locale: string): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as getRequestConfig, type GetRequestConfigParams, type RequestConfig } from './server/getRequestConfig.js';
|
|
2
|
+
export { setCachedRequestLocale as setRequestLocale } from './server/RequestLocaleCache.js';
|
|
3
|
+
export { default as getTranslations } from './server/getTranslations.js';
|
|
4
|
+
export { default as getLocale } from './server/getLocale.js';
|
|
5
|
+
export { default as getMessages } from './server/getMessages.js';
|
|
6
|
+
export { getMessagesFromConfig } from './server/getMessages.js';
|
|
7
|
+
export { default as getFormatter } from './server/getFormatter.js';
|
|
8
|
+
export { default as getTimeZone } from './server/getTimeZone.js';
|
|
9
|
+
export { default as getNow } from './server/getNow.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ResolvedRoutingConfig } from '../routing/config.js';
|
|
2
|
+
import type { DomainsConfig, LocalePrefixConfigVerbose, LocalePrefixMode, Locales, Pathnames } from '../routing/types.js';
|
|
3
|
+
export type Href = string | {
|
|
4
|
+
pathname: string;
|
|
5
|
+
query?: Record<string, string>;
|
|
6
|
+
};
|
|
7
|
+
export declare function isLocalizableHref(href: Href): boolean;
|
|
8
|
+
export declare function unprefixPathname(pathname: string, prefix: string): string;
|
|
9
|
+
export declare function prefixPathname(prefix: string, pathname: string): string;
|
|
10
|
+
export declare function hasPathnamePrefixed(prefix: string | undefined, pathname: string): boolean;
|
|
11
|
+
export declare function getLocalizedTemplate<AppLocales extends Locales>(pathnameConfig: Pathnames<AppLocales>[keyof Pathnames<AppLocales>], locale: AppLocales[number], internalTemplate: string): string;
|
|
12
|
+
export declare function normalizeTrailingSlash(pathname: string, trailingSlash?: boolean): string;
|
|
13
|
+
export declare function matchesPathname(template: string, pathname: string): boolean;
|
|
14
|
+
export declare function getLocalePrefix<AppLocales extends Locales, AppLocalePrefixMode extends LocalePrefixMode>(locale: AppLocales[number], localePrefix: LocalePrefixConfigVerbose<AppLocales, AppLocalePrefixMode>): string;
|
|
15
|
+
export declare function getLocaleAsPrefix(locale: string): string;
|
|
16
|
+
export declare function templateToRegex(template: string): RegExp;
|
|
17
|
+
export declare function getSortedPathnames(pathnames: Array<string>): string[];
|
|
18
|
+
export declare function isPromise<Value>(value: Value | Promise<Value>): value is Promise<Value>;
|
|
19
|
+
export declare function encodePathname(pathname: string): string;
|
|
20
|
+
export declare function getRoute<AppLocales extends Locales>(locale: AppLocales[number], pathname: string, pathnames: Pathnames<AppLocales>): keyof Pathnames<AppLocales>;
|
|
21
|
+
export declare function applyPathnamePrefix<AppLocales extends Locales, AppLocalePrefixMode extends LocalePrefixMode, AppPathnames extends Pathnames<AppLocales> | undefined, AppDomains extends DomainsConfig<AppLocales> | undefined>(pathname: string, locale: Locales[number], routing: Pick<ResolvedRoutingConfig<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>, 'localePrefix' | 'domains'> & Partial<Pick<ResolvedRoutingConfig<AppLocales, AppLocalePrefixMode, AppPathnames, AppDomains>, 'defaultLocale'>>, force?: boolean): string;
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tanstack-start-intl",
|
|
3
|
+
"version": "0.0.0-alpha.0",
|
|
4
|
+
"sideEffects": false,
|
|
5
|
+
"description": "Internationalization (i18n) for TanStack Start",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "rm -rf dist && rollup -c",
|
|
9
|
+
"lint": "eslint src && tsc --noEmit",
|
|
10
|
+
"lint:prettier": "prettier src --check"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/esm/production/index.js",
|
|
14
|
+
"typings": "./dist/types/src/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/types/src/index.d.ts",
|
|
18
|
+
"development": "./dist/esm/development/index.js",
|
|
19
|
+
"default": "./dist/esm/production/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./server": {
|
|
22
|
+
"types": "./dist/types/src/server.d.ts",
|
|
23
|
+
"development": "./dist/esm/development/server.js",
|
|
24
|
+
"default": "./dist/esm/production/server.js"
|
|
25
|
+
},
|
|
26
|
+
"./routing": {
|
|
27
|
+
"types": "./dist/types/src/routing.d.ts",
|
|
28
|
+
"development": "./dist/esm/development/routing.js",
|
|
29
|
+
"default": "./dist/esm/production/routing.js"
|
|
30
|
+
},
|
|
31
|
+
"./config": {
|
|
32
|
+
"types": "./dist/types/src/config.d.ts",
|
|
33
|
+
"development": "./dist/esm/development/config.js",
|
|
34
|
+
"default": "./dist/esm/production/config.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": ["dist", "config.d.ts", "routing.d.ts", "server.d.ts"],
|
|
38
|
+
"keywords": [
|
|
39
|
+
"react",
|
|
40
|
+
"intl",
|
|
41
|
+
"i18n",
|
|
42
|
+
"internationalization",
|
|
43
|
+
"localization",
|
|
44
|
+
"tanstack",
|
|
45
|
+
"tanstack-start"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"use-intl": "workspace:^"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@tanstack/react-router": ">=1.0.0",
|
|
52
|
+
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^20.14.5",
|
|
56
|
+
"@types/react": "^19.2.3",
|
|
57
|
+
"@types/react-dom": "^19.2.3",
|
|
58
|
+
"eslint": "9.11.1",
|
|
59
|
+
"eslint-config-molindo": "^8.0.0",
|
|
60
|
+
"prettier": "^3.3.3",
|
|
61
|
+
"react": "^19.2.3",
|
|
62
|
+
"react-dom": "^19.2.3",
|
|
63
|
+
"rollup": "^4.18.0",
|
|
64
|
+
"tools": "workspace:^",
|
|
65
|
+
"typescript": "^5.5.3"
|
|
66
|
+
}
|
|
67
|
+
}
|