cloudflare-next-intl 0.2.2 → 0.3.1

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.
Files changed (97) hide show
  1. package/README.md +2 -3
  2. package/dist/src/client/components/client_provider.d.ts +3 -1
  3. package/dist/src/client/components/client_provider.js +16 -3
  4. package/dist/src/client/functions/get_cookie.bench.d.ts +1 -0
  5. package/dist/src/client/functions/get_cookie.bench.js +11 -0
  6. package/dist/src/client/functions/set_cookie.bench.d.ts +1 -0
  7. package/dist/src/client/functions/set_cookie.bench.js +10 -0
  8. package/dist/src/client/functions/set_cookie.d.ts +4 -2
  9. package/dist/src/client/functions/set_cookie.js +3 -1
  10. package/dist/src/client/hooks/client_hooks.d.ts +5 -0
  11. package/dist/src/client/hooks/client_hooks.js +7 -2
  12. package/dist/src/client/index.js +3 -0
  13. package/dist/src/config/cookie_key.bench.d.ts +1 -0
  14. package/dist/src/config/cookie_key.bench.js +9 -0
  15. package/dist/src/config/init_config.bench.d.ts +1 -0
  16. package/dist/src/config/init_config.bench.js +7 -0
  17. package/dist/src/config/intl_config.js +4 -1
  18. package/dist/src/config/intl_sitemap.bench.d.ts +1 -0
  19. package/dist/src/config/intl_sitemap.bench.js +18 -0
  20. package/dist/src/config/intl_sitemap.js +1 -1
  21. package/dist/src/config/middleware.bench.d.ts +1 -0
  22. package/dist/src/config/middleware.bench.js +34 -0
  23. package/dist/src/config/middleware.js +43 -8
  24. package/dist/src/firebase_auth/client/auth_actions.bench.d.ts +1 -0
  25. package/dist/src/firebase_auth/client/auth_actions.bench.js +36 -0
  26. package/dist/src/firebase_auth/client/auth_actions.d.ts +46 -0
  27. package/dist/src/firebase_auth/client/auth_actions.js +102 -0
  28. package/dist/src/firebase_auth/client/auth_user_cache.bench.d.ts +1 -0
  29. package/dist/src/firebase_auth/client/auth_user_cache.bench.js +11 -0
  30. package/dist/src/firebase_auth/client/auth_user_cache.d.ts +4 -0
  31. package/dist/src/firebase_auth/client/auth_user_cache.js +14 -0
  32. package/dist/src/firebase_auth/client/auth_user_provider.d.ts +36 -0
  33. package/dist/src/firebase_auth/client/auth_user_provider.js +148 -0
  34. package/dist/src/firebase_auth/client/firebase_client.bench.d.ts +1 -0
  35. package/dist/src/firebase_auth/client/firebase_client.bench.js +28 -0
  36. package/dist/src/firebase_auth/client/firebase_client.d.ts +18 -0
  37. package/dist/src/firebase_auth/client/firebase_client.js +40 -0
  38. package/dist/src/firebase_auth/client/use_auth_user.d.ts +12 -0
  39. package/dist/src/firebase_auth/client/use_auth_user.js +20 -0
  40. package/dist/src/firebase_auth/error_messages/default_messages.en.bench.d.ts +1 -0
  41. package/dist/src/firebase_auth/error_messages/default_messages.en.bench.js +10 -0
  42. package/dist/src/firebase_auth/error_messages/default_messages.en.d.ts +1 -0
  43. package/dist/src/firebase_auth/error_messages/default_messages.en.js +14 -0
  44. package/dist/src/firebase_auth/error_messages/firebase_auth_error_helper.bench.d.ts +1 -0
  45. package/dist/src/firebase_auth/error_messages/firebase_auth_error_helper.bench.js +15 -0
  46. package/dist/src/firebase_auth/error_messages/firebase_auth_error_helper.d.ts +6 -0
  47. package/dist/src/firebase_auth/error_messages/firebase_auth_error_helper.js +45 -0
  48. package/dist/src/firebase_auth/index.d.ts +9 -0
  49. package/dist/src/firebase_auth/index.js +13 -0
  50. package/dist/src/firebase_auth/middleware/update_session.bench.d.ts +1 -0
  51. package/dist/src/firebase_auth/middleware/update_session.bench.js +37 -0
  52. package/dist/src/firebase_auth/middleware/update_session.d.ts +22 -0
  53. package/dist/src/firebase_auth/middleware/update_session.js +213 -0
  54. package/dist/src/firebase_auth/require_config.bench.d.ts +1 -0
  55. package/dist/src/firebase_auth/require_config.bench.js +24 -0
  56. package/dist/src/firebase_auth/require_config.d.ts +8 -0
  57. package/dist/src/firebase_auth/require_config.js +13 -0
  58. package/dist/src/firebase_auth/server/auth_user_server_provider.d.ts +24 -0
  59. package/dist/src/firebase_auth/server/auth_user_server_provider.js +51 -0
  60. package/dist/src/firebase_auth/server/firebase_server.bench.d.ts +1 -0
  61. package/dist/src/firebase_auth/server/firebase_server.bench.js +27 -0
  62. package/dist/src/firebase_auth/server/firebase_server.d.ts +15 -0
  63. package/dist/src/firebase_auth/server/firebase_server.js +50 -0
  64. package/dist/src/firebase_auth/server/use_auth_user_server.bench.d.ts +1 -0
  65. package/dist/src/firebase_auth/server/use_auth_user_server.bench.js +14 -0
  66. package/dist/src/firebase_auth/server/use_auth_user_server.d.ts +39 -0
  67. package/dist/src/firebase_auth/server/use_auth_user_server.js +37 -0
  68. package/dist/src/firebase_auth/types.d.ts +22 -0
  69. package/dist/src/firebase_auth/types.js +1 -0
  70. package/dist/src/general/cache_variables.bench.d.ts +1 -0
  71. package/dist/src/general/cache_variables.bench.js +19 -0
  72. package/dist/src/general/cache_variables.d.ts +1 -0
  73. package/dist/src/general/cache_variables.js +4 -6
  74. package/dist/src/general/general_functions.bench.d.ts +1 -0
  75. package/dist/src/general/general_functions.bench.js +18 -0
  76. package/dist/src/general/general_functions.js +15 -2
  77. package/dist/src/general/index.js +0 -1
  78. package/dist/src/general/metadata.bench.d.ts +1 -0
  79. package/dist/src/general/metadata.bench.js +15 -0
  80. package/dist/src/general/metadata.js +1 -1
  81. package/dist/src/server/components/server_provider.js +7 -1
  82. package/dist/src/server/functions/get_user_locale.bench.d.ts +1 -0
  83. package/dist/src/server/functions/get_user_locale.bench.js +13 -0
  84. package/dist/src/server/functions/get_user_locale.js +7 -4
  85. package/dist/src/server/functions/server.bench.d.ts +1 -0
  86. package/dist/src/server/functions/server.bench.js +39 -0
  87. package/dist/src/server/functions/server.js +9 -5
  88. package/dist/src/server/functions/use_functions.bench.d.ts +1 -0
  89. package/dist/src/server/functions/use_functions.bench.js +23 -0
  90. package/dist/src/server/functions/use_functions.d.ts +5 -0
  91. package/dist/src/server/functions/use_functions.js +19 -4
  92. package/dist/src/server/index.js +4 -1
  93. package/dist/src/test_utils/mock_next_server.d.ts +5 -0
  94. package/dist/src/test_utils/mock_next_server.js +10 -0
  95. package/dist/src/types/types.d.ts +48 -1
  96. package/llms.txt +48 -0
  97. package/package.json +52 -10
@@ -51,7 +51,10 @@ export function getTranslationsImpl(locale, messages, namespace, cacheKey) {
51
51
  }
52
52
  }
53
53
  }
54
- // If after traversal, no base translations object was found.
54
+ // If after traversal, no base translations object was found. Unreachable:
55
+ // the loop above always either sets translationsBase or returns early on
56
+ // its final iteration, since namespaceParts always has length >= 1
57
+ // (''.split('.') yields ['']).
55
58
  if (!translationsBase) {
56
59
  return errorAndReturnFallback(`Translations for namespace "${namespace}" could not be found.`, cacheKeyValue, locale, namespace);
57
60
  }
@@ -66,6 +69,9 @@ export function getTranslationsImpl(locale, messages, namespace, cacheKey) {
66
69
  // Traverse the resolved translations base using the key parts.
67
70
  for (let i = 0; i < keyParts.length; i++) {
68
71
  const part = keyParts[i];
72
+ // Unreachable: currentTranslation only ever becomes a string via
73
+ // the reassignment below, which is guarded to only assign
74
+ // non-null objects.
69
75
  if (typeof currentTranslation === 'string') {
70
76
  // Translation key path prematurely leads to a string.
71
77
  console.warn(`Translation key "${key}" in namespace "${namespace}" leads to a string prematurely at "${part}" for locale "${locale}".`);
@@ -73,6 +79,10 @@ export function getTranslationsImpl(locale, messages, namespace, cacheKey) {
73
79
  }
74
80
  const value = currentTranslation[part];
75
81
  if (i === keyParts.length - 1) {
82
+ if (typeof value !== 'string') {
83
+ console.warn(`Translation key "${key}" in namespace "${namespace}" resolves to a non-string value for locale "${locale}". Expected string, got "${typeof value}".`);
84
+ return key;
85
+ }
76
86
  return value;
77
87
  }
78
88
  else {
@@ -87,7 +97,10 @@ export function getTranslationsImpl(locale, messages, namespace, cacheKey) {
87
97
  }
88
98
  }
89
99
  }
90
- // If the loop completes and no string translation was found (e.g., key missing or not a string).
100
+ // If the loop completes and no string translation was found (e.g.,
101
+ // key missing or not a string). Unreachable: keyParts always has
102
+ // length >= 1 (''.split('.') yields ['']), and every branch above
103
+ // returns on the final iteration.
91
104
  console.warn(`Translation key "${key}" in namespace "${namespace}" is missing or not a string for locale "${locale}".`);
92
105
  return key; // Return the key as fallback
93
106
  };
@@ -1,3 +1,2 @@
1
1
  export { setLocaleCache as setLocale, setLocaleAsync } from './cache_variables';
2
2
  export { alternatesLinks } from './metadata';
3
- // export { default as getLayoutStates } from './get_layout_states';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { bench, describe } from 'vitest';
2
+ import { alternatesLinks, languages } from './metadata';
3
+ describe('languages', () => {
4
+ bench('builds per-locale hreflang map for one URL', () => {
5
+ languages(`https://example.com/${Math.random()}`, '/about');
6
+ });
7
+ });
8
+ describe('alternatesLinks', () => {
9
+ bench('builds canonical + languages for the default locale', () => {
10
+ alternatesLinks({ url: `https://example.com/${Math.random()}`, locale: 'en', linkPart: '/about' });
11
+ });
12
+ bench('builds languages only for a non-default locale (no canonical)', () => {
13
+ alternatesLinks({ url: `https://example.com/${Math.random()}`, locale: 'de', linkPart: '/about' });
14
+ });
15
+ });
@@ -34,7 +34,7 @@ export function iAlternatesLinks({ locale, url, canonical, linkPart }) {
34
34
  };
35
35
  }
36
36
  catch (e) {
37
- console.error(`Language Helper error for Metadata, link: ${url}, linkPart: ${linkPart}, Error: ${e}`);
37
+ console.error(`[cloudflare-next-intl] alternatesLinks failed for url="${url}" linkPart="${linkPart}" — returning undefined, so "alternates" will be omitted from your metadata. Underlying error:`, e);
38
38
  return undefined;
39
39
  }
40
40
  }
@@ -3,6 +3,7 @@ import { setLocaleCache, setMessageForLocaleCache } from "../../general/cache_va
3
3
  import { getMessage } from "../functions/server";
4
4
  import dynamic from "next/dynamic";
5
5
  import { localesSet } from "../../config/middleware";
6
+ import config from "../../config/intl_config";
6
7
  const LocationzationClientProvider = dynamic(() => import("../../client/components/client_provider"));
7
8
  /**
8
9
  * Server component that provides locale/messages context to the rest of the
@@ -45,5 +46,10 @@ export default async function LocationzationProvider({ language, messages, child
45
46
  setMessageForLocaleCache(language, messages);
46
47
  }
47
48
  const messagesValue = messages ?? await getMessage(language);
48
- return _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, children: children });
49
+ let initialAuthUser = null;
50
+ if (config.firebaseAuth) {
51
+ const { resolveAuthUserAndRedirect } = await import("../../firebase_auth/server/auth_user_server_provider");
52
+ initialAuthUser = await resolveAuthUserAndRedirect();
53
+ }
54
+ return _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, initialAuthUser: initialAuthUser, children: children });
49
55
  }
@@ -0,0 +1,13 @@
1
+ import { bench, describe } from 'vitest';
2
+ import { languageDetecotr } from './get_user_locale';
3
+ describe('languageDetecotr', () => {
4
+ bench('single locale header', () => {
5
+ languageDetecotr('en');
6
+ });
7
+ bench('realistic multi-entry Accept-Language header', () => {
8
+ languageDetecotr('fr-CA,fr;q=0.9,en-US;q=0.8,en;q=0.7,de;q=0.6,es;q=0.5');
9
+ });
10
+ bench('no header (default-locale fallback)', () => {
11
+ languageDetecotr(null);
12
+ });
13
+ });
@@ -14,11 +14,14 @@ function languageDetecotrImpl(acceptLanguageHeader) {
14
14
  .split(',');
15
15
  let localeValue;
16
16
  for (const item of parsedLocales) {
17
- const parts = item.trim().split(';');
18
- const locale = parts[0];
19
- const languageOnly = locale.split('-')[0];
17
+ const trimmed = item.trim();
18
+ const semi = trimmed.indexOf(';');
19
+ const locale = semi === -1 ? trimmed : trimmed.slice(0, semi);
20
+ const dash = locale.indexOf('-');
21
+ const languageOnly = dash === -1 ? locale : locale.slice(0, dash);
20
22
  if (languageOnly && localesSet.has(languageOnly)) {
21
- const q = parts.length > 1 ? parseFloat(parts[1].split('=')[1]) : 1;
23
+ const eq = semi === -1 ? -1 : trimmed.indexOf('=', semi);
24
+ const q = eq === -1 ? 1 : parseFloat(trimmed.slice(eq + 1));
22
25
  if (!localeValue || localeValue.q < q) {
23
26
  localeValue = { locale: languageOnly, q };
24
27
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ import { bench, describe, vi } from 'vitest';
2
+ const mockCookiesGet = () => ({ value: 'en' });
3
+ vi.mock('next/headers', () => ({
4
+ cookies: vi.fn(async () => ({ get: mockCookiesGet })),
5
+ }));
6
+ describe('getMessage', () => {
7
+ bench('cold: dynamic import + cache write for an uncached locale', async () => {
8
+ vi.resetModules();
9
+ const { getMessage } = await import('./server');
10
+ await getMessage('en');
11
+ });
12
+ bench('warm: module-scope Map cache hit for an already-loaded locale', async () => {
13
+ const { getMessage } = await import('./server');
14
+ await getMessage('en');
15
+ });
16
+ });
17
+ describe('getTranslations', () => {
18
+ bench('cold: full namespace traversal + translator build', async () => {
19
+ vi.resetModules();
20
+ const { getTranslations } = await import('./server');
21
+ await getTranslations('Common', 'en');
22
+ });
23
+ bench('warm: translationFunctionsCache hit for repeated namespace/locale', async () => {
24
+ const { getTranslations } = await import('./server');
25
+ await getTranslations('Common', 'en');
26
+ await getTranslations('Common', 'en');
27
+ });
28
+ });
29
+ describe('getLocale', () => {
30
+ bench('cold: reads the locale cookie', async () => {
31
+ vi.resetModules();
32
+ const { getLocale } = await import('./server');
33
+ await getLocale();
34
+ });
35
+ bench('warm: module-scope currentLanguage cache hit', async () => {
36
+ const { getLocale } = await import('./server');
37
+ await getLocale();
38
+ });
39
+ });
@@ -1,7 +1,7 @@
1
1
  import { getTranslationsImpl } from "../../general/general_functions";
2
2
  import config from "../../config/intl_config";
3
3
  import { localeCookieName } from "../../config/cookie_key";
4
- import { getLocaleCache, getMessageCache, setLocaleCache, setMessageForLocaleCache } from "../../general/cache_variables";
4
+ import { getLocaleCache, getMessageCache, getTranslationCache, setLocaleCache, setMessageForLocaleCache } from "../../general/cache_variables";
5
5
  import { cache } from "react";
6
6
  import { localesSet } from "../../config/middleware";
7
7
  const isDev = process.env.NODE_ENV === 'development';
@@ -57,10 +57,14 @@ async function iGetTranslations(namespace, locale) {
57
57
  // Determine the effective locale, awaiting getLocale only if not provided.
58
58
  const effectiveLocale = locale ?? (await getLocale());
59
59
  const cacheKey = `${effectiveLocale}-${namespace}`;
60
- // Return cached translation function immediately if available.
61
- // if (translationFunctionsCache.has(cacheKey)) {
62
- // return translationFunctionsCache.get(cacheKey)!;
63
- // }
60
+ // Return cached translation function immediately if available. Skipped
61
+ // in dev for the same reason iGetMessage skips its own cache read: a
62
+ // stale translator function built from a since-edited messages/*.json
63
+ // must not survive across requests during local development.
64
+ const cachedTranslation = isDev ? undefined : getTranslationCache(cacheKey);
65
+ if (cachedTranslation) {
66
+ return cachedTranslation;
67
+ }
64
68
  // Load messages for the effective locale. This also benefits from caching.
65
69
  const serverMessages = await iGetMessage(effectiveLocale);
66
70
  return getTranslationsImpl(effectiveLocale, serverMessages, namespace, cacheKey);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { bench, describe, vi } from 'vitest';
2
+ import * as ReactModule from 'react';
3
+ vi.mock('./server', () => ({
4
+ getLocale: vi.fn(async () => 'en'),
5
+ getMessage: vi.fn(async () => ({ Common: { title: 'Hello' } })),
6
+ }));
7
+ describe('useLocaleImpl', () => {
8
+ vi.spyOn(ReactModule, 'use').mockReturnValue('en');
9
+ bench('resolves the locale via use(getLocale())', async () => {
10
+ const { useLocaleImpl } = await import('./use_functions');
11
+ useLocaleImpl();
12
+ });
13
+ });
14
+ describe('useTranslations (RSC)', () => {
15
+ vi.spyOn(ReactModule, 'use')
16
+ .mockReturnValueOnce('en')
17
+ .mockImplementation(() => ({ Common: { title: 'Hello' } }));
18
+ bench('resolves locale + messages and builds a translation function', async () => {
19
+ const { useTranslations } = await import('./use_functions');
20
+ const t = useTranslations('Common');
21
+ t('title');
22
+ });
23
+ });
@@ -12,6 +12,8 @@ import type { TranslatorReturnType } from "../../types/types";
12
12
  *
13
13
  * @returns The current locale (e.g. `"en"`).
14
14
  * @throws If called without an `IntlProvider` above it in the tree.
15
+ * @example
16
+ * const locale = useLocale(); // "en"
15
17
  */
16
18
  export declare function useLocaleImpl(): string;
17
19
  export declare const useLocale: typeof useLocaleImpl;
@@ -22,6 +24,9 @@ export declare const useLocale: typeof useLocaleImpl;
22
24
  * @param namespace Dot-separated key prefix into your messages file.
23
25
  * @returns A `(key: string) => string` translation function.
24
26
  * @throws If called without an `IntlProvider` above it in the tree.
27
+ * @example
28
+ * const t = useTranslations("Index");
29
+ * return <h1>{t("title")}</h1>;
25
30
  */
26
31
  declare function useTranslationsImpl(namespace: string): TranslatorReturnType;
27
32
  export declare const useTranslations: typeof useTranslationsImpl;
@@ -1,6 +1,8 @@
1
1
  import { getTranslationsImpl } from "../../general/general_functions";
2
2
  import { getLocale, getMessage } from "./server";
3
+ import { getTranslationCache } from "../../general/cache_variables";
3
4
  import { cache, use } from "react";
5
+ const isDev = process.env.NODE_ENV === 'development';
4
6
  /**
5
7
  * React Server Component `useLocale`, reached via the `cloudflare-next-intl/use`
6
8
  * subpath's `react-server` condition (resolved automatically — you always
@@ -14,11 +16,13 @@ import { cache, use } from "react";
14
16
  *
15
17
  * @returns The current locale (e.g. `"en"`).
16
18
  * @throws If called without an `IntlProvider` above it in the tree.
19
+ * @example
20
+ * const locale = useLocale(); // "en"
17
21
  */
18
22
  export function useLocaleImpl() {
19
23
  const language = use(getLocale());
20
24
  if (language === undefined) {
21
- throw new Error('Please set IntlProvider before using useLocale');
25
+ throw new Error('useLocale must be used within an IntlProvider');
22
26
  }
23
27
  return language;
24
28
  }
@@ -30,13 +34,24 @@ export const useLocale = cache(useLocaleImpl);
30
34
  * @param namespace Dot-separated key prefix into your messages file.
31
35
  * @returns A `(key: string) => string` translation function.
32
36
  * @throws If called without an `IntlProvider` above it in the tree.
37
+ * @example
38
+ * const t = useTranslations("Index");
39
+ * return <h1>{t("title")}</h1>;
33
40
  */
34
41
  function useTranslationsImpl(namespace) {
35
42
  const language = use(getLocale());
43
+ if (!language) {
44
+ throw new Error('useTranslations must be used within an IntlProvider');
45
+ }
46
+ const cacheKey = `${language}-${namespace}`;
47
+ const cachedTranslation = isDev ? undefined : getTranslationCache(cacheKey);
48
+ if (cachedTranslation) {
49
+ return cachedTranslation;
50
+ }
36
51
  const messages = use(getMessage(language));
37
- if (!language || !messages) {
38
- throw new Error('Please set IntlProvider before using useTranslations');
52
+ if (!messages) {
53
+ throw new Error('useTranslations must be used within an IntlProvider');
39
54
  }
40
- return getTranslationsImpl(language, messages, namespace);
55
+ return getTranslationsImpl(language, messages, namespace, cacheKey);
41
56
  }
42
57
  export const useTranslations = cache(useTranslationsImpl);
@@ -1,4 +1,7 @@
1
- // export { useLocale,useTranslations } from './functions/use_functions'; // Export specific server function
1
+ // Intentionally not re-exported here: useLocale/useTranslations are public
2
+ // via the "cloudflare-next-intl/use" subpath instead, which resolves to this
3
+ // react-server implementation or the client one automatically via export conditions.
4
+ // export { useLocale,useTranslations } from './functions/use_functions';
2
5
  export { getMessage as getMessage, getTranslations, getLocale } from './functions/server'; // Export specific server function
3
6
  export { default as IntlProvider } from './components/server_provider';
4
7
  export { default as Link } from './components/link';
@@ -0,0 +1,5 @@
1
+ import { NextRequest } from 'next/server';
2
+ export declare function makeTestRequest(url: string, init?: {
3
+ cookies?: Record<string, string>;
4
+ headers?: Record<string, string>;
5
+ }): NextRequest;
@@ -0,0 +1,10 @@
1
+ import { NextRequest } from 'next/server';
2
+ export function makeTestRequest(url, init) {
3
+ const request = new NextRequest(url, { headers: init?.headers });
4
+ if (init?.cookies) {
5
+ for (const [key, value] of Object.entries(init.cookies)) {
6
+ request.cookies.set(key, value);
7
+ }
8
+ }
9
+ return request;
10
+ }
@@ -73,6 +73,53 @@ export interface RoutingConfig<AppLocales extends Locales, AppLocalePrefixMode e
73
73
  * By setting this to `false`, the cookie as well as the `accept-language` header will no longer be used for locale detection.
74
74
  **/
75
75
  localeDetection?: boolean;
76
+ /**
77
+ * Configures the optional `firebase_auth` submodule. Omit entirely (or
78
+ * leave undefined) to keep it fully disabled — no file in this package
79
+ * ever imports `firebase/app`/`firebase/auth` unless a firebase_auth
80
+ * export is actually called, and every such export throws a clear error
81
+ * if this field is missing at call time rather than silently no-op'ing.
82
+ */
83
+ firebaseAuth?: FirebaseAuthRoutingConfig;
84
+ }
85
+ export interface FirebaseAuthRoutingConfig {
86
+ /**
87
+ * Whether `intlMiddleware` should automatically run the firebase_auth
88
+ * redirect/session-refresh logic (guest→`redirectAuthPath`, signed-in→
89
+ * `homePath` on auth pages, ID-token refresh) for every request.
90
+ * Defaults to `true` — set `false` to keep `firebaseAuth` configured
91
+ * (e.g. for the client/server providers, actions) while driving the
92
+ * middleware redirect logic yourself instead.
93
+ */
94
+ middlewareEnabled?: boolean;
95
+ /** Firebase project's Web API key (`NEXT_PUBLIC_FIREBASE_API_KEY` equivalent). */
96
+ apiKey: string;
97
+ /** Firebase project's auth domain, e.g. "my-app.firebaseapp.com". */
98
+ authDomain: string;
99
+ /** Firebase project ID. */
100
+ projectId: string;
101
+ /** Firebase project's storage bucket. */
102
+ storageBucket?: string;
103
+ /** Firebase project's messaging sender ID. */
104
+ messagingSenderId?: string;
105
+ /** Firebase app ID. */
106
+ appId: string;
107
+ /** Firebase Analytics measurement ID. */
108
+ measurementId?: string;
109
+ /** Path to redirect signed-out users to, e.g. "/login". */
110
+ redirectAuthPath: string;
111
+ /** Path to redirect signed-in users away from auth pages to, e.g. "/". */
112
+ homePath: string;
113
+ /** Path to redirect unverified-email users to. Omit to skip email-verification redirects. */
114
+ verifyEmailPath?: string;
115
+ /** Returns true if the given (locale-stripped) path is an auth page (login/signup/etc). */
116
+ isAuthPath: (path: string) => boolean;
117
+ /** Locale-stripped paths exempt from all auth redirects (e.g. public marketing pages). */
118
+ whiteListPaths?: readonly string[];
119
+ /** Session cookie max-age in seconds. Defaults to 5 days (432000). */
120
+ sessionCookieMaxAge?: number;
121
+ /** Refresh-token cookie max-age in seconds. Defaults to 365 days (31536000). */
122
+ refreshTokenCookieMaxAge?: number;
76
123
  }
77
124
  export interface CookieAttributes {
78
125
  /**
@@ -185,7 +232,7 @@ export type TranslationEntry = string | TranslationObject;
185
232
  export interface TranslationObject {
186
233
  [key: string]: TranslationEntry;
187
234
  }
188
- export type ReturnType = any;
235
+ export type ReturnType = string;
189
236
  export type TranslatorReturnType = (key: string) => ReturnType;
190
237
  export type changeFrequency = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never' | undefined;
191
238
  export type Alternates = {
package/llms.txt ADDED
@@ -0,0 +1,48 @@
1
+ # cloudflare-next-intl
2
+
3
+ Optimized next-intl-style i18n for Next.js App Router, tuned for Cloudflare
4
+ Workers deployment (tree-shakeable, per-subpath exports instead of one big
5
+ barrel). Requires a `@intl-config` path alias pointing at a config file built
6
+ via `setIntlConfig` — see `src/config/README.md` / README "Setup" — before any
7
+ other subpath can be used.
8
+
9
+ ## Subpaths
10
+
11
+ - `.` — everything, re-exported (prefer the flat subpaths below for smaller bundles).
12
+ - `./client` — client-side barrel: `LocaleLink`, `usePathname`, `setCookieClient`, `getCookieClient`.
13
+ - `./server` — server-side barrel: `getMessage`, `getTranslations`, `getLocale`, `IntlProvider`, `Link`, `IntlHelperScript`, `getLocaleStaticParams`.
14
+ - `./middleware` — `intlMiddleware` for `middleware.ts`; locale detection/rewrite/redirect.
15
+ - `./setIntlConfig` — identity function for typed `RoutingConfig` authoring; use in your `@intl-config` file.
16
+ - `./serverProvider` — `IntlProvider` server component (also under `./server`).
17
+ - `./Link` — server-side locale-aware `<Link>` (also under `./server`).
18
+ - `./IntlHelperScript` — inline script for flash-free locale/theme hydration.
19
+ - `./LocaleLink` — client-side locale-aware link (counterpart to `./Link`).
20
+ - `./usePathname` — client hook: pathname with locale prefix stripped.
21
+ - `./metadata` — Next.js `generateMetadata` helper with locale alternates.
22
+ - `./setCookieClient` / `./getCookieClient` — small client cookie utilities (not used internally for locale routing).
23
+ - `./localeStaticParams` — `generateStaticParams` helper for locale segments.
24
+ - `./use` — `useLocale`/`useTranslations`; resolves to the RSC or client implementation automatically via the `react-server` export condition. Both throw `"... must be used within an IntlProvider"` if called outside one.
25
+ - `./ThemeSwitcher` — optional light/dark toggle component.
26
+
27
+ ## `firebaseAuth*` subpaths (require `firebaseAuth` set on your `RoutingConfig`)
28
+
29
+ - `./firebaseAuthClient` — lazy Firebase client SDK getter.
30
+ - `./firebaseAuthClientProvider` — `AuthUserProvider`: client auth-state provider (session cookie sync, auth-page redirects).
31
+ - `./firebaseAuthServerProvider` — server-side equivalent provider (not used by the default auto-wiring path — see its doc comment).
32
+ - `./useFirebaseAuthUser` — `useAuthUser()`; resolves to RSC or client implementation via the `react-server` condition. Client variant throws `"useAuthUser must be used within an AuthUserProvider"` if called outside one.
33
+ - `./getFirebaseAuthUser` — `getAuthUser()`; unconditional server-only export of the same RSC implementation `useFirebaseAuthUser` resolves to via `react-server`. Use this when you want `await` to be visible from the type itself — TypeScript doesn't evaluate the `react-server` condition, so `useFirebaseAuthUser` always types as its client (sync) signature in editors regardless of call site.
34
+ - `./firebaseAuthActions` — `createLoginAction`/`createSignUpAction`/`createForgotPasswordAction`: factories returning React `useActionState`-shaped form actions.
35
+ - `./firebaseAuthMiddleware` — `updateSession`: session-cookie refresh, called automatically by `./middleware`'s default handler.
36
+
37
+ ## Conventions
38
+
39
+ - Every exported function/component has a JSDoc comment with an `@example` where usage isn't obvious from the signature alone.
40
+ - Errors from missing-provider hooks name the hook and the required provider explicitly (e.g. `"useAuthUser must be used within an AuthUserProvider"`).
41
+ - One exported concept per file/subpath; `exports` in `package.json` is the source of truth for what's public — `src/**/index.ts` barrels are internal except `./client` and `./server`, which are themselves public subpaths.
42
+
43
+ ## Gotchas
44
+
45
+ - Missing `@intl-config` alias (see README "Setup") throws at module-load time, before any of your own code runs.
46
+ - Missing `firebaseAuth` on `RoutingConfig` throws immediately from any `firebaseAuth*` export, naming the missing config field.
47
+ - `useLocale`/`useTranslations` (from `./use`) and `useAuthUser` (from `./useFirebaseAuthUser`) resolve to a Server- or Client-Component implementation automatically via the `react-server` export condition — always import from the subpath, never from an internal file path.
48
+ - No unified `useFirebaseAuthUser` re-export inside `cloudflare-next-intl/firebaseAuth`'s barrel — that barrel can't replicate the `react-server` condition split, so import `cloudflare-next-intl/useFirebaseAuthUser` directly for the hook.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Optimized Next Intl Package Special for App Router and Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,7 +8,8 @@
8
8
  "files": [
9
9
  "dist",
10
10
  "LICENSE",
11
- "README.md"
11
+ "README.md",
12
+ "llms.txt"
12
13
  ],
13
14
  "exports": {
14
15
  ".": {
@@ -55,10 +56,6 @@
55
56
  "types": "./dist/src/general/metadata.d.ts",
56
57
  "import": "./dist/src/general/metadata.js"
57
58
  },
58
- "./getLayoutStates": {
59
- "types": "./dist/src/general/get_layout_states.d.ts",
60
- "import": "./dist/src/general/get_layout_states.js"
61
- },
62
59
  "./setCookieClient": {
63
60
  "types": "./dist/src/client/functions/set_cookie.d.ts",
64
61
  "import": "./dist/src/client/functions/set_cookie.js"
@@ -84,15 +81,50 @@
84
81
  "./ThemeSwitcher": {
85
82
  "types": "./dist/src/theme_switcher/components/theme_switcher.d.ts",
86
83
  "import": "./dist/src/theme_switcher/components/theme_switcher.js"
84
+ },
85
+ "./firebaseAuthClient": {
86
+ "types": "./dist/src/firebase_auth/client/firebase_client.d.ts",
87
+ "import": "./dist/src/firebase_auth/client/firebase_client.js"
88
+ },
89
+ "./firebaseAuthClientProvider": {
90
+ "types": "./dist/src/firebase_auth/client/auth_user_provider.d.ts",
91
+ "import": "./dist/src/firebase_auth/client/auth_user_provider.js"
92
+ },
93
+ "./firebaseAuthServerProvider": {
94
+ "types": "./dist/src/firebase_auth/server/auth_user_server_provider.d.ts",
95
+ "import": "./dist/src/firebase_auth/server/auth_user_server_provider.js"
96
+ },
97
+ "./useFirebaseAuthUser": {
98
+ "react-server": {
99
+ "types": "./dist/src/firebase_auth/server/use_auth_user_server.d.ts",
100
+ "import": "./dist/src/firebase_auth/server/use_auth_user_server.js"
101
+ },
102
+ "default": {
103
+ "types": "./dist/src/firebase_auth/client/use_auth_user.d.ts",
104
+ "import": "./dist/src/firebase_auth/client/use_auth_user.js"
105
+ }
106
+ },
107
+ "./getFirebaseAuthUser": {
108
+ "types": "./dist/src/firebase_auth/server/use_auth_user_server.d.ts",
109
+ "import": "./dist/src/firebase_auth/server/use_auth_user_server.js"
110
+ },
111
+ "./firebaseAuthActions": {
112
+ "types": "./dist/src/firebase_auth/client/auth_actions.d.ts",
113
+ "import": "./dist/src/firebase_auth/client/auth_actions.js"
114
+ },
115
+ "./firebaseAuthMiddleware": {
116
+ "types": "./dist/src/firebase_auth/middleware/update_session.d.ts",
117
+ "import": "./dist/src/firebase_auth/middleware/update_session.js"
87
118
  }
88
119
  },
89
120
  "scripts": {
90
- "test": "echo \"Error: no test specified\" && exit 1",
121
+ "test": "vitest run --coverage",
122
+ "bench": "vitest bench --run",
91
123
  "build": "tsc"
92
124
  },
93
125
  "repository": {
94
126
  "type": "git",
95
- "url": "git+https://github.com/DemienIlnutskiy/cloudflare-next-intl.git"
127
+ "url": "git+https://github.com/demian-ilnytskyi/cloudflare-next-intl.git"
96
128
  },
97
129
  "keywords": [
98
130
  "nextjs",
@@ -122,10 +154,11 @@
122
154
  "author": "Demian Ilnutskyi",
123
155
  "license": "MIT",
124
156
  "bugs": {
125
- "url": "https://github.com/DemienIlnutskiy/cloudflare-next-intl/issues"
157
+ "url": "https://github.com/demian-ilnytskyi/cloudflare-next-intl/issues"
126
158
  },
127
- "homepage": "https://github.com/DemienIlnutskiy/cloudflare-next-intl#readme",
159
+ "homepage": "https://github.com/demian-ilnytskyi/cloudflare-next-intl#readme",
128
160
  "peerDependencies": {
161
+ "firebase": ">=10.0.0",
129
162
  "next": ">=12.0.0",
130
163
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0",
131
164
  "typescript": ">=5.0.0"
@@ -133,17 +166,26 @@
133
166
  "peerDependenciesMeta": {
134
167
  "typescript": {
135
168
  "optional": true
169
+ },
170
+ "firebase": {
171
+ "optional": true
136
172
  }
137
173
  },
138
174
  "devDependencies": {
139
175
  "@eslint/eslintrc": "^3",
140
176
  "@eslint/js": "^9.27.0",
177
+ "@testing-library/dom": "^10.4.1",
178
+ "@testing-library/jest-dom": "^7.0.0",
179
+ "@testing-library/react": "^16.3.2",
141
180
  "@types/node": "^20.14.5",
142
181
  "@types/react": "^19.0.0",
143
182
  "@types/react-dom": "^19.0.0",
183
+ "@vitest/coverage-v8": "^3.2.7",
144
184
  "eslint": "^9.28.0",
145
185
  "eslint-config-next": "15.3.3",
146
186
  "eslint-config-prettier": "^10.1.2",
187
+ "firebase": "^12.17.0",
188
+ "jsdom": "^29.1.1",
147
189
  "next": "^15.3.0",
148
190
  "react": "^19.0.0",
149
191
  "react-dom": "^19.0.0",