next-tongue 0.9.8 → 0.9.9
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/compiler/validate/a11y.js +5 -1
- package/dist/compiler/validate/catalog.d.ts +5 -0
- package/dist/compiler/validate/catalog.js +21 -2
- package/dist/navigation/index.d.ts +8 -0
- package/dist/navigation/index.js +14 -0
- package/dist/proxy/index.d.ts +2 -0
- package/dist/proxy/index.js +3 -1
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -1
- package/dist/server/request-locale.d.ts +27 -0
- package/dist/server/request-locale.js +22 -0
- package/package.json +1 -1
|
@@ -75,6 +75,8 @@ function hasLangHint(src) {
|
|
|
75
75
|
/<html[^>]*\blang=["']/.test(src) ||
|
|
76
76
|
/\blang=\{locale\}/.test(src) ||
|
|
77
77
|
/document\.documentElement\.lang/.test(src) ||
|
|
78
|
+
/setAttribute\(\s*['"]lang['"]/.test(src) ||
|
|
79
|
+
/\bHtmlLang\b/.test(src) ||
|
|
78
80
|
/\.lang\s*=/.test(src));
|
|
79
81
|
}
|
|
80
82
|
function hasDirHint(src) {
|
|
@@ -82,7 +84,9 @@ function hasDirHint(src) {
|
|
|
82
84
|
/<html[^>]*\bdir=["']/.test(src) ||
|
|
83
85
|
/\bdir=\{getDir\(/.test(src) ||
|
|
84
86
|
/\bdir=\{.*getDir/.test(src) ||
|
|
85
|
-
/document\.documentElement\.dir/.test(src)
|
|
87
|
+
/document\.documentElement\.dir/.test(src) ||
|
|
88
|
+
/setAttribute\(\s*['"]dir['"]/.test(src) ||
|
|
89
|
+
/\bHtmlLang\b/.test(src));
|
|
86
90
|
}
|
|
87
91
|
async function findLocaleLayouts(cwd) {
|
|
88
92
|
return findLayoutsUnder(cwd, ['app/[locale]', 'src/app/[locale]']);
|
|
@@ -22,3 +22,8 @@ export declare function validateStale(config: ResolvedTongueConfig, cwd: string,
|
|
|
22
22
|
}): Promise<CheckIssue[]>;
|
|
23
23
|
/** Letter + single quote + letter — usually meant as prose apostrophe, not ICU quote. */
|
|
24
24
|
export declare function likelyUnescapedApostrophe(icu: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Apostrophe-in-words is common in fr/it/…; English `don't` floods CI with noise.
|
|
27
|
+
* Only hint for locales that regularly need ICU `''` in prose.
|
|
28
|
+
*/
|
|
29
|
+
export declare function shouldHintApostrophe(locale: string): boolean;
|
|
@@ -69,11 +69,12 @@ export async function validateCatalog(config, cwd, opts) {
|
|
|
69
69
|
});
|
|
70
70
|
continue;
|
|
71
71
|
}
|
|
72
|
-
if (
|
|
72
|
+
if (shouldHintApostrophe(config.defaultLocale) &&
|
|
73
|
+
likelyUnescapedApostrophe(source)) {
|
|
73
74
|
issues.push({
|
|
74
75
|
check: 'icu',
|
|
75
76
|
severity: 'warn',
|
|
76
|
-
message: `Key "${fullKey}" looks like a natural-language apostrophe
|
|
77
|
+
message: `Key "${fullKey}" looks like a natural-language apostrophe. In ICU, double it: "l''été" → l'été`,
|
|
77
78
|
key: fullKey,
|
|
78
79
|
locale: config.defaultLocale,
|
|
79
80
|
file,
|
|
@@ -142,6 +143,17 @@ export async function validateCatalog(config, cwd, opts) {
|
|
|
142
143
|
});
|
|
143
144
|
continue;
|
|
144
145
|
}
|
|
146
|
+
if (shouldHintApostrophe(locale) &&
|
|
147
|
+
likelyUnescapedApostrophe(resolved)) {
|
|
148
|
+
issues.push({
|
|
149
|
+
check: 'icu',
|
|
150
|
+
severity: 'warn',
|
|
151
|
+
message: `Key "${fullKey}" looks like a natural-language apostrophe. In ICU, double it: "l''été" → l'été`,
|
|
152
|
+
key: fullKey,
|
|
153
|
+
locale,
|
|
154
|
+
file: localeFile,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
145
157
|
const mismatchSev = severityFor(config.strict.mismatch);
|
|
146
158
|
if (mismatchSev) {
|
|
147
159
|
const otherArgs = collectArgs(otherAst);
|
|
@@ -247,3 +259,10 @@ export function likelyUnescapedApostrophe(icu) {
|
|
|
247
259
|
}
|
|
248
260
|
return false;
|
|
249
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Apostrophe-in-words is common in fr/it/…; English `don't` floods CI with noise.
|
|
264
|
+
* Only hint for locales that regularly need ICU `''` in prose.
|
|
265
|
+
*/
|
|
266
|
+
export function shouldHintApostrophe(locale) {
|
|
267
|
+
return /^(fr|it|ca|oc|ht|lb|rm|ga|gd|mt)(-|$)/i.test(locale);
|
|
268
|
+
}
|
|
@@ -48,6 +48,7 @@ export declare function createNavigation(config: NavigationConfig): {
|
|
|
48
48
|
refresh: () => void;
|
|
49
49
|
};
|
|
50
50
|
usePathname: () => string;
|
|
51
|
+
usePathnameWithoutLocale: () => string;
|
|
51
52
|
getPathname: (args: {
|
|
52
53
|
href: Href;
|
|
53
54
|
locale: Locale;
|
|
@@ -57,3 +58,10 @@ export declare function createNavigation(config: NavigationConfig): {
|
|
|
57
58
|
children: (options: import("./locale-switcher.js").UseLocaleSwitcherResult) => ReactNode;
|
|
58
59
|
}) => import("react").JSX.Element;
|
|
59
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Strip a leading locale segment from `next/navigation`'s pathname.
|
|
63
|
+
* Prefer `createNavigation(…).usePathname` / `usePathnameWithoutLocale` when
|
|
64
|
+
* you already use tongue navigation; this unbound helper is for apps that keep
|
|
65
|
+
* plain `next/link` + proxy for as-needed paths.
|
|
66
|
+
*/
|
|
67
|
+
export declare function usePathnameWithoutLocale(locales: readonly string[]): string;
|
package/dist/navigation/index.js
CHANGED
|
@@ -77,6 +77,10 @@ export function createNavigation(config) {
|
|
|
77
77
|
const locale = useLocale();
|
|
78
78
|
return toInternalPathname(stripped, locale, bound.pathnames);
|
|
79
79
|
}
|
|
80
|
+
/** Same as `usePathname` when pathnames are unset — locale prefix stripped. */
|
|
81
|
+
function usePathnameWithoutLocale() {
|
|
82
|
+
return stripLocalePrefix(useNextPathname(), bound.locales);
|
|
83
|
+
}
|
|
80
84
|
const { useLocaleSwitcher, LocaleSwitcher } = createLocaleSwitcher(config);
|
|
81
85
|
return {
|
|
82
86
|
Link,
|
|
@@ -84,8 +88,18 @@ export function createNavigation(config) {
|
|
|
84
88
|
permanentRedirect,
|
|
85
89
|
useRouter,
|
|
86
90
|
usePathname,
|
|
91
|
+
usePathnameWithoutLocale,
|
|
87
92
|
getPathname: (args) => resolve(args.href, args.locale),
|
|
88
93
|
useLocaleSwitcher,
|
|
89
94
|
LocaleSwitcher,
|
|
90
95
|
};
|
|
91
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Strip a leading locale segment from `next/navigation`'s pathname.
|
|
99
|
+
* Prefer `createNavigation(…).usePathname` / `usePathnameWithoutLocale` when
|
|
100
|
+
* you already use tongue navigation; this unbound helper is for apps that keep
|
|
101
|
+
* plain `next/link` + proxy for as-needed paths.
|
|
102
|
+
*/
|
|
103
|
+
export function usePathnameWithoutLocale(locales) {
|
|
104
|
+
return stripLocalePrefix(useNextPathname(), locales);
|
|
105
|
+
}
|
package/dist/proxy/index.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export type CreateProxyOptions = {
|
|
|
48
48
|
resolveLocale?: (req: TongueProxyRequest) => string | undefined | Promise<string | undefined>;
|
|
49
49
|
onLocale?: (locale: string, req: TongueProxyRequest) => void;
|
|
50
50
|
};
|
|
51
|
+
/** Default locale cookie name (`SameSite=Lax`). Use with `requestLocale` in API routes. */
|
|
52
|
+
export declare const LOCALE_COOKIE_NAME = "TONGUE_LOCALE";
|
|
51
53
|
/**
|
|
52
54
|
* Next.js 16 `proxy.ts` handler for locale prefix routing.
|
|
53
55
|
* Returns standard `Response` objects (compatible with Next proxy/middleware).
|
package/dist/proxy/index.js
CHANGED
|
@@ -2,8 +2,10 @@ import { toInternalPathname } from '../navigation/pathname.js';
|
|
|
2
2
|
import { createLocaleMatcher, } from './locale-matcher.js';
|
|
3
3
|
export { createLocaleMatcher, buildLocaleMatcherTables, emitLocaleMatcherModule, localeParentChain, matchTagAgainstTables, } from './locale-matcher.js';
|
|
4
4
|
export { verifyLocalePreference, signLocalePreference, timingSafeEqual, DEFAULT_PREF_COOKIE, } from '../runtime/locale-preference.js';
|
|
5
|
+
/** Default locale cookie name (`SameSite=Lax`). Use with `requestLocale` in API routes. */
|
|
6
|
+
export const LOCALE_COOKIE_NAME = 'TONGUE_LOCALE';
|
|
5
7
|
const DEFAULT_COOKIE = {
|
|
6
|
-
name:
|
|
8
|
+
name: LOCALE_COOKIE_NAME,
|
|
7
9
|
maxAge: 60 * 60 * 24 * 365,
|
|
8
10
|
sameSite: 'lax',
|
|
9
11
|
path: '/',
|
package/dist/server/index.d.ts
CHANGED
|
@@ -61,3 +61,5 @@ export type { Format, SanctionedUnit, SimpleUnit, DisplayNameType, } from '../ru
|
|
|
61
61
|
export { isSanctionedUnit, SIMPLE_UNITS } from '../runtime/format.js';
|
|
62
62
|
export { setLocalePreference, } from './locale-preference.js';
|
|
63
63
|
export type { SetLocalePreferenceOptions, CookieStoreLike, } from './locale-preference.js';
|
|
64
|
+
export { requestLocale, LOCALE_COOKIE_NAME, LOCALE_HEADER_NAME, } from './request-locale.js';
|
|
65
|
+
export type { RequestLocaleOptions, RequestLocaleSource, } from './request-locale.js';
|
package/dist/server/index.js
CHANGED
|
@@ -56,7 +56,7 @@ export async function getT(localeOrNs, namespace) {
|
|
|
56
56
|
async function getTWithLocale(locale, ns) {
|
|
57
57
|
assertLocale(locale);
|
|
58
58
|
if (!namespaceLoader) {
|
|
59
|
-
throw new TongueError('TONGUE_E_TYPES_MISSING', 'Message modules not loaded.
|
|
59
|
+
throw new TongueError('TONGUE_E_TYPES_MISSING', 'Message modules not loaded. Import ensureTongue from `#tongue/runtime` and call it once (layout/page), or wrap next.config with withTongue() and run `tongue build && next build` so `.tongue/` is fresh.');
|
|
60
60
|
}
|
|
61
61
|
const messages = await namespaceLoader(ns);
|
|
62
62
|
return createT({
|
|
@@ -141,6 +141,7 @@ export { generateAlternates, createGenerateAlternates, } from './alternates.js';
|
|
|
141
141
|
export { translateIssues, defaultFormatPath, } from '../runtime/translate-issues.js';
|
|
142
142
|
export { isSanctionedUnit, SIMPLE_UNITS } from '../runtime/format.js';
|
|
143
143
|
export { setLocalePreference, } from './locale-preference.js';
|
|
144
|
+
export { requestLocale, LOCALE_COOKIE_NAME, LOCALE_HEADER_NAME, } from './request-locale.js';
|
|
144
145
|
function assertLocale(locale) {
|
|
145
146
|
if (runtimeLocales.length && !runtimeLocales.includes(locale)) {
|
|
146
147
|
throw new TongueError('TONGUE_E_LOCALE', `Locale "${locale}" is not in configured locales [${runtimeLocales.join(', ')}]`);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Default cookie name set by `createProxy` (`SameSite=Lax`). */
|
|
2
|
+
export declare const LOCALE_COOKIE_NAME = "TONGUE_LOCALE";
|
|
3
|
+
/** Common header for client → API locale (wizard / fetch). */
|
|
4
|
+
export declare const LOCALE_HEADER_NAME = "x-locale";
|
|
5
|
+
export type RequestLocaleSource = {
|
|
6
|
+
headers: {
|
|
7
|
+
get: (name: string) => string | null;
|
|
8
|
+
};
|
|
9
|
+
cookies?: {
|
|
10
|
+
get: (name: string) => {
|
|
11
|
+
value: string;
|
|
12
|
+
} | undefined;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type RequestLocaleOptions = {
|
|
16
|
+
locales: readonly string[];
|
|
17
|
+
defaultLocale: string;
|
|
18
|
+
/** Defaults to `TONGUE_LOCALE`. */
|
|
19
|
+
cookieName?: string;
|
|
20
|
+
/** Defaults to `x-locale`. */
|
|
21
|
+
headerName?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Resolve locale in Route Handlers / Server Actions (no `params.locale`).
|
|
25
|
+
* Order: header → cookie → defaultLocale.
|
|
26
|
+
*/
|
|
27
|
+
export declare function requestLocale(req: RequestLocaleSource, opts: RequestLocaleOptions): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isLocale as isLocaleValue } from '../runtime/locale.js';
|
|
2
|
+
/** Default cookie name set by `createProxy` (`SameSite=Lax`). */
|
|
3
|
+
export const LOCALE_COOKIE_NAME = 'TONGUE_LOCALE';
|
|
4
|
+
/** Common header for client → API locale (wizard / fetch). */
|
|
5
|
+
export const LOCALE_HEADER_NAME = 'x-locale';
|
|
6
|
+
/**
|
|
7
|
+
* Resolve locale in Route Handlers / Server Actions (no `params.locale`).
|
|
8
|
+
* Order: header → cookie → defaultLocale.
|
|
9
|
+
*/
|
|
10
|
+
export function requestLocale(req, opts) {
|
|
11
|
+
const headerName = opts.headerName ?? LOCALE_HEADER_NAME;
|
|
12
|
+
const cookieName = opts.cookieName ?? LOCALE_COOKIE_NAME;
|
|
13
|
+
const fromHeader = req.headers.get(headerName)?.trim();
|
|
14
|
+
if (fromHeader && isLocaleValue(fromHeader, opts.locales)) {
|
|
15
|
+
return fromHeader;
|
|
16
|
+
}
|
|
17
|
+
const fromCookie = req.cookies?.get(cookieName)?.value?.trim();
|
|
18
|
+
if (fromCookie && isLocaleValue(fromCookie, opts.locales)) {
|
|
19
|
+
return fromCookie;
|
|
20
|
+
}
|
|
21
|
+
return opts.defaultLocale;
|
|
22
|
+
}
|