next-i18next 16.0.2 → 16.0.3

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 CHANGED
@@ -242,9 +242,29 @@ For the no-locale-path mode (cookie-based), see `useChangeLanguage` [below](#no-
242
242
 
243
243
  ---
244
244
 
245
+ ## Hide Default Locale
246
+
247
+ If you want clean URLs for the default language while keeping locale prefixes for other languages, set `hideDefaultLocale: true`:
248
+
249
+ ```ts
250
+ const i18nConfig: I18nConfig = {
251
+ supportedLngs: ['en', 'de'],
252
+ fallbackLng: 'en',
253
+ hideDefaultLocale: true,
254
+ }
255
+ ```
256
+
257
+ In this mode:
258
+ - `/about` serves the default language (English) — no prefix needed
259
+ - `/de/about` serves German — non-default locales keep their prefix
260
+ - `/en/about` automatically redirects to `/about` (canonical clean URL)
261
+ - The `[lng]` folder structure stays the same — the proxy rewrites internally
262
+
263
+ ---
264
+
245
265
  ## No-Locale-Path Mode
246
266
 
247
- If you prefer clean URLs without a locale prefix (e.g., `/about` instead of `/en/about`), set `localeInPath: false`:
267
+ If you prefer clean URLs without a locale prefix for **all** languages (e.g., `/about` instead of `/en/about`), set `localeInPath: false`:
248
268
 
249
269
  ```ts
250
270
  const i18nConfig: I18nConfig = {
@@ -609,6 +629,7 @@ In **serverless environments** (Lambda, Vercel Serverless, etc.), the cache only
609
629
  | `defaultNS` | `'common'` | Default namespace |
610
630
  | `ns` | `[defaultNS]` | All known namespaces |
611
631
  | `localeInPath` | `true` | Include locale in URL path |
632
+ | `hideDefaultLocale` | `false` | When `true` (with `localeInPath: true`), the default language has no URL prefix |
612
633
  | `localePath` | `'/locales'` | Path to locale files relative to `/public` |
613
634
  | `localeStructure` | `'{{lng}}/{{ns}}'` | Locale file directory structure |
614
635
  | `localeExtension` | `'json'` | Locale file extension |
@@ -15,6 +15,7 @@ function normalizeConfig(userConfig) {
15
15
  defaultNS,
16
16
  ns: userConfig.ns ?? [defaultNS],
17
17
  localeInPath: userConfig.localeInPath ?? true,
18
+ hideDefaultLocale: userConfig.hideDefaultLocale ?? false,
18
19
  localePath: userConfig.localePath ?? "/locales",
19
20
  localeStructure: userConfig.localeStructure ?? "{{lng}}/{{ns}}",
20
21
  localeExtension: userConfig.localeExtension ?? "json",
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/appRouter/config.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n"],"mappings":";;AAEA,SAAgB,aAAa,QAAgC;AAC3D,QAAO;;AAGT,SAAgB,gBAAgB,YAA0C;CAExE,MAAM,gBAAgB,WAAW,iBAC/B,WAAW,MAAM,SAAS,QAAQ,MAAc,MAAM,UAAU,IAChE,CAAC,KAAK;CACR,MAAM,cAAc,WAAW,eAC7B,WAAW,MAAM,iBACjB,cAAc;AAEhB,KAAI,CAAC,YACH,OAAM,IAAI,MAAM,gEAAgE;AAElF,KAAI,cAAc,WAAW,EAC3B,OAAM,IAAI,MAAM,mFAAmF;CAGrG,MAAM,YAAY,WAAW,aAAa;AAE1C,QAAO;EACL;EACA;EACA;EACA,IAAI,WAAW,MAAM,CAAC,UAAU;EAChC,cAAc,WAAW,gBAAgB;EACzC,YAAY,WAAW,cAAc;EACrC,iBAAiB,WAAW,mBAAmB;EAC/C,iBAAiB,WAAW,mBAAmB;EAC/C,YAAY,WAAW,cAAc;EACrC,YAAY,WAAW,cAAc;EACrC,cAAc,WAAW,gBAAgB,MAAM,KAAK,KAAK;EACzD,cAAc,WAAW,gBAAgB;GAAC;GAAQ;GAAU;GAAU;EACtE,UAAU,WAAW;EACrB,WAAW,WAAW;EACtB,gBAAgB,WAAW;EAC3B,KAAK,WAAW,OAAO,EAAE;EACzB,gBAAiB,WAAW,kBAAkB,EAAE;EAChD,0BAA0B,WAAW,4BAA4B;EAEjE,MAAM,WAAW;EACjB,iBAAiB,WAAW;EAC5B,mBAAmB,WAAW;EAC/B"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/appRouter/config.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n hideDefaultLocale: userConfig.hideDefaultLocale ?? false,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n"],"mappings":";;AAEA,SAAgB,aAAa,QAAgC;AAC3D,QAAO;;AAGT,SAAgB,gBAAgB,YAA0C;CAExE,MAAM,gBAAgB,WAAW,iBAC/B,WAAW,MAAM,SAAS,QAAQ,MAAc,MAAM,UAAU,IAChE,CAAC,KAAK;CACR,MAAM,cAAc,WAAW,eAC7B,WAAW,MAAM,iBACjB,cAAc;AAEhB,KAAI,CAAC,YACH,OAAM,IAAI,MAAM,gEAAgE;AAElF,KAAI,cAAc,WAAW,EAC3B,OAAM,IAAI,MAAM,mFAAmF;CAGrG,MAAM,YAAY,WAAW,aAAa;AAE1C,QAAO;EACL;EACA;EACA;EACA,IAAI,WAAW,MAAM,CAAC,UAAU;EAChC,cAAc,WAAW,gBAAgB;EACzC,mBAAmB,WAAW,qBAAqB;EACnD,YAAY,WAAW,cAAc;EACrC,iBAAiB,WAAW,mBAAmB;EAC/C,iBAAiB,WAAW,mBAAmB;EAC/C,YAAY,WAAW,cAAc;EACrC,YAAY,WAAW,cAAc;EACrC,cAAc,WAAW,gBAAgB,MAAM,KAAK,KAAK;EACzD,cAAc,WAAW,gBAAgB;GAAC;GAAQ;GAAU;GAAU;EACtE,UAAU,WAAW;EACrB,WAAW,WAAW;EACtB,gBAAgB,WAAW;EAC3B,KAAK,WAAW,OAAO,EAAE;EACzB,gBAAiB,WAAW,kBAAkB,EAAE;EAChD,0BAA0B,WAAW,4BAA4B;EAEjE,MAAM,WAAW;EACjB,iBAAiB,WAAW;EAC5B,mBAAmB,WAAW;EAC/B"}
@@ -23,6 +23,10 @@ interface I18nConfig {
23
23
  resourceLoader?: ResourceLoader;
24
24
  /** Whether to include locale in URL path (defaults to true) */
25
25
  localeInPath?: boolean;
26
+ /** When true (and localeInPath is true), the default language has no URL prefix.
27
+ * e.g. `/about` serves the default language, `/de/about` serves German.
28
+ * Requests to the explicit default prefix (`/en/about`) are redirected to `/about`. */
29
+ hideDefaultLocale?: boolean;
26
30
  /** Cookie name for storing selected language (defaults to 'i18next') */
27
31
  cookieName?: string;
28
32
  /** Custom header name for passing language to server components (defaults to 'x-i18next-current-language') */
@@ -62,6 +66,7 @@ interface NormalizedConfig {
62
66
  defaultNS: string;
63
67
  ns: string[];
64
68
  localeInPath: boolean;
69
+ hideDefaultLocale: boolean;
65
70
  localePath: string;
66
71
  localeStructure: string;
67
72
  localeExtension: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/appRouter/types.ts","../../src/appRouter/config.ts"],"mappings":";;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;EAFL;EAIV,aAAA;;EAEA,WAAA;EAN4B;EAQ5B,SAAA;EARoE;EAUpE,EAAA;EAV2E;EAc3E,UAAA;EAZyB;EAczB,eAAA;EAIY;EAFZ,eAAA;EA0BsB;EAxBtB,SAAA,GAAY,QAAA;EAwBS;EAtBrB,cAAA,GAAiB,cAAA;EAlBjB;EAsBA,YAAA;EAlBA;EAsBA,UAAA;EAhBA;EAkBA,UAAA;EAdA;EAgBA,YAAA;EAdY;EAgBZ,YAAA;EAdiB;EAgBjB,QAAA;EARA;EAYA,GAAA;EARA;EAUA,cAAA,GAAiB,IAAA,CAAK,WAAA;EANtB;EAUA,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAFE;EAKJ,eAAA;EAAA;EAEA,iBAAA;EAEA;EAAA,wBAAA;AAAA;AAAA,UAGe,gBAAA;EACf,aAAA;EACA,WAAA;EACA,SAAA;EACA,EAAA;EACA,YAAA;EACA,UAAA;EACA,eAAA;EACA,eAAA;EACA,UAAA;EACA,UAAA;EACA,YAAA;EACA,YAAA;EACA,QAAA;EACA,SAAA,GAAY,QAAA;EACZ,cAAA,GAAiB,cAAA;EACjB,GAAA;EACA,cAAA,EAAgB,MAAA;EAChB,wBAAA;EAEA,IAAA,GAAO,UAAA;EACP,eAAA;EACA,iBAAA;AAAA;AAAA,KAGU,UAAA,YAAsB,aAAA,GAAgB,aAAA;EAChD,CAAA,EAAG,SAAA,CAAU,EAAA,EAAI,OAAA;EACjB,IAAA,EAAM,IAAA,EAXN;EAaA,GAAA;AAAA;;;iBChGc,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,UAAA;AAAA,iBAIlC,eAAA,CAAgB,UAAA,EAAY,UAAA,GAAa,gBAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/appRouter/types.ts","../../src/appRouter/config.ts"],"mappings":";;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;EAFL;EAIV,aAAA;;EAEA,WAAA;EAN4B;EAQ5B,SAAA;EARoE;EAUpE,EAAA;EAV2E;EAc3E,UAAA;EAZyB;EAczB,eAAA;EAIY;EAFZ,eAAA;EA8BsB;EA5BtB,SAAA,GAAY,QAAA;EA4BS;EA1BrB,cAAA,GAAiB,cAAA;EAlBjB;EAsBA,YAAA;EAlBA;;;EAsBA,iBAAA;EAZA;EAgBA,UAAA;EAdY;EAgBZ,UAAA;EAdiB;EAgBjB,YAAA;EARA;EAUA,YAAA;EAJA;EAMA,QAAA;EAFA;EAMA,GAAA;EAAA;EAEA,cAAA,GAAiB,IAAA,CAAK,WAAA;EAAL;EAIjB,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAKF;EAFA,eAAA;EAIwB;EAFxB,iBAAA;EAKe;EAHf,wBAAA;AAAA;AAAA,UAGe,gBAAA;EACf,aAAA;EACA,WAAA;EACA,SAAA;EACA,EAAA;EACA,YAAA;EACA,iBAAA;EACA,UAAA;EACA,eAAA;EACA,eAAA;EACA,UAAA;EACA,UAAA;EACA,YAAA;EACA,YAAA;EACA,QAAA;EACA,SAAA,GAAY,QAAA;EACZ,cAAA,GAAiB,cAAA;EACjB,GAAA;EACA,cAAA,EAAgB,MAAA;EAChB,wBAAA;EAEA,IAAA,GAAO,UAAA;EACP,eAAA;EACA,iBAAA;AAAA;AAAA,KAGU,UAAA,YAAsB,aAAA,GAAgB,aAAA;EAChD,CAAA,EAAG,SAAA,CAAU,EAAA,EAAI,OAAA;EACjB,IAAA,EAAM,IAAA,EATN;EAWA,GAAA;AAAA;;;iBCrGc,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,UAAA;AAAA,iBAIlC,eAAA,CAAgB,UAAA,EAAY,UAAA,GAAa,gBAAA"}
@@ -23,6 +23,10 @@ interface I18nConfig {
23
23
  resourceLoader?: ResourceLoader;
24
24
  /** Whether to include locale in URL path (defaults to true) */
25
25
  localeInPath?: boolean;
26
+ /** When true (and localeInPath is true), the default language has no URL prefix.
27
+ * e.g. `/about` serves the default language, `/de/about` serves German.
28
+ * Requests to the explicit default prefix (`/en/about`) are redirected to `/about`. */
29
+ hideDefaultLocale?: boolean;
26
30
  /** Cookie name for storing selected language (defaults to 'i18next') */
27
31
  cookieName?: string;
28
32
  /** Custom header name for passing language to server components (defaults to 'x-i18next-current-language') */
@@ -62,6 +66,7 @@ interface NormalizedConfig {
62
66
  defaultNS: string;
63
67
  ns: string[];
64
68
  localeInPath: boolean;
69
+ hideDefaultLocale: boolean;
65
70
  localePath: string;
66
71
  localeStructure: string;
67
72
  localeExtension: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/appRouter/types.ts","../../src/appRouter/config.ts"],"mappings":";;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;EAFL;EAIV,aAAA;;EAEA,WAAA;EAN4B;EAQ5B,SAAA;EARoE;EAUpE,EAAA;EAV2E;EAc3E,UAAA;EAZyB;EAczB,eAAA;EAIY;EAFZ,eAAA;EA0BsB;EAxBtB,SAAA,GAAY,QAAA;EAwBS;EAtBrB,cAAA,GAAiB,cAAA;EAlBjB;EAsBA,YAAA;EAlBA;EAsBA,UAAA;EAhBA;EAkBA,UAAA;EAdA;EAgBA,YAAA;EAdY;EAgBZ,YAAA;EAdiB;EAgBjB,QAAA;EARA;EAYA,GAAA;EARA;EAUA,cAAA,GAAiB,IAAA,CAAK,WAAA;EANtB;EAUA,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAFE;EAKJ,eAAA;EAAA;EAEA,iBAAA;EAEA;EAAA,wBAAA;AAAA;AAAA,UAGe,gBAAA;EACf,aAAA;EACA,WAAA;EACA,SAAA;EACA,EAAA;EACA,YAAA;EACA,UAAA;EACA,eAAA;EACA,eAAA;EACA,UAAA;EACA,UAAA;EACA,YAAA;EACA,YAAA;EACA,QAAA;EACA,SAAA,GAAY,QAAA;EACZ,cAAA,GAAiB,cAAA;EACjB,GAAA;EACA,cAAA,EAAgB,MAAA;EAChB,wBAAA;EAEA,IAAA,GAAO,UAAA;EACP,eAAA;EACA,iBAAA;AAAA;AAAA,KAGU,UAAA,YAAsB,aAAA,GAAgB,aAAA;EAChD,CAAA,EAAG,SAAA,CAAU,EAAA,EAAI,OAAA;EACjB,IAAA,EAAM,IAAA,EAXN;EAaA,GAAA;AAAA;;;iBChGc,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,UAAA;AAAA,iBAIlC,eAAA,CAAgB,UAAA,EAAY,UAAA,GAAa,gBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/appRouter/types.ts","../../src/appRouter/config.ts"],"mappings":";;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;EAFL;EAIV,aAAA;;EAEA,WAAA;EAN4B;EAQ5B,SAAA;EARoE;EAUpE,EAAA;EAV2E;EAc3E,UAAA;EAZyB;EAczB,eAAA;EAIY;EAFZ,eAAA;EA8BsB;EA5BtB,SAAA,GAAY,QAAA;EA4BS;EA1BrB,cAAA,GAAiB,cAAA;EAlBjB;EAsBA,YAAA;EAlBA;;;EAsBA,iBAAA;EAZA;EAgBA,UAAA;EAdY;EAgBZ,UAAA;EAdiB;EAgBjB,YAAA;EARA;EAUA,YAAA;EAJA;EAMA,QAAA;EAFA;EAMA,GAAA;EAAA;EAEA,cAAA,GAAiB,IAAA,CAAK,WAAA;EAAL;EAIjB,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAKF;EAFA,eAAA;EAIwB;EAFxB,iBAAA;EAKe;EAHf,wBAAA;AAAA;AAAA,UAGe,gBAAA;EACf,aAAA;EACA,WAAA;EACA,SAAA;EACA,EAAA;EACA,YAAA;EACA,iBAAA;EACA,UAAA;EACA,eAAA;EACA,eAAA;EACA,UAAA;EACA,UAAA;EACA,YAAA;EACA,YAAA;EACA,QAAA;EACA,SAAA,GAAY,QAAA;EACZ,cAAA,GAAiB,cAAA;EACjB,GAAA;EACA,cAAA,EAAgB,MAAA;EAChB,wBAAA;EAEA,IAAA,GAAO,UAAA;EACP,eAAA;EACA,iBAAA;AAAA;AAAA,KAGU,UAAA,YAAsB,aAAA,GAAgB,aAAA;EAChD,CAAA,EAAG,SAAA,CAAU,EAAA,EAAI,OAAA;EACjB,IAAA,EAAM,IAAA,EATN;EAWA,GAAA;AAAA;;;iBCrGc,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,UAAA;AAAA,iBAIlC,eAAA,CAAgB,UAAA,EAAY,UAAA,GAAa,gBAAA"}
@@ -14,6 +14,7 @@ function normalizeConfig(userConfig) {
14
14
  defaultNS,
15
15
  ns: userConfig.ns ?? [defaultNS],
16
16
  localeInPath: userConfig.localeInPath ?? true,
17
+ hideDefaultLocale: userConfig.hideDefaultLocale ?? false,
17
18
  localePath: userConfig.localePath ?? "/locales",
18
19
  localeStructure: userConfig.localeStructure ?? "{{lng}}/{{ns}}",
19
20
  localeExtension: userConfig.localeExtension ?? "json",
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/appRouter/config.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n"],"mappings":";AAEA,SAAgB,aAAa,QAAgC;AAC3D,QAAO;;AAGT,SAAgB,gBAAgB,YAA0C;CAExE,MAAM,gBAAgB,WAAW,iBAC/B,WAAW,MAAM,SAAS,QAAQ,MAAc,MAAM,UAAU,IAChE,CAAC,KAAK;CACR,MAAM,cAAc,WAAW,eAC7B,WAAW,MAAM,iBACjB,cAAc;AAEhB,KAAI,CAAC,YACH,OAAM,IAAI,MAAM,gEAAgE;AAElF,KAAI,cAAc,WAAW,EAC3B,OAAM,IAAI,MAAM,mFAAmF;CAGrG,MAAM,YAAY,WAAW,aAAa;AAE1C,QAAO;EACL;EACA;EACA;EACA,IAAI,WAAW,MAAM,CAAC,UAAU;EAChC,cAAc,WAAW,gBAAgB;EACzC,YAAY,WAAW,cAAc;EACrC,iBAAiB,WAAW,mBAAmB;EAC/C,iBAAiB,WAAW,mBAAmB;EAC/C,YAAY,WAAW,cAAc;EACrC,YAAY,WAAW,cAAc;EACrC,cAAc,WAAW,gBAAgB,MAAM,KAAK,KAAK;EACzD,cAAc,WAAW,gBAAgB;GAAC;GAAQ;GAAU;GAAU;EACtE,UAAU,WAAW;EACrB,WAAW,WAAW;EACtB,gBAAgB,WAAW;EAC3B,KAAK,WAAW,OAAO,EAAE;EACzB,gBAAiB,WAAW,kBAAkB,EAAE;EAChD,0BAA0B,WAAW,4BAA4B;EAEjE,MAAM,WAAW;EACjB,iBAAiB,WAAW;EAC5B,mBAAmB,WAAW;EAC/B"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/appRouter/config.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n hideDefaultLocale: userConfig.hideDefaultLocale ?? false,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n"],"mappings":";AAEA,SAAgB,aAAa,QAAgC;AAC3D,QAAO;;AAGT,SAAgB,gBAAgB,YAA0C;CAExE,MAAM,gBAAgB,WAAW,iBAC/B,WAAW,MAAM,SAAS,QAAQ,MAAc,MAAM,UAAU,IAChE,CAAC,KAAK;CACR,MAAM,cAAc,WAAW,eAC7B,WAAW,MAAM,iBACjB,cAAc;AAEhB,KAAI,CAAC,YACH,OAAM,IAAI,MAAM,gEAAgE;AAElF,KAAI,cAAc,WAAW,EAC3B,OAAM,IAAI,MAAM,mFAAmF;CAGrG,MAAM,YAAY,WAAW,aAAa;AAE1C,QAAO;EACL;EACA;EACA;EACA,IAAI,WAAW,MAAM,CAAC,UAAU;EAChC,cAAc,WAAW,gBAAgB;EACzC,mBAAmB,WAAW,qBAAqB;EACnD,YAAY,WAAW,cAAc;EACrC,iBAAiB,WAAW,mBAAmB;EAC/C,iBAAiB,WAAW,mBAAmB;EAC/C,YAAY,WAAW,cAAc;EACrC,YAAY,WAAW,cAAc;EACrC,cAAc,WAAW,gBAAgB,MAAM,KAAK,KAAK;EACzD,cAAc,WAAW,gBAAgB;GAAC;GAAQ;GAAU;GAAU;EACtE,UAAU,WAAW;EACrB,WAAW,WAAW;EACtB,gBAAgB,WAAW;EAC3B,KAAK,WAAW,OAAO,EAAE;EACzB,gBAAiB,WAAW,kBAAkB,EAAE;EAChD,0BAA0B,WAAW,4BAA4B;EAEjE,MAAM,WAAW;EACjB,iBAAiB,WAAW;EAC5B,mBAAmB,WAAW;EAC/B"}
@@ -16,6 +16,7 @@ function normalizeConfig(userConfig) {
16
16
  defaultNS,
17
17
  ns: userConfig.ns ?? [defaultNS],
18
18
  localeInPath: userConfig.localeInPath ?? true,
19
+ hideDefaultLocale: userConfig.hideDefaultLocale ?? false,
19
20
  localePath: userConfig.localePath ?? "/locales",
20
21
  localeStructure: userConfig.localeStructure ?? "{{lng}}/{{ns}}",
21
22
  localeExtension: userConfig.localeExtension ?? "json",
@@ -108,11 +109,33 @@ function createProxy(userConfig) {
108
109
  if (!lng) lng = config.fallbackLng;
109
110
  const lngInPath = findLocaleInPath(basePath ? pathname.slice(basePath.length) || "/" : pathname, config.supportedLngs, nonExplicit);
110
111
  if (config.localeInPath) {
112
+ const prefix = basePath ?? "";
113
+ const pathAfterBase = basePath ? pathname.slice(basePath.length) : pathname;
114
+ if (config.hideDefaultLocale && lngInPath === config.fallbackLng) {
115
+ const pathWithoutLocale = pathAfterBase.replace(/^\/[^/]+/, "") || "/";
116
+ const redirectUrl = new URL(`${prefix}${pathWithoutLocale}${search}`, req.url);
117
+ const response = next_server.NextResponse.redirect(redirectUrl);
118
+ response.cookies.set(config.cookieName, config.fallbackLng, {
119
+ path: "/",
120
+ maxAge: config.cookieMaxAge,
121
+ sameSite: "lax"
122
+ });
123
+ return response;
124
+ }
111
125
  const headers = new Headers(req.headers);
112
126
  headers.set(config.headerName, lngInPath || lng);
113
127
  if (!lngInPath) {
114
- const prefix = basePath ?? "";
115
- const pathAfterBase = basePath ? pathname.slice(basePath.length) : pathname;
128
+ if (config.hideDefaultLocale) {
129
+ const rewriteUrl = new URL(`${prefix}/${config.fallbackLng}${pathAfterBase}${search}`, req.url);
130
+ headers.set(config.headerName, config.fallbackLng);
131
+ const response = next_server.NextResponse.rewrite(rewriteUrl, { request: { headers } });
132
+ response.cookies.set(config.cookieName, config.fallbackLng, {
133
+ path: "/",
134
+ maxAge: config.cookieMaxAge,
135
+ sameSite: "lax"
136
+ });
137
+ return response;
138
+ }
116
139
  const redirectUrl = new URL(`${prefix}/${lng}${pathAfterBase}${search}`, req.url);
117
140
  const response = next_server.NextResponse.redirect(redirectUrl);
118
141
  response.cookies.set(config.cookieName, lng, {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["NextResponse"],"sources":["../../../src/appRouter/config.ts","../../../src/appRouter/proxy/languageDetector.ts","../../../src/appRouter/proxy/index.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n","/**\n * Edge-safe Accept-Language parser and language matcher.\n * No external dependencies — runs in Edge Runtime, Node.js, and browser.\n */\n\nexport function parseAcceptLanguage(header: string | null | undefined): string[] {\n if (!header) return []\n return header\n .split(',')\n .map(part => {\n const [lang, qPart] = part.trim().split(';')\n const q = qPart?.trim().startsWith('q=')\n ? parseFloat(qPart.trim().slice(2))\n : 1.0\n return { lang: lang.trim(), q: isNaN(q) ? 0 : q }\n })\n .filter(item => item.lang && item.q > 0)\n .sort((a, b) => b.q - a.q)\n .map(item => item.lang)\n}\n\n/**\n * Find a matching supported language for a given code, respecting nonExplicitSupportedLngs.\n *\n * Matching order (mirrors i18next's LanguageUtils.getBestMatchFromCodes):\n * 1. Exact match (case-insensitive)\n * 2. Preferred prefix → supported base (e.g. preferred 'en-US' matches supported 'en')\n * 3. If nonExplicitSupportedLngs: preferred base → supported region\n * (e.g. preferred 'en' matches supported 'en-US')\n */\nexport function findSupportedMatch(\n code: string,\n supportedLanguages: readonly string[],\n nonExplicitSupportedLngs: boolean,\n): string | undefined {\n const lower = code.toLowerCase()\n\n // 1. Exact match (case-insensitive)\n const exact = supportedLanguages.find(l => l.toLowerCase() === lower)\n if (exact) return exact\n\n // 2. Preferred prefix → supported base (e.g. 'en-US' → 'en')\n const prefix = lower.split('-')[0]\n const partial = supportedLanguages.find(l => l.toLowerCase() === prefix)\n if (partial) return partial\n\n // 3. Reverse match: preferred base → supported region (e.g. 'en' → 'en-US')\n if (nonExplicitSupportedLngs) {\n const reverse = supportedLanguages.find(\n l => l.toLowerCase().split('-')[0] === prefix\n )\n if (reverse) return reverse\n }\n\n return undefined\n}\n\nexport function matchLanguage(\n acceptLanguages: string[],\n supportedLanguages: readonly string[],\n defaultLanguage: string,\n nonExplicitSupportedLngs = false,\n): string {\n for (const preferred of acceptLanguages) {\n const match = findSupportedMatch(preferred, supportedLanguages, nonExplicitSupportedLngs)\n if (match) return match\n }\n return defaultLanguage\n}\n","import { NextRequest, NextResponse } from 'next/server'\nimport type { I18nConfig } from '../types'\nimport { normalizeConfig } from '../config'\nimport { parseAcceptLanguage, matchLanguage, findSupportedMatch } from './languageDetector'\n\n// Re-export config utilities for Edge-safe usage (no react-i18next dependency)\nexport { defineConfig, normalizeConfig } from '../config'\nexport type { I18nConfig, NormalizedConfig, ResourceLoader } from '../types'\n\nfunction findLocaleInPath(\n pathname: string,\n supportedLngs: readonly string[],\n nonExplicitSupportedLngs: boolean,\n): string | undefined {\n // Extract the first path segment\n const match = pathname.match(/^\\/([^/]+)/)\n if (!match) return undefined\n return findSupportedMatch(match[1], supportedLngs, nonExplicitSupportedLngs)\n}\n\nexport function createProxy(userConfig: I18nConfig) {\n const config = normalizeConfig(userConfig)\n const nonExplicit = config.nonExplicitSupportedLngs\n // Normalize basePath: ensure leading slash, strip trailing slash\n const basePath = config.basePath\n ? ('/' + config.basePath.replace(/^\\/+/, '').replace(/\\/+$/, ''))\n : undefined\n\n return function middleware(req: NextRequest): NextResponse {\n const { pathname, search } = req.nextUrl\n\n // When basePath is set, only handle requests under that prefix\n if (basePath) {\n if (pathname !== basePath && !pathname.startsWith(basePath + '/')) {\n return NextResponse.next()\n }\n }\n\n // Skip ignored paths\n for (const ignored of config.ignoredPaths) {\n if (pathname.startsWith(ignored)) {\n return NextResponse.next()\n }\n }\n\n // Skip common static file extensions\n if (/\\.(ico|png|jpg|jpeg|svg|gif|webp|css|js|map|woff2?|ttf|eot)$/.test(pathname)) {\n return NextResponse.next()\n }\n\n // Detect language from cookie, then Accept-Language header, then default\n let lng: string | undefined\n const cookieValue = req.cookies.get(config.cookieName)?.value\n if (cookieValue) {\n lng = matchLanguage([cookieValue], config.supportedLngs, config.fallbackLng, nonExplicit)\n }\n if (!lng) {\n lng = matchLanguage(\n parseAcceptLanguage(req.headers.get('Accept-Language')),\n config.supportedLngs,\n config.fallbackLng,\n nonExplicit,\n )\n }\n if (!lng) {\n lng = config.fallbackLng\n }\n\n // For locale-in-path detection, strip basePath prefix so we look at the right segment\n const pathForLocale = basePath ? pathname.slice(basePath.length) || '/' : pathname\n const lngInPath = findLocaleInPath(pathForLocale, config.supportedLngs, nonExplicit)\n\n if (config.localeInPath) {\n // Set custom header for server components to read\n const headers = new Headers(req.headers)\n headers.set(config.headerName, lngInPath || lng)\n\n // Redirect if no locale in path\n if (!lngInPath) {\n const prefix = basePath ?? ''\n const pathAfterBase = basePath ? pathname.slice(basePath.length) : pathname\n const redirectUrl = new URL(`${prefix}/${lng}${pathAfterBase}${search}`, req.url)\n const response = NextResponse.redirect(redirectUrl)\n response.cookies.set(config.cookieName, lng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n // Persist language from referer URL into cookie\n const response = NextResponse.next({ request: { headers } })\n if (req.headers.has('referer')) {\n const refererUrl = new URL(req.headers.get('referer')!)\n const refererPathForLocale = basePath\n ? refererUrl.pathname.slice(basePath.length) || '/'\n : refererUrl.pathname\n const lngInReferer = findLocaleInPath(refererPathForLocale, config.supportedLngs, nonExplicit)\n if (lngInReferer) {\n response.cookies.set(config.cookieName, lngInReferer, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n }\n }\n\n return response\n } else {\n // No-locale-path mode: don't redirect, just set the header\n const headers = new Headers(req.headers)\n headers.set(config.headerName, lng)\n\n const response = NextResponse.next({ request: { headers } })\n return response\n }\n }\n}\n\n/**\n * Backwards-compatible alias for createProxy.\n * Use `createProxy` for new projects with Next.js 16+ `proxy.ts`.\n */\nexport const createMiddleware = createProxy\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["NextResponse"],"sources":["../../../src/appRouter/config.ts","../../../src/appRouter/proxy/languageDetector.ts","../../../src/appRouter/proxy/index.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n hideDefaultLocale: userConfig.hideDefaultLocale ?? false,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n","/**\n * Edge-safe Accept-Language parser and language matcher.\n * No external dependencies — runs in Edge Runtime, Node.js, and browser.\n */\n\nexport function parseAcceptLanguage(header: string | null | undefined): string[] {\n if (!header) return []\n return header\n .split(',')\n .map(part => {\n const [lang, qPart] = part.trim().split(';')\n const q = qPart?.trim().startsWith('q=')\n ? parseFloat(qPart.trim().slice(2))\n : 1.0\n return { lang: lang.trim(), q: isNaN(q) ? 0 : q }\n })\n .filter(item => item.lang && item.q > 0)\n .sort((a, b) => b.q - a.q)\n .map(item => item.lang)\n}\n\n/**\n * Find a matching supported language for a given code, respecting nonExplicitSupportedLngs.\n *\n * Matching order (mirrors i18next's LanguageUtils.getBestMatchFromCodes):\n * 1. Exact match (case-insensitive)\n * 2. Preferred prefix → supported base (e.g. preferred 'en-US' matches supported 'en')\n * 3. If nonExplicitSupportedLngs: preferred base → supported region\n * (e.g. preferred 'en' matches supported 'en-US')\n */\nexport function findSupportedMatch(\n code: string,\n supportedLanguages: readonly string[],\n nonExplicitSupportedLngs: boolean,\n): string | undefined {\n const lower = code.toLowerCase()\n\n // 1. Exact match (case-insensitive)\n const exact = supportedLanguages.find(l => l.toLowerCase() === lower)\n if (exact) return exact\n\n // 2. Preferred prefix → supported base (e.g. 'en-US' → 'en')\n const prefix = lower.split('-')[0]\n const partial = supportedLanguages.find(l => l.toLowerCase() === prefix)\n if (partial) return partial\n\n // 3. Reverse match: preferred base → supported region (e.g. 'en' → 'en-US')\n if (nonExplicitSupportedLngs) {\n const reverse = supportedLanguages.find(\n l => l.toLowerCase().split('-')[0] === prefix\n )\n if (reverse) return reverse\n }\n\n return undefined\n}\n\nexport function matchLanguage(\n acceptLanguages: string[],\n supportedLanguages: readonly string[],\n defaultLanguage: string,\n nonExplicitSupportedLngs = false,\n): string {\n for (const preferred of acceptLanguages) {\n const match = findSupportedMatch(preferred, supportedLanguages, nonExplicitSupportedLngs)\n if (match) return match\n }\n return defaultLanguage\n}\n","import { NextRequest, NextResponse } from 'next/server'\nimport type { I18nConfig } from '../types'\nimport { normalizeConfig } from '../config'\nimport { parseAcceptLanguage, matchLanguage, findSupportedMatch } from './languageDetector'\n\n// Re-export config utilities for Edge-safe usage (no react-i18next dependency)\nexport { defineConfig, normalizeConfig } from '../config'\nexport type { I18nConfig, NormalizedConfig, ResourceLoader } from '../types'\n\nfunction findLocaleInPath(\n pathname: string,\n supportedLngs: readonly string[],\n nonExplicitSupportedLngs: boolean,\n): string | undefined {\n // Extract the first path segment\n const match = pathname.match(/^\\/([^/]+)/)\n if (!match) return undefined\n return findSupportedMatch(match[1], supportedLngs, nonExplicitSupportedLngs)\n}\n\nexport function createProxy(userConfig: I18nConfig) {\n const config = normalizeConfig(userConfig)\n const nonExplicit = config.nonExplicitSupportedLngs\n // Normalize basePath: ensure leading slash, strip trailing slash\n const basePath = config.basePath\n ? ('/' + config.basePath.replace(/^\\/+/, '').replace(/\\/+$/, ''))\n : undefined\n\n return function middleware(req: NextRequest): NextResponse {\n const { pathname, search } = req.nextUrl\n\n // When basePath is set, only handle requests under that prefix\n if (basePath) {\n if (pathname !== basePath && !pathname.startsWith(basePath + '/')) {\n return NextResponse.next()\n }\n }\n\n // Skip ignored paths\n for (const ignored of config.ignoredPaths) {\n if (pathname.startsWith(ignored)) {\n return NextResponse.next()\n }\n }\n\n // Skip common static file extensions\n if (/\\.(ico|png|jpg|jpeg|svg|gif|webp|css|js|map|woff2?|ttf|eot)$/.test(pathname)) {\n return NextResponse.next()\n }\n\n // Detect language from cookie, then Accept-Language header, then default\n let lng: string | undefined\n const cookieValue = req.cookies.get(config.cookieName)?.value\n if (cookieValue) {\n lng = matchLanguage([cookieValue], config.supportedLngs, config.fallbackLng, nonExplicit)\n }\n if (!lng) {\n lng = matchLanguage(\n parseAcceptLanguage(req.headers.get('Accept-Language')),\n config.supportedLngs,\n config.fallbackLng,\n nonExplicit,\n )\n }\n if (!lng) {\n lng = config.fallbackLng\n }\n\n // For locale-in-path detection, strip basePath prefix so we look at the right segment\n const pathForLocale = basePath ? pathname.slice(basePath.length) || '/' : pathname\n const lngInPath = findLocaleInPath(pathForLocale, config.supportedLngs, nonExplicit)\n\n if (config.localeInPath) {\n const prefix = basePath ?? ''\n const pathAfterBase = basePath ? pathname.slice(basePath.length) : pathname\n\n // hideDefaultLocale: redirect explicit default-locale paths to the clean URL\n if (config.hideDefaultLocale && lngInPath === config.fallbackLng) {\n const pathWithoutLocale = pathAfterBase.replace(/^\\/[^/]+/, '') || '/'\n const redirectUrl = new URL(`${prefix}${pathWithoutLocale}${search}`, req.url)\n const response = NextResponse.redirect(redirectUrl)\n response.cookies.set(config.cookieName, config.fallbackLng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n // Set custom header for server components to read\n const headers = new Headers(req.headers)\n headers.set(config.headerName, lngInPath || lng)\n\n // Redirect if no locale in path\n if (!lngInPath) {\n if (config.hideDefaultLocale) {\n // Rewrite internally to the default-locale path, keeping the clean URL\n const rewriteUrl = new URL(`${prefix}/${config.fallbackLng}${pathAfterBase}${search}`, req.url)\n headers.set(config.headerName, config.fallbackLng)\n const response = NextResponse.rewrite(rewriteUrl, { request: { headers } })\n response.cookies.set(config.cookieName, config.fallbackLng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n const redirectUrl = new URL(`${prefix}/${lng}${pathAfterBase}${search}`, req.url)\n const response = NextResponse.redirect(redirectUrl)\n response.cookies.set(config.cookieName, lng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n // Persist language from referer URL into cookie\n const response = NextResponse.next({ request: { headers } })\n if (req.headers.has('referer')) {\n const refererUrl = new URL(req.headers.get('referer')!)\n const refererPathForLocale = basePath\n ? refererUrl.pathname.slice(basePath.length) || '/'\n : refererUrl.pathname\n const lngInReferer = findLocaleInPath(refererPathForLocale, config.supportedLngs, nonExplicit)\n if (lngInReferer) {\n response.cookies.set(config.cookieName, lngInReferer, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n }\n }\n\n return response\n } else {\n // No-locale-path mode: don't redirect, just set the header\n const headers = new Headers(req.headers)\n headers.set(config.headerName, lng)\n\n const response = NextResponse.next({ request: { headers } })\n return response\n }\n }\n}\n\n/**\n * Backwards-compatible alias for createProxy.\n * Use `createProxy` for new projects with Next.js 16+ `proxy.ts`.\n */\nexport const createMiddleware = createProxy\n"],"mappings":"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"}
@@ -24,6 +24,10 @@ interface I18nConfig {
24
24
  resourceLoader?: ResourceLoader;
25
25
  /** Whether to include locale in URL path (defaults to true) */
26
26
  localeInPath?: boolean;
27
+ /** When true (and localeInPath is true), the default language has no URL prefix.
28
+ * e.g. `/about` serves the default language, `/de/about` serves German.
29
+ * Requests to the explicit default prefix (`/en/about`) are redirected to `/about`. */
30
+ hideDefaultLocale?: boolean;
27
31
  /** Cookie name for storing selected language (defaults to 'i18next') */
28
32
  cookieName?: string;
29
33
  /** Custom header name for passing language to server components (defaults to 'x-i18next-current-language') */
@@ -63,6 +67,7 @@ interface NormalizedConfig {
63
67
  defaultNS: string;
64
68
  ns: string[];
65
69
  localeInPath: boolean;
70
+ hideDefaultLocale: boolean;
66
71
  localePath: string;
67
72
  localeStructure: string;
68
73
  localeExtension: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/appRouter/types.ts","../../../src/appRouter/config.ts","../../../src/appRouter/proxy/index.ts"],"mappings":";;;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;;EAEf,aAAA;EAJwB;EAMxB,WAAA;EAN2E;EAQ3E,SAAA;EAR8C;EAU9C,EAAA;EAV2E;EAc3E,UAAA;EAZe;EAcf,eAAA;;EAEA,eAAA;EAIiB;EAFjB,SAAA,GAAY,QAAA;EAwBK;EAtBjB,cAAA,GAAiB,cAAA;EAsBI;EAlBrB,YAAA;EApBA;EAwBA,UAAA;EApBA;EAsBA,UAAA;EAhBA;EAkBA,YAAA;EAdA;EAgBA,YAAA;EAdA;EAgBA,QAAA;EAZA;EAgBA,GAAA;EAVA;EAYA,cAAA,GAAiB,IAAA,CAAK,WAAA;EARtB;EAYA,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAHE;EAMJ,eAAA;EAHE;EAKF,iBAAA;EAAA;EAEA,wBAAA;AAAA;AAAA,UAGe,gBAAA;EACf,aAAA;EACA,WAAA;EACA,SAAA;EACA,EAAA;EACA,YAAA;EACA,UAAA;EACA,eAAA;EACA,eAAA;EACA,UAAA;EACA,UAAA;EACA,YAAA;EACA,YAAA;EACA,QAAA;EACA,SAAA,GAAY,QAAA;EACZ,cAAA,GAAiB,cAAA;EACjB,GAAA;EACA,cAAA,EAAgB,MAAA;EAChB,wBAAA;EAEA,IAAA,GAAO,UAAA;EACP,eAAA;EACA,iBAAA;AAAA;;;iBCzFc,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,UAAA;AAAA,iBAIlC,eAAA,CAAgB,UAAA,EAAY,UAAA,GAAa,gBAAA;;;iBCczC,WAAA,CAAY,UAAA,EAAY,UAAA,IAQX,GAAA,EAAK,WAAA,KAAc,YAAA;;;;;cAgGnC,gBAAA,SAAgB,WAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/appRouter/types.ts","../../../src/appRouter/config.ts","../../../src/appRouter/proxy/index.ts"],"mappings":";;;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;;EAEf,aAAA;EAJwB;EAMxB,WAAA;EAN2E;EAQ3E,SAAA;EAR8C;EAU9C,EAAA;EAV2E;EAc3E,UAAA;EAZe;EAcf,eAAA;;EAEA,eAAA;EAIiB;EAFjB,SAAA,GAAY,QAAA;EA4BK;EA1BjB,cAAA,GAAiB,cAAA;EA0BI;EAtBrB,YAAA;EApBA;;;EAwBA,iBAAA;EAdA;EAkBA,UAAA;EAdA;EAgBA,UAAA;EAdA;EAgBA,YAAA;EAZA;EAcA,YAAA;EANA;EAQA,QAAA;EAJA;EAQA,GAAA;EAJA;EAMA,cAAA,GAAiB,IAAA,CAAK,WAAA;EAAtB;EAIA,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAGF;EAAA,eAAA;EAIA;EAFA,iBAAA;EAEwB;EAAxB,wBAAA;AAAA;AAAA,UAGe,gBAAA;EACf,aAAA;EACA,WAAA;EACA,SAAA;EACA,EAAA;EACA,YAAA;EACA,iBAAA;EACA,UAAA;EACA,eAAA;EACA,eAAA;EACA,UAAA;EACA,UAAA;EACA,YAAA;EACA,YAAA;EACA,QAAA;EACA,SAAA,GAAY,QAAA;EACZ,cAAA,GAAiB,cAAA;EACjB,GAAA;EACA,cAAA,EAAgB,MAAA;EAChB,wBAAA;EAEA,IAAA,GAAO,UAAA;EACP,eAAA;EACA,iBAAA;AAAA;;;iBC9Fc,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,UAAA;AAAA,iBAIlC,eAAA,CAAgB,UAAA,EAAY,UAAA,GAAa,gBAAA;;;iBCczC,WAAA,CAAY,UAAA,EAAY,UAAA,IAQX,GAAA,EAAK,WAAA,KAAc,YAAA;;;;;cA2HnC,gBAAA,SAAgB,WAAA"}
@@ -24,6 +24,10 @@ interface I18nConfig {
24
24
  resourceLoader?: ResourceLoader;
25
25
  /** Whether to include locale in URL path (defaults to true) */
26
26
  localeInPath?: boolean;
27
+ /** When true (and localeInPath is true), the default language has no URL prefix.
28
+ * e.g. `/about` serves the default language, `/de/about` serves German.
29
+ * Requests to the explicit default prefix (`/en/about`) are redirected to `/about`. */
30
+ hideDefaultLocale?: boolean;
27
31
  /** Cookie name for storing selected language (defaults to 'i18next') */
28
32
  cookieName?: string;
29
33
  /** Custom header name for passing language to server components (defaults to 'x-i18next-current-language') */
@@ -63,6 +67,7 @@ interface NormalizedConfig {
63
67
  defaultNS: string;
64
68
  ns: string[];
65
69
  localeInPath: boolean;
70
+ hideDefaultLocale: boolean;
66
71
  localePath: string;
67
72
  localeStructure: string;
68
73
  localeExtension: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/appRouter/types.ts","../../../src/appRouter/config.ts","../../../src/appRouter/proxy/index.ts"],"mappings":";;;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;;EAEf,aAAA;EAJwB;EAMxB,WAAA;EAN2E;EAQ3E,SAAA;EAR8C;EAU9C,EAAA;EAV2E;EAc3E,UAAA;EAZe;EAcf,eAAA;;EAEA,eAAA;EAIiB;EAFjB,SAAA,GAAY,QAAA;EAwBK;EAtBjB,cAAA,GAAiB,cAAA;EAsBI;EAlBrB,YAAA;EApBA;EAwBA,UAAA;EApBA;EAsBA,UAAA;EAhBA;EAkBA,YAAA;EAdA;EAgBA,YAAA;EAdA;EAgBA,QAAA;EAZA;EAgBA,GAAA;EAVA;EAYA,cAAA,GAAiB,IAAA,CAAK,WAAA;EARtB;EAYA,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAHE;EAMJ,eAAA;EAHE;EAKF,iBAAA;EAAA;EAEA,wBAAA;AAAA;AAAA,UAGe,gBAAA;EACf,aAAA;EACA,WAAA;EACA,SAAA;EACA,EAAA;EACA,YAAA;EACA,UAAA;EACA,eAAA;EACA,eAAA;EACA,UAAA;EACA,UAAA;EACA,YAAA;EACA,YAAA;EACA,QAAA;EACA,SAAA,GAAY,QAAA;EACZ,cAAA,GAAiB,cAAA;EACjB,GAAA;EACA,cAAA,EAAgB,MAAA;EAChB,wBAAA;EAEA,IAAA,GAAO,UAAA;EACP,eAAA;EACA,iBAAA;AAAA;;;iBCzFc,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,UAAA;AAAA,iBAIlC,eAAA,CAAgB,UAAA,EAAY,UAAA,GAAa,gBAAA;;;iBCczC,WAAA,CAAY,UAAA,EAAY,UAAA,IAQX,GAAA,EAAK,WAAA,KAAc,YAAA;;;;;cAgGnC,gBAAA,SAAgB,WAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/appRouter/types.ts","../../../src/appRouter/config.ts","../../../src/appRouter/proxy/index.ts"],"mappings":";;;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;;EAEf,aAAA;EAJwB;EAMxB,WAAA;EAN2E;EAQ3E,SAAA;EAR8C;EAU9C,EAAA;EAV2E;EAc3E,UAAA;EAZe;EAcf,eAAA;;EAEA,eAAA;EAIiB;EAFjB,SAAA,GAAY,QAAA;EA4BK;EA1BjB,cAAA,GAAiB,cAAA;EA0BI;EAtBrB,YAAA;EApBA;;;EAwBA,iBAAA;EAdA;EAkBA,UAAA;EAdA;EAgBA,UAAA;EAdA;EAgBA,YAAA;EAZA;EAcA,YAAA;EANA;EAQA,QAAA;EAJA;EAQA,GAAA;EAJA;EAMA,cAAA,GAAiB,IAAA,CAAK,WAAA;EAAtB;EAIA,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAGF;EAAA,eAAA;EAIA;EAFA,iBAAA;EAEwB;EAAxB,wBAAA;AAAA;AAAA,UAGe,gBAAA;EACf,aAAA;EACA,WAAA;EACA,SAAA;EACA,EAAA;EACA,YAAA;EACA,iBAAA;EACA,UAAA;EACA,eAAA;EACA,eAAA;EACA,UAAA;EACA,UAAA;EACA,YAAA;EACA,YAAA;EACA,QAAA;EACA,SAAA,GAAY,QAAA;EACZ,cAAA,GAAiB,cAAA;EACjB,GAAA;EACA,cAAA,EAAgB,MAAA;EAChB,wBAAA;EAEA,IAAA,GAAO,UAAA;EACP,eAAA;EACA,iBAAA;AAAA;;;iBC9Fc,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,UAAA;AAAA,iBAIlC,eAAA,CAAgB,UAAA,EAAY,UAAA,GAAa,gBAAA;;;iBCczC,WAAA,CAAY,UAAA,EAAY,UAAA,IAQX,GAAA,EAAK,WAAA,KAAc,YAAA;;;;;cA2HnC,gBAAA,SAAgB,WAAA"}
@@ -15,6 +15,7 @@ function normalizeConfig(userConfig) {
15
15
  defaultNS,
16
16
  ns: userConfig.ns ?? [defaultNS],
17
17
  localeInPath: userConfig.localeInPath ?? true,
18
+ hideDefaultLocale: userConfig.hideDefaultLocale ?? false,
18
19
  localePath: userConfig.localePath ?? "/locales",
19
20
  localeStructure: userConfig.localeStructure ?? "{{lng}}/{{ns}}",
20
21
  localeExtension: userConfig.localeExtension ?? "json",
@@ -107,11 +108,33 @@ function createProxy(userConfig) {
107
108
  if (!lng) lng = config.fallbackLng;
108
109
  const lngInPath = findLocaleInPath(basePath ? pathname.slice(basePath.length) || "/" : pathname, config.supportedLngs, nonExplicit);
109
110
  if (config.localeInPath) {
111
+ const prefix = basePath ?? "";
112
+ const pathAfterBase = basePath ? pathname.slice(basePath.length) : pathname;
113
+ if (config.hideDefaultLocale && lngInPath === config.fallbackLng) {
114
+ const pathWithoutLocale = pathAfterBase.replace(/^\/[^/]+/, "") || "/";
115
+ const redirectUrl = new URL(`${prefix}${pathWithoutLocale}${search}`, req.url);
116
+ const response = NextResponse.redirect(redirectUrl);
117
+ response.cookies.set(config.cookieName, config.fallbackLng, {
118
+ path: "/",
119
+ maxAge: config.cookieMaxAge,
120
+ sameSite: "lax"
121
+ });
122
+ return response;
123
+ }
110
124
  const headers = new Headers(req.headers);
111
125
  headers.set(config.headerName, lngInPath || lng);
112
126
  if (!lngInPath) {
113
- const prefix = basePath ?? "";
114
- const pathAfterBase = basePath ? pathname.slice(basePath.length) : pathname;
127
+ if (config.hideDefaultLocale) {
128
+ const rewriteUrl = new URL(`${prefix}/${config.fallbackLng}${pathAfterBase}${search}`, req.url);
129
+ headers.set(config.headerName, config.fallbackLng);
130
+ const response = NextResponse.rewrite(rewriteUrl, { request: { headers } });
131
+ response.cookies.set(config.cookieName, config.fallbackLng, {
132
+ path: "/",
133
+ maxAge: config.cookieMaxAge,
134
+ sameSite: "lax"
135
+ });
136
+ return response;
137
+ }
115
138
  const redirectUrl = new URL(`${prefix}/${lng}${pathAfterBase}${search}`, req.url);
116
139
  const response = NextResponse.redirect(redirectUrl);
117
140
  response.cookies.set(config.cookieName, lng, {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/appRouter/config.ts","../../../src/appRouter/proxy/languageDetector.ts","../../../src/appRouter/proxy/index.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n","/**\n * Edge-safe Accept-Language parser and language matcher.\n * No external dependencies — runs in Edge Runtime, Node.js, and browser.\n */\n\nexport function parseAcceptLanguage(header: string | null | undefined): string[] {\n if (!header) return []\n return header\n .split(',')\n .map(part => {\n const [lang, qPart] = part.trim().split(';')\n const q = qPart?.trim().startsWith('q=')\n ? parseFloat(qPart.trim().slice(2))\n : 1.0\n return { lang: lang.trim(), q: isNaN(q) ? 0 : q }\n })\n .filter(item => item.lang && item.q > 0)\n .sort((a, b) => b.q - a.q)\n .map(item => item.lang)\n}\n\n/**\n * Find a matching supported language for a given code, respecting nonExplicitSupportedLngs.\n *\n * Matching order (mirrors i18next's LanguageUtils.getBestMatchFromCodes):\n * 1. Exact match (case-insensitive)\n * 2. Preferred prefix → supported base (e.g. preferred 'en-US' matches supported 'en')\n * 3. If nonExplicitSupportedLngs: preferred base → supported region\n * (e.g. preferred 'en' matches supported 'en-US')\n */\nexport function findSupportedMatch(\n code: string,\n supportedLanguages: readonly string[],\n nonExplicitSupportedLngs: boolean,\n): string | undefined {\n const lower = code.toLowerCase()\n\n // 1. Exact match (case-insensitive)\n const exact = supportedLanguages.find(l => l.toLowerCase() === lower)\n if (exact) return exact\n\n // 2. Preferred prefix → supported base (e.g. 'en-US' → 'en')\n const prefix = lower.split('-')[0]\n const partial = supportedLanguages.find(l => l.toLowerCase() === prefix)\n if (partial) return partial\n\n // 3. Reverse match: preferred base → supported region (e.g. 'en' → 'en-US')\n if (nonExplicitSupportedLngs) {\n const reverse = supportedLanguages.find(\n l => l.toLowerCase().split('-')[0] === prefix\n )\n if (reverse) return reverse\n }\n\n return undefined\n}\n\nexport function matchLanguage(\n acceptLanguages: string[],\n supportedLanguages: readonly string[],\n defaultLanguage: string,\n nonExplicitSupportedLngs = false,\n): string {\n for (const preferred of acceptLanguages) {\n const match = findSupportedMatch(preferred, supportedLanguages, nonExplicitSupportedLngs)\n if (match) return match\n }\n return defaultLanguage\n}\n","import { NextRequest, NextResponse } from 'next/server'\nimport type { I18nConfig } from '../types'\nimport { normalizeConfig } from '../config'\nimport { parseAcceptLanguage, matchLanguage, findSupportedMatch } from './languageDetector'\n\n// Re-export config utilities for Edge-safe usage (no react-i18next dependency)\nexport { defineConfig, normalizeConfig } from '../config'\nexport type { I18nConfig, NormalizedConfig, ResourceLoader } from '../types'\n\nfunction findLocaleInPath(\n pathname: string,\n supportedLngs: readonly string[],\n nonExplicitSupportedLngs: boolean,\n): string | undefined {\n // Extract the first path segment\n const match = pathname.match(/^\\/([^/]+)/)\n if (!match) return undefined\n return findSupportedMatch(match[1], supportedLngs, nonExplicitSupportedLngs)\n}\n\nexport function createProxy(userConfig: I18nConfig) {\n const config = normalizeConfig(userConfig)\n const nonExplicit = config.nonExplicitSupportedLngs\n // Normalize basePath: ensure leading slash, strip trailing slash\n const basePath = config.basePath\n ? ('/' + config.basePath.replace(/^\\/+/, '').replace(/\\/+$/, ''))\n : undefined\n\n return function middleware(req: NextRequest): NextResponse {\n const { pathname, search } = req.nextUrl\n\n // When basePath is set, only handle requests under that prefix\n if (basePath) {\n if (pathname !== basePath && !pathname.startsWith(basePath + '/')) {\n return NextResponse.next()\n }\n }\n\n // Skip ignored paths\n for (const ignored of config.ignoredPaths) {\n if (pathname.startsWith(ignored)) {\n return NextResponse.next()\n }\n }\n\n // Skip common static file extensions\n if (/\\.(ico|png|jpg|jpeg|svg|gif|webp|css|js|map|woff2?|ttf|eot)$/.test(pathname)) {\n return NextResponse.next()\n }\n\n // Detect language from cookie, then Accept-Language header, then default\n let lng: string | undefined\n const cookieValue = req.cookies.get(config.cookieName)?.value\n if (cookieValue) {\n lng = matchLanguage([cookieValue], config.supportedLngs, config.fallbackLng, nonExplicit)\n }\n if (!lng) {\n lng = matchLanguage(\n parseAcceptLanguage(req.headers.get('Accept-Language')),\n config.supportedLngs,\n config.fallbackLng,\n nonExplicit,\n )\n }\n if (!lng) {\n lng = config.fallbackLng\n }\n\n // For locale-in-path detection, strip basePath prefix so we look at the right segment\n const pathForLocale = basePath ? pathname.slice(basePath.length) || '/' : pathname\n const lngInPath = findLocaleInPath(pathForLocale, config.supportedLngs, nonExplicit)\n\n if (config.localeInPath) {\n // Set custom header for server components to read\n const headers = new Headers(req.headers)\n headers.set(config.headerName, lngInPath || lng)\n\n // Redirect if no locale in path\n if (!lngInPath) {\n const prefix = basePath ?? ''\n const pathAfterBase = basePath ? pathname.slice(basePath.length) : pathname\n const redirectUrl = new URL(`${prefix}/${lng}${pathAfterBase}${search}`, req.url)\n const response = NextResponse.redirect(redirectUrl)\n response.cookies.set(config.cookieName, lng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n // Persist language from referer URL into cookie\n const response = NextResponse.next({ request: { headers } })\n if (req.headers.has('referer')) {\n const refererUrl = new URL(req.headers.get('referer')!)\n const refererPathForLocale = basePath\n ? refererUrl.pathname.slice(basePath.length) || '/'\n : refererUrl.pathname\n const lngInReferer = findLocaleInPath(refererPathForLocale, config.supportedLngs, nonExplicit)\n if (lngInReferer) {\n response.cookies.set(config.cookieName, lngInReferer, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n }\n }\n\n return response\n } else {\n // No-locale-path mode: don't redirect, just set the header\n const headers = new Headers(req.headers)\n headers.set(config.headerName, lng)\n\n const response = NextResponse.next({ request: { headers } })\n return response\n }\n }\n}\n\n/**\n * Backwards-compatible alias for createProxy.\n * Use `createProxy` for new projects with Next.js 16+ `proxy.ts`.\n */\nexport const createMiddleware = createProxy\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/appRouter/config.ts","../../../src/appRouter/proxy/languageDetector.ts","../../../src/appRouter/proxy/index.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n hideDefaultLocale: userConfig.hideDefaultLocale ?? false,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n","/**\n * Edge-safe Accept-Language parser and language matcher.\n * No external dependencies — runs in Edge Runtime, Node.js, and browser.\n */\n\nexport function parseAcceptLanguage(header: string | null | undefined): string[] {\n if (!header) return []\n return header\n .split(',')\n .map(part => {\n const [lang, qPart] = part.trim().split(';')\n const q = qPart?.trim().startsWith('q=')\n ? parseFloat(qPart.trim().slice(2))\n : 1.0\n return { lang: lang.trim(), q: isNaN(q) ? 0 : q }\n })\n .filter(item => item.lang && item.q > 0)\n .sort((a, b) => b.q - a.q)\n .map(item => item.lang)\n}\n\n/**\n * Find a matching supported language for a given code, respecting nonExplicitSupportedLngs.\n *\n * Matching order (mirrors i18next's LanguageUtils.getBestMatchFromCodes):\n * 1. Exact match (case-insensitive)\n * 2. Preferred prefix → supported base (e.g. preferred 'en-US' matches supported 'en')\n * 3. If nonExplicitSupportedLngs: preferred base → supported region\n * (e.g. preferred 'en' matches supported 'en-US')\n */\nexport function findSupportedMatch(\n code: string,\n supportedLanguages: readonly string[],\n nonExplicitSupportedLngs: boolean,\n): string | undefined {\n const lower = code.toLowerCase()\n\n // 1. Exact match (case-insensitive)\n const exact = supportedLanguages.find(l => l.toLowerCase() === lower)\n if (exact) return exact\n\n // 2. Preferred prefix → supported base (e.g. 'en-US' → 'en')\n const prefix = lower.split('-')[0]\n const partial = supportedLanguages.find(l => l.toLowerCase() === prefix)\n if (partial) return partial\n\n // 3. Reverse match: preferred base → supported region (e.g. 'en' → 'en-US')\n if (nonExplicitSupportedLngs) {\n const reverse = supportedLanguages.find(\n l => l.toLowerCase().split('-')[0] === prefix\n )\n if (reverse) return reverse\n }\n\n return undefined\n}\n\nexport function matchLanguage(\n acceptLanguages: string[],\n supportedLanguages: readonly string[],\n defaultLanguage: string,\n nonExplicitSupportedLngs = false,\n): string {\n for (const preferred of acceptLanguages) {\n const match = findSupportedMatch(preferred, supportedLanguages, nonExplicitSupportedLngs)\n if (match) return match\n }\n return defaultLanguage\n}\n","import { NextRequest, NextResponse } from 'next/server'\nimport type { I18nConfig } from '../types'\nimport { normalizeConfig } from '../config'\nimport { parseAcceptLanguage, matchLanguage, findSupportedMatch } from './languageDetector'\n\n// Re-export config utilities for Edge-safe usage (no react-i18next dependency)\nexport { defineConfig, normalizeConfig } from '../config'\nexport type { I18nConfig, NormalizedConfig, ResourceLoader } from '../types'\n\nfunction findLocaleInPath(\n pathname: string,\n supportedLngs: readonly string[],\n nonExplicitSupportedLngs: boolean,\n): string | undefined {\n // Extract the first path segment\n const match = pathname.match(/^\\/([^/]+)/)\n if (!match) return undefined\n return findSupportedMatch(match[1], supportedLngs, nonExplicitSupportedLngs)\n}\n\nexport function createProxy(userConfig: I18nConfig) {\n const config = normalizeConfig(userConfig)\n const nonExplicit = config.nonExplicitSupportedLngs\n // Normalize basePath: ensure leading slash, strip trailing slash\n const basePath = config.basePath\n ? ('/' + config.basePath.replace(/^\\/+/, '').replace(/\\/+$/, ''))\n : undefined\n\n return function middleware(req: NextRequest): NextResponse {\n const { pathname, search } = req.nextUrl\n\n // When basePath is set, only handle requests under that prefix\n if (basePath) {\n if (pathname !== basePath && !pathname.startsWith(basePath + '/')) {\n return NextResponse.next()\n }\n }\n\n // Skip ignored paths\n for (const ignored of config.ignoredPaths) {\n if (pathname.startsWith(ignored)) {\n return NextResponse.next()\n }\n }\n\n // Skip common static file extensions\n if (/\\.(ico|png|jpg|jpeg|svg|gif|webp|css|js|map|woff2?|ttf|eot)$/.test(pathname)) {\n return NextResponse.next()\n }\n\n // Detect language from cookie, then Accept-Language header, then default\n let lng: string | undefined\n const cookieValue = req.cookies.get(config.cookieName)?.value\n if (cookieValue) {\n lng = matchLanguage([cookieValue], config.supportedLngs, config.fallbackLng, nonExplicit)\n }\n if (!lng) {\n lng = matchLanguage(\n parseAcceptLanguage(req.headers.get('Accept-Language')),\n config.supportedLngs,\n config.fallbackLng,\n nonExplicit,\n )\n }\n if (!lng) {\n lng = config.fallbackLng\n }\n\n // For locale-in-path detection, strip basePath prefix so we look at the right segment\n const pathForLocale = basePath ? pathname.slice(basePath.length) || '/' : pathname\n const lngInPath = findLocaleInPath(pathForLocale, config.supportedLngs, nonExplicit)\n\n if (config.localeInPath) {\n const prefix = basePath ?? ''\n const pathAfterBase = basePath ? pathname.slice(basePath.length) : pathname\n\n // hideDefaultLocale: redirect explicit default-locale paths to the clean URL\n if (config.hideDefaultLocale && lngInPath === config.fallbackLng) {\n const pathWithoutLocale = pathAfterBase.replace(/^\\/[^/]+/, '') || '/'\n const redirectUrl = new URL(`${prefix}${pathWithoutLocale}${search}`, req.url)\n const response = NextResponse.redirect(redirectUrl)\n response.cookies.set(config.cookieName, config.fallbackLng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n // Set custom header for server components to read\n const headers = new Headers(req.headers)\n headers.set(config.headerName, lngInPath || lng)\n\n // Redirect if no locale in path\n if (!lngInPath) {\n if (config.hideDefaultLocale) {\n // Rewrite internally to the default-locale path, keeping the clean URL\n const rewriteUrl = new URL(`${prefix}/${config.fallbackLng}${pathAfterBase}${search}`, req.url)\n headers.set(config.headerName, config.fallbackLng)\n const response = NextResponse.rewrite(rewriteUrl, { request: { headers } })\n response.cookies.set(config.cookieName, config.fallbackLng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n const redirectUrl = new URL(`${prefix}/${lng}${pathAfterBase}${search}`, req.url)\n const response = NextResponse.redirect(redirectUrl)\n response.cookies.set(config.cookieName, lng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n // Persist language from referer URL into cookie\n const response = NextResponse.next({ request: { headers } })\n if (req.headers.has('referer')) {\n const refererUrl = new URL(req.headers.get('referer')!)\n const refererPathForLocale = basePath\n ? refererUrl.pathname.slice(basePath.length) || '/'\n : refererUrl.pathname\n const lngInReferer = findLocaleInPath(refererPathForLocale, config.supportedLngs, nonExplicit)\n if (lngInReferer) {\n response.cookies.set(config.cookieName, lngInReferer, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n }\n }\n\n return response\n } else {\n // No-locale-path mode: don't redirect, just set the header\n const headers = new Headers(req.headers)\n headers.set(config.headerName, lng)\n\n const response = NextResponse.next({ request: { headers } })\n return response\n }\n }\n}\n\n/**\n * Backwards-compatible alias for createProxy.\n * Use `createProxy` for new projects with Next.js 16+ `proxy.ts`.\n */\nexport const createMiddleware = createProxy\n"],"mappings":"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"}
@@ -39,6 +39,7 @@ function normalizeConfig(userConfig) {
39
39
  defaultNS,
40
40
  ns: userConfig.ns ?? [defaultNS],
41
41
  localeInPath: userConfig.localeInPath ?? true,
42
+ hideDefaultLocale: userConfig.hideDefaultLocale ?? false,
42
43
  localePath: userConfig.localePath ?? "/locales",
43
44
  localeStructure: userConfig.localeStructure ?? "{{lng}}/{{ns}}",
44
45
  localeExtension: userConfig.localeExtension ?? "json",
@@ -1 +1 @@
1
- {"version":3,"file":"server.cjs","names":[],"sources":["../../src/appRouter/config.ts","../../src/appRouter/server.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n","import { createInstance } from 'i18next'\nimport type { i18n as I18NextClient, Resource, Module, FlatNamespace, KeyPrefix } from 'i18next'\nimport resourcesToBackend from 'i18next-resources-to-backend'\nimport { cache } from 'react'\nimport { headers, cookies } from 'next/headers'\n\nimport type { I18nConfig, NormalizedConfig, GetTResult } from './types'\nimport { normalizeConfig } from './config'\n\nlet _config: NormalizedConfig | null = null\n\n// Module-level singleton: persists across requests within the same server process.\n// This is critical for custom backends (i18next-http-backend, i18next-locize-backend)\n// to avoid re-fetching translations on every request.\n// In serverless environments (Lambda, Cloud Functions, etc.), this lives as long as\n// the warm function instance — backends with reloadInterval will refresh automatically.\nlet _sharedInstance: I18NextClient | null = null\nlet _sharedInstancePromise: Promise<I18NextClient> | null = null\n\nfunction getConfig(): NormalizedConfig {\n if (!_config) {\n throw new Error(\n 'next-i18next: Server module not initialized. Call initServerI18next(config) in your root layout.'\n )\n }\n return _config\n}\n\n/**\n * Initialize the server-side i18next configuration.\n * Call this once in your root layout or a shared setup file.\n */\nexport function initServerI18next(userConfig: I18nConfig): void {\n _config = normalizeConfig(userConfig)\n}\n\nfunction hasCustomBackend(plugins: any[]): boolean {\n return plugins.some((b: Module) => b.type === 'backend')\n}\n\nfunction createResourceBackend(config: NormalizedConfig) {\n if (config.resourceLoader) {\n return resourcesToBackend(config.resourceLoader)\n }\n return resourcesToBackend(async (language: string, namespace: string) => {\n const filePath = `${config.localePath}/${config.localeStructure\n .replace('{{lng}}', language)\n .replace('{{ns}}', namespace)}.${config.localeExtension}`\n\n // Node.js runtime: read from filesystem\n if (typeof process !== 'undefined' && process.versions?.node) {\n try {\n const fs = await import('fs/promises')\n const pathMod = await import('path')\n const resolved = pathMod.resolve(process.cwd(), `public${filePath}`)\n const content = await fs.readFile(resolved, 'utf-8')\n return JSON.parse(content)\n } catch {\n throw new Error(\n `next-i18next: Could not read locale file \"public${filePath}\". ` +\n 'On serverless platforms (Vercel, AWS Lambda, etc.), files in public/ are served via CDN ' +\n 'but are NOT available on the filesystem at runtime. Use the `resourceLoader` option with ' +\n 'dynamic imports instead:\\n\\n' +\n ' resourceLoader: (language, namespace) =>\\n' +\n // eslint-disable-next-line no-template-curly-in-string\n ' import(`./public/locales/${language}/${namespace}.json`)\\n'\n )\n }\n }\n\n // Edge runtime: filesystem not available\n throw new Error(\n `next-i18next: Cannot load locale file \"${filePath}\" in Edge Runtime. ` +\n 'Provide pre-bundled `resources`, a custom `resourceLoader`, or use a custom backend (e.g. i18next-http-backend) via the `use` option.'\n )\n })\n}\n\n/**\n * Get or create the shared i18next instance.\n * The instance is created once and reused across all requests.\n * All languages are preloaded so that getFixedT(lng) works for any supported language.\n * Additional namespaces are loaded on demand and cached in the instance store.\n */\nasync function getSharedInstance(config: NormalizedConfig): Promise<I18NextClient> {\n if (_sharedInstance?.isInitialized) return _sharedInstance\n\n // Deduplicate concurrent init calls (multiple requests arriving while first init is in flight)\n if (_sharedInstancePromise) return _sharedInstancePromise\n\n _sharedInstancePromise = (async () => {\n const i18nInstance = createInstance()\n\n // Add a backend when needed:\n // - No resources provided → backend loads everything\n // - Resources provided with partialBundledLanguages → backend loads the rest\n // - Custom backend in config.use → user handles it, skip default backend\n const partialBundled = config.i18nextOptions?.partialBundledLanguages\n if ((!config.resources || partialBundled) && !hasCustomBackend(config.use)) {\n i18nInstance.use(createResourceBackend(config))\n }\n\n config.use.forEach((plugin: any) => i18nInstance.use(plugin))\n\n await i18nInstance.init({\n // No `lng` — the shared instance is language-neutral.\n // We use getFixedT(lng, ns) to get language-specific translators.\n lng: config.fallbackLng,\n ns: config.ns,\n defaultNS: config.defaultNS,\n fallbackLng: config.fallbackLng,\n supportedLngs: config.supportedLngs,\n nonExplicitSupportedLngs: config.nonExplicitSupportedLngs,\n fallbackNS: config.defaultNS,\n preload: config.supportedLngs, // preload ALL languages upfront\n interpolation: { escapeValue: false },\n ...(config.resources ? { resources: config.resources } : {}),\n ...config.i18nextOptions,\n })\n\n _sharedInstance = i18nInstance\n return i18nInstance\n })()\n\n return _sharedInstancePromise\n}\n\n// Per-request language detection, deduplicated within a single React render\nconst detectLanguage = cache(async (config: NormalizedConfig): Promise<string> => {\n const headerList = await headers()\n const fromHeader = headerList.get(config.headerName)\n if (fromHeader) return fromHeader\n\n const cookieStore = await cookies()\n const cookieValue = cookieStore.get(config.cookieName)?.value\n if (cookieValue) {\n if (config.supportedLngs.includes(cookieValue)) {\n return cookieValue\n }\n // nonExplicitSupportedLngs: e.g. cookie 'en' matches supported 'en-US'\n if (config.nonExplicitSupportedLngs) {\n const prefix = cookieValue.toLowerCase().split('-')[0]\n const match = config.supportedLngs.find(\n l => l.toLowerCase() === prefix || l.toLowerCase().split('-')[0] === prefix\n )\n if (match) return match\n }\n }\n\n return config.fallbackLng\n})\n\n/**\n * Get a translation function for use in Server Components, layouts, and generateMetadata.\n *\n * The underlying i18next instance is a **module-level singleton** that persists across\n * requests. This means custom backends (i18next-http-backend, i18next-locize-backend, etc.)\n * only fetch translations once (or according to their own reloadInterval), not on every request.\n *\n * @example\n * ```tsx\n * import { getT } from 'next-i18next/server'\n *\n * export default async function Page() {\n * const { t, i18n } = await getT('home')\n * return <h1>{t('heading')}</h1>\n * }\n * ```\n */\nexport async function getT<\n Ns extends FlatNamespace = FlatNamespace,\n KPrefix extends KeyPrefix<Ns> = undefined,\n>(\n ns?: Ns | Ns[],\n options: { keyPrefix?: KPrefix; lng?: string } = {},\n): Promise<GetTResult<Ns, KPrefix>> {\n const config = getConfig()\n\n const lng = options.lng || await detectLanguage(config)\n const i18nInstance = await getSharedInstance(config)\n\n // Load additional namespaces on demand if not already loaded\n const nsArray: string[] = ns\n ? (Array.isArray(ns) ? ns as string[] : [ns as string])\n : config.ns\n const missingNs = nsArray.filter(n => !i18nInstance.hasLoadedNamespace(n))\n if (missingNs.length > 0) {\n await i18nInstance.loadNamespaces(missingNs)\n }\n\n const resolvedNs = ns\n ? (Array.isArray(ns) ? ns[0] : ns) as string\n : config.defaultNS\n\n return {\n t: i18nInstance.getFixedT(lng, resolvedNs, options.keyPrefix as string | undefined),\n i18n: i18nInstance,\n lng,\n } as any\n}\n\n/**\n * Extract loaded resources from the server i18next instance for passing to I18nProvider.\n *\n * @example\n * ```tsx\n * const { i18n } = await getT()\n * const resources = getResources(i18n, ['common', 'footer'])\n * return <I18nProvider language={i18n.language} resources={resources}>{children}</I18nProvider>\n * ```\n */\nexport function getResources(\n i18n: I18NextClient,\n namespaces?: string[],\n): Resource {\n const resources: Resource = {}\n const store = i18n.store?.data || {}\n const nsFilter = namespaces ? new Set(namespaces) : null\n\n for (const lng of Object.keys(store)) {\n resources[lng] = {}\n for (const ns of Object.keys(store[lng])) {\n if (!nsFilter || nsFilter.has(ns)) {\n resources[lng][ns] = store[lng][ns]\n }\n }\n }\n\n return resources\n}\n\n/**\n * Helper for generateStaticParams — returns params for all supported languages.\n *\n * @example\n * ```tsx\n * import { generateI18nStaticParams } from 'next-i18next/server'\n *\n * export async function generateStaticParams() {\n * return generateI18nStaticParams()\n * }\n * ```\n */\nexport function generateI18nStaticParams(): { lng: string }[] {\n const config = getConfig()\n return config.supportedLngs.map(lng => ({ lng }))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.cjs","names":[],"sources":["../../src/appRouter/config.ts","../../src/appRouter/server.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n hideDefaultLocale: userConfig.hideDefaultLocale ?? false,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n","import { createInstance } from 'i18next'\nimport type { i18n as I18NextClient, Resource, Module, FlatNamespace, KeyPrefix } from 'i18next'\nimport resourcesToBackend from 'i18next-resources-to-backend'\nimport { cache } from 'react'\nimport { headers, cookies } from 'next/headers'\n\nimport type { I18nConfig, NormalizedConfig, GetTResult } from './types'\nimport { normalizeConfig } from './config'\n\nlet _config: NormalizedConfig | null = null\n\n// Module-level singleton: persists across requests within the same server process.\n// This is critical for custom backends (i18next-http-backend, i18next-locize-backend)\n// to avoid re-fetching translations on every request.\n// In serverless environments (Lambda, Cloud Functions, etc.), this lives as long as\n// the warm function instance — backends with reloadInterval will refresh automatically.\nlet _sharedInstance: I18NextClient | null = null\nlet _sharedInstancePromise: Promise<I18NextClient> | null = null\n\nfunction getConfig(): NormalizedConfig {\n if (!_config) {\n throw new Error(\n 'next-i18next: Server module not initialized. Call initServerI18next(config) in your root layout.'\n )\n }\n return _config\n}\n\n/**\n * Initialize the server-side i18next configuration.\n * Call this once in your root layout or a shared setup file.\n */\nexport function initServerI18next(userConfig: I18nConfig): void {\n _config = normalizeConfig(userConfig)\n}\n\nfunction hasCustomBackend(plugins: any[]): boolean {\n return plugins.some((b: Module) => b.type === 'backend')\n}\n\nfunction createResourceBackend(config: NormalizedConfig) {\n if (config.resourceLoader) {\n return resourcesToBackend(config.resourceLoader)\n }\n return resourcesToBackend(async (language: string, namespace: string) => {\n const filePath = `${config.localePath}/${config.localeStructure\n .replace('{{lng}}', language)\n .replace('{{ns}}', namespace)}.${config.localeExtension}`\n\n // Node.js runtime: read from filesystem\n if (typeof process !== 'undefined' && process.versions?.node) {\n try {\n const fs = await import('fs/promises')\n const pathMod = await import('path')\n const resolved = pathMod.resolve(process.cwd(), `public${filePath}`)\n const content = await fs.readFile(resolved, 'utf-8')\n return JSON.parse(content)\n } catch {\n throw new Error(\n `next-i18next: Could not read locale file \"public${filePath}\". ` +\n 'On serverless platforms (Vercel, AWS Lambda, etc.), files in public/ are served via CDN ' +\n 'but are NOT available on the filesystem at runtime. Use the `resourceLoader` option with ' +\n 'dynamic imports instead:\\n\\n' +\n ' resourceLoader: (language, namespace) =>\\n' +\n // eslint-disable-next-line no-template-curly-in-string\n ' import(`./public/locales/${language}/${namespace}.json`)\\n'\n )\n }\n }\n\n // Edge runtime: filesystem not available\n throw new Error(\n `next-i18next: Cannot load locale file \"${filePath}\" in Edge Runtime. ` +\n 'Provide pre-bundled `resources`, a custom `resourceLoader`, or use a custom backend (e.g. i18next-http-backend) via the `use` option.'\n )\n })\n}\n\n/**\n * Get or create the shared i18next instance.\n * The instance is created once and reused across all requests.\n * All languages are preloaded so that getFixedT(lng) works for any supported language.\n * Additional namespaces are loaded on demand and cached in the instance store.\n */\nasync function getSharedInstance(config: NormalizedConfig): Promise<I18NextClient> {\n if (_sharedInstance?.isInitialized) return _sharedInstance\n\n // Deduplicate concurrent init calls (multiple requests arriving while first init is in flight)\n if (_sharedInstancePromise) return _sharedInstancePromise\n\n _sharedInstancePromise = (async () => {\n const i18nInstance = createInstance()\n\n // Add a backend when needed:\n // - No resources provided → backend loads everything\n // - Resources provided with partialBundledLanguages → backend loads the rest\n // - Custom backend in config.use → user handles it, skip default backend\n const partialBundled = config.i18nextOptions?.partialBundledLanguages\n if ((!config.resources || partialBundled) && !hasCustomBackend(config.use)) {\n i18nInstance.use(createResourceBackend(config))\n }\n\n config.use.forEach((plugin: any) => i18nInstance.use(plugin))\n\n await i18nInstance.init({\n // No `lng` — the shared instance is language-neutral.\n // We use getFixedT(lng, ns) to get language-specific translators.\n lng: config.fallbackLng,\n ns: config.ns,\n defaultNS: config.defaultNS,\n fallbackLng: config.fallbackLng,\n supportedLngs: config.supportedLngs,\n nonExplicitSupportedLngs: config.nonExplicitSupportedLngs,\n fallbackNS: config.defaultNS,\n preload: config.supportedLngs, // preload ALL languages upfront\n interpolation: { escapeValue: false },\n ...(config.resources ? { resources: config.resources } : {}),\n ...config.i18nextOptions,\n })\n\n _sharedInstance = i18nInstance\n return i18nInstance\n })()\n\n return _sharedInstancePromise\n}\n\n// Per-request language detection, deduplicated within a single React render\nconst detectLanguage = cache(async (config: NormalizedConfig): Promise<string> => {\n const headerList = await headers()\n const fromHeader = headerList.get(config.headerName)\n if (fromHeader) return fromHeader\n\n const cookieStore = await cookies()\n const cookieValue = cookieStore.get(config.cookieName)?.value\n if (cookieValue) {\n if (config.supportedLngs.includes(cookieValue)) {\n return cookieValue\n }\n // nonExplicitSupportedLngs: e.g. cookie 'en' matches supported 'en-US'\n if (config.nonExplicitSupportedLngs) {\n const prefix = cookieValue.toLowerCase().split('-')[0]\n const match = config.supportedLngs.find(\n l => l.toLowerCase() === prefix || l.toLowerCase().split('-')[0] === prefix\n )\n if (match) return match\n }\n }\n\n return config.fallbackLng\n})\n\n/**\n * Get a translation function for use in Server Components, layouts, and generateMetadata.\n *\n * The underlying i18next instance is a **module-level singleton** that persists across\n * requests. This means custom backends (i18next-http-backend, i18next-locize-backend, etc.)\n * only fetch translations once (or according to their own reloadInterval), not on every request.\n *\n * @example\n * ```tsx\n * import { getT } from 'next-i18next/server'\n *\n * export default async function Page() {\n * const { t, i18n } = await getT('home')\n * return <h1>{t('heading')}</h1>\n * }\n * ```\n */\nexport async function getT<\n Ns extends FlatNamespace = FlatNamespace,\n KPrefix extends KeyPrefix<Ns> = undefined,\n>(\n ns?: Ns | Ns[],\n options: { keyPrefix?: KPrefix; lng?: string } = {},\n): Promise<GetTResult<Ns, KPrefix>> {\n const config = getConfig()\n\n const lng = options.lng || await detectLanguage(config)\n const i18nInstance = await getSharedInstance(config)\n\n // Load additional namespaces on demand if not already loaded\n const nsArray: string[] = ns\n ? (Array.isArray(ns) ? ns as string[] : [ns as string])\n : config.ns\n const missingNs = nsArray.filter(n => !i18nInstance.hasLoadedNamespace(n))\n if (missingNs.length > 0) {\n await i18nInstance.loadNamespaces(missingNs)\n }\n\n const resolvedNs = ns\n ? (Array.isArray(ns) ? ns[0] : ns) as string\n : config.defaultNS\n\n return {\n t: i18nInstance.getFixedT(lng, resolvedNs, options.keyPrefix as string | undefined),\n i18n: i18nInstance,\n lng,\n } as any\n}\n\n/**\n * Extract loaded resources from the server i18next instance for passing to I18nProvider.\n *\n * @example\n * ```tsx\n * const { i18n } = await getT()\n * const resources = getResources(i18n, ['common', 'footer'])\n * return <I18nProvider language={i18n.language} resources={resources}>{children}</I18nProvider>\n * ```\n */\nexport function getResources(\n i18n: I18NextClient,\n namespaces?: string[],\n): Resource {\n const resources: Resource = {}\n const store = i18n.store?.data || {}\n const nsFilter = namespaces ? new Set(namespaces) : null\n\n for (const lng of Object.keys(store)) {\n resources[lng] = {}\n for (const ns of Object.keys(store[lng])) {\n if (!nsFilter || nsFilter.has(ns)) {\n resources[lng][ns] = store[lng][ns]\n }\n }\n }\n\n return resources\n}\n\n/**\n * Helper for generateStaticParams — returns params for all supported languages.\n *\n * @example\n * ```tsx\n * import { generateI18nStaticParams } from 'next-i18next/server'\n *\n * export async function generateStaticParams() {\n * return generateI18nStaticParams()\n * }\n * ```\n */\nexport function generateI18nStaticParams(): { lng: string }[] {\n const config = getConfig()\n return config.supportedLngs.map(lng => ({ lng }))\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,SAAgB,gBAAgB,YAA0C;CAExE,MAAM,gBAAgB,WAAW,iBAC/B,WAAW,MAAM,SAAS,QAAQ,MAAc,MAAM,UAAU,IAChE,CAAC,KAAK;CACR,MAAM,cAAc,WAAW,eAC7B,WAAW,MAAM,iBACjB,cAAc;AAEhB,KAAI,CAAC,YACH,OAAM,IAAI,MAAM,gEAAgE;AAElF,KAAI,cAAc,WAAW,EAC3B,OAAM,IAAI,MAAM,mFAAmF;CAGrG,MAAM,YAAY,WAAW,aAAa;AAE1C,QAAO;EACL;EACA;EACA;EACA,IAAI,WAAW,MAAM,CAAC,UAAU;EAChC,cAAc,WAAW,gBAAgB;EACzC,mBAAmB,WAAW,qBAAqB;EACnD,YAAY,WAAW,cAAc;EACrC,iBAAiB,WAAW,mBAAmB;EAC/C,iBAAiB,WAAW,mBAAmB;EAC/C,YAAY,WAAW,cAAc;EACrC,YAAY,WAAW,cAAc;EACrC,cAAc,WAAW,gBAAgB,MAAM,KAAK,KAAK;EACzD,cAAc,WAAW,gBAAgB;GAAC;GAAQ;GAAU;GAAU;EACtE,UAAU,WAAW;EACrB,WAAW,WAAW;EACtB,gBAAgB,WAAW;EAC3B,KAAK,WAAW,OAAO,EAAE;EACzB,gBAAiB,WAAW,kBAAkB,EAAE;EAChD,0BAA0B,WAAW,4BAA4B;EAEjE,MAAM,WAAW;EACjB,iBAAiB,WAAW;EAC5B,mBAAmB,WAAW;EAC/B;;;;ACvCH,IAAI,UAAmC;AAOvC,IAAI,kBAAwC;AAC5C,IAAI,yBAAwD;AAE5D,SAAS,YAA8B;AACrC,KAAI,CAAC,QACH,OAAM,IAAI,MACR,mGACD;AAEH,QAAO;;;;;;AAOT,SAAgB,kBAAkB,YAA8B;AAC9D,WAAU,gBAAgB,WAAW;;AAGvC,SAAS,iBAAiB,SAAyB;AACjD,QAAO,QAAQ,MAAM,MAAc,EAAE,SAAS,UAAU;;AAG1D,SAAS,sBAAsB,QAA0B;AACvD,KAAI,OAAO,eACT,SAAA,GAAA,6BAAA,SAA0B,OAAO,eAAe;AAElD,SAAA,GAAA,6BAAA,SAA0B,OAAO,UAAkB,cAAsB;EACvE,MAAM,WAAW,GAAG,OAAO,WAAW,GAAG,OAAO,gBAC7C,QAAQ,WAAW,SAAS,CAC5B,QAAQ,UAAU,UAAU,CAAC,GAAG,OAAO;AAG1C,MAAI,OAAO,YAAY,eAAe,QAAQ,UAAU,KACtD,KAAI;GACF,MAAM,KAAK,MAAM,OAAO;GAExB,MAAM,YADU,MAAM,OAAO,SACJ,QAAQ,QAAQ,KAAK,EAAE,SAAS,WAAW;GACpE,MAAM,UAAU,MAAM,GAAG,SAAS,UAAU,QAAQ;AACpD,UAAO,KAAK,MAAM,QAAQ;UACpB;AACN,SAAM,IAAI,MACR,mDAAmD,SAAS;;;;EAO7D;;AAKL,QAAM,IAAI,MACR,0CAA0C,SAAS,gKAEpD;GACD;;;;;;;;AASJ,eAAe,kBAAkB,QAAkD;AACjF,KAAI,iBAAiB,cAAe,QAAO;AAG3C,KAAI,uBAAwB,QAAO;AAEnC,2BAA0B,YAAY;EACpC,MAAM,gBAAA,GAAA,QAAA,iBAA+B;EAMrC,MAAM,iBAAiB,OAAO,gBAAgB;AAC9C,OAAK,CAAC,OAAO,aAAa,mBAAmB,CAAC,iBAAiB,OAAO,IAAI,CACxE,cAAa,IAAI,sBAAsB,OAAO,CAAC;AAGjD,SAAO,IAAI,SAAS,WAAgB,aAAa,IAAI,OAAO,CAAC;AAE7D,QAAM,aAAa,KAAK;GAGtB,KAAK,OAAO;GACZ,IAAI,OAAO;GACX,WAAW,OAAO;GAClB,aAAa,OAAO;GACpB,eAAe,OAAO;GACtB,0BAA0B,OAAO;GACjC,YAAY,OAAO;GACnB,SAAS,OAAO;GAChB,eAAe,EAAE,aAAa,OAAO;GACrC,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,WAAW,GAAG,EAAE;GAC3D,GAAG,OAAO;GACX,CAAC;AAEF,oBAAkB;AAClB,SAAO;KACL;AAEJ,QAAO;;AAIT,MAAM,kBAAA,GAAA,MAAA,OAAuB,OAAO,WAA8C;CAEhF,MAAM,cADa,OAAA,GAAA,aAAA,UAAe,EACJ,IAAI,OAAO,WAAW;AACpD,KAAI,WAAY,QAAO;CAGvB,MAAM,eADc,OAAA,GAAA,aAAA,UAAe,EACH,IAAI,OAAO,WAAW,EAAE;AACxD,KAAI,aAAa;AACf,MAAI,OAAO,cAAc,SAAS,YAAY,CAC5C,QAAO;AAGT,MAAI,OAAO,0BAA0B;GACnC,MAAM,SAAS,YAAY,aAAa,CAAC,MAAM,IAAI,CAAC;GACpD,MAAM,QAAQ,OAAO,cAAc,MACjC,MAAK,EAAE,aAAa,KAAK,UAAU,EAAE,aAAa,CAAC,MAAM,IAAI,CAAC,OAAO,OACtE;AACD,OAAI,MAAO,QAAO;;;AAItB,QAAO,OAAO;EACd;;;;;;;;;;;;;;;;;;AAmBF,eAAsB,KAIpB,IACA,UAAiD,EAAE,EACjB;CAClC,MAAM,SAAS,WAAW;CAE1B,MAAM,MAAM,QAAQ,OAAO,MAAM,eAAe,OAAO;CACvD,MAAM,eAAe,MAAM,kBAAkB,OAAO;CAMpD,MAAM,aAHoB,KACrB,MAAM,QAAQ,GAAG,GAAG,KAAiB,CAAC,GAAa,GACpD,OAAO,IACe,QAAO,MAAK,CAAC,aAAa,mBAAmB,EAAE,CAAC;AAC1E,KAAI,UAAU,SAAS,EACrB,OAAM,aAAa,eAAe,UAAU;CAG9C,MAAM,aAAa,KACd,MAAM,QAAQ,GAAG,GAAG,GAAG,KAAK,KAC7B,OAAO;AAEX,QAAO;EACL,GAAG,aAAa,UAAU,KAAK,YAAY,QAAQ,UAAgC;EACnF,MAAM;EACN;EACD;;;;;;;;;;;;AAaH,SAAgB,aACd,MACA,YACU;CACV,MAAM,YAAsB,EAAE;CAC9B,MAAM,QAAQ,KAAK,OAAO,QAAQ,EAAE;CACpC,MAAM,WAAW,aAAa,IAAI,IAAI,WAAW,GAAG;AAEpD,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,EAAE;AACpC,YAAU,OAAO,EAAE;AACnB,OAAK,MAAM,MAAM,OAAO,KAAK,MAAM,KAAK,CACtC,KAAI,CAAC,YAAY,SAAS,IAAI,GAAG,CAC/B,WAAU,KAAK,MAAM,MAAM,KAAK;;AAKtC,QAAO;;;;;;;;;;;;;;AAeT,SAAgB,2BAA8C;AAE5D,QADe,WAAW,CACZ,cAAc,KAAI,SAAQ,EAAE,KAAK,EAAE"}
@@ -23,6 +23,10 @@ interface I18nConfig {
23
23
  resourceLoader?: ResourceLoader;
24
24
  /** Whether to include locale in URL path (defaults to true) */
25
25
  localeInPath?: boolean;
26
+ /** When true (and localeInPath is true), the default language has no URL prefix.
27
+ * e.g. `/about` serves the default language, `/de/about` serves German.
28
+ * Requests to the explicit default prefix (`/en/about`) are redirected to `/about`. */
29
+ hideDefaultLocale?: boolean;
26
30
  /** Cookie name for storing selected language (defaults to 'i18next') */
27
31
  cookieName?: string;
28
32
  /** Custom header name for passing language to server components (defaults to 'x-i18next-current-language') */
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.cts","names":[],"sources":["../../src/appRouter/types.ts","../../src/appRouter/server.ts"],"mappings":";;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;EAFL;EAIV,aAAA;;EAEA,WAAA;EAN4B;EAQ5B,SAAA;EARoE;EAUpE,EAAA;EAV2E;EAc3E,UAAA;EAZyB;EAczB,eAAA;EAIY;EAFZ,eAAA;EA0BsB;EAxBtB,SAAA,GAAY,QAAA;EAwBS;EAtBrB,cAAA,GAAiB,cAAA;EAlBjB;EAsBA,YAAA;EAlBA;EAsBA,UAAA;EAhBA;EAkBA,UAAA;EAdA;EAgBA,YAAA;EAdY;EAgBZ,YAAA;EAdiB;EAgBjB,QAAA;EARA;EAYA,GAAA;EARA;EAUA,cAAA,GAAiB,IAAA,CAAK,WAAA;EANtB;EAUA,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAFE;EAKJ,eAAA;EAAA;EAEA,iBAAA;EAEA;EAAA,wBAAA;AAAA;AAAA,KA4BU,UAAA,YAAsB,aAAA,GAAgB,aAAA;EAChD,CAAA,EAAG,SAAA,CAAU,EAAA,EAAI,OAAA;EACjB,IAAA,EAAM,IAAA;EAEN,GAAA;AAAA;;;;AAhGF;;;iBC8BgB,iBAAA,CAAkB,UAAA,EAAY,UAAA;;;;;;AD5B9C;;;;;;;;;;;;iBCqKsB,IAAA,YACT,aAAA,GAAgB,aAAA,kBACX,SAAA,CAAU,EAAA,cAAA,CAE1B,EAAA,GAAK,EAAA,GAAK,EAAA,IACV,OAAA;EAAW,SAAA,GAAY,OAAA;EAAS,GAAA;AAAA,IAC/B,OAAA,CAAQ,UAAA,CAAW,EAAA,EAAI,OAAA;;;;;;;;;;;iBAoCV,YAAA,CACd,IAAA,EAAM,IAAA,EACN,UAAA,cACC,QAAA;;;;;;;;;;;;;iBA6Ba,wBAAA,CAAA;EAA8B,GAAA;AAAA"}
1
+ {"version":3,"file":"server.d.cts","names":[],"sources":["../../src/appRouter/types.ts","../../src/appRouter/server.ts"],"mappings":";;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;EAFL;EAIV,aAAA;;EAEA,WAAA;EAN4B;EAQ5B,SAAA;EARoE;EAUpE,EAAA;EAV2E;EAc3E,UAAA;EAZyB;EAczB,eAAA;EAIY;EAFZ,eAAA;EA8BsB;EA5BtB,SAAA,GAAY,QAAA;EA4BS;EA1BrB,cAAA,GAAiB,cAAA;EAlBjB;EAsBA,YAAA;EAlBA;;;EAsBA,iBAAA;EAZA;EAgBA,UAAA;EAdY;EAgBZ,UAAA;EAdiB;EAgBjB,YAAA;EARA;EAUA,YAAA;EAJA;EAMA,QAAA;EAFA;EAMA,GAAA;EAAA;EAEA,cAAA,GAAiB,IAAA,CAAK,WAAA;EAAL;EAIjB,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAKF;EAFA,eAAA;EAIwB;EAFxB,iBAAA;EA+BU;EA7BV,wBAAA;AAAA;AAAA,KA6BU,UAAA,YAAsB,aAAA,GAAgB,aAAA;EAChD,CAAA,EAAG,SAAA,CAAU,EAAA,EAAI,OAAA;EACjB,IAAA,EAAM,IAAA,ECrEsC;EDuE5C,GAAA;AAAA;;;;AArGF;;;iBC8BgB,iBAAA,CAAkB,UAAA,EAAY,UAAA;;;;;;AD5B9C;;;;;;;;;;;;iBCqKsB,IAAA,YACT,aAAA,GAAgB,aAAA,kBACX,SAAA,CAAU,EAAA,cAAA,CAE1B,EAAA,GAAK,EAAA,GAAK,EAAA,IACV,OAAA;EAAW,SAAA,GAAY,OAAA;EAAS,GAAA;AAAA,IAC/B,OAAA,CAAQ,UAAA,CAAW,EAAA,EAAI,OAAA;;;;;;;;;;;iBAoCV,YAAA,CACd,IAAA,EAAM,IAAA,EACN,UAAA,cACC,QAAA;;;;;;;;;;;;;iBA6Ba,wBAAA,CAAA;EAA8B,GAAA;AAAA"}
@@ -23,6 +23,10 @@ interface I18nConfig {
23
23
  resourceLoader?: ResourceLoader;
24
24
  /** Whether to include locale in URL path (defaults to true) */
25
25
  localeInPath?: boolean;
26
+ /** When true (and localeInPath is true), the default language has no URL prefix.
27
+ * e.g. `/about` serves the default language, `/de/about` serves German.
28
+ * Requests to the explicit default prefix (`/en/about`) are redirected to `/about`. */
29
+ hideDefaultLocale?: boolean;
26
30
  /** Cookie name for storing selected language (defaults to 'i18next') */
27
31
  cookieName?: string;
28
32
  /** Custom header name for passing language to server components (defaults to 'x-i18next-current-language') */
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.mts","names":[],"sources":["../../src/appRouter/types.ts","../../src/appRouter/server.ts"],"mappings":";;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;EAFL;EAIV,aAAA;;EAEA,WAAA;EAN4B;EAQ5B,SAAA;EARoE;EAUpE,EAAA;EAV2E;EAc3E,UAAA;EAZyB;EAczB,eAAA;EAIY;EAFZ,eAAA;EA0BsB;EAxBtB,SAAA,GAAY,QAAA;EAwBS;EAtBrB,cAAA,GAAiB,cAAA;EAlBjB;EAsBA,YAAA;EAlBA;EAsBA,UAAA;EAhBA;EAkBA,UAAA;EAdA;EAgBA,YAAA;EAdY;EAgBZ,YAAA;EAdiB;EAgBjB,QAAA;EARA;EAYA,GAAA;EARA;EAUA,cAAA,GAAiB,IAAA,CAAK,WAAA;EANtB;EAUA,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAFE;EAKJ,eAAA;EAAA;EAEA,iBAAA;EAEA;EAAA,wBAAA;AAAA;AAAA,KA4BU,UAAA,YAAsB,aAAA,GAAgB,aAAA;EAChD,CAAA,EAAG,SAAA,CAAU,EAAA,EAAI,OAAA;EACjB,IAAA,EAAM,IAAA;EAEN,GAAA;AAAA;;;;AAhGF;;;iBC8BgB,iBAAA,CAAkB,UAAA,EAAY,UAAA;;;;;;AD5B9C;;;;;;;;;;;;iBCqKsB,IAAA,YACT,aAAA,GAAgB,aAAA,kBACX,SAAA,CAAU,EAAA,cAAA,CAE1B,EAAA,GAAK,EAAA,GAAK,EAAA,IACV,OAAA;EAAW,SAAA,GAAY,OAAA;EAAS,GAAA;AAAA,IAC/B,OAAA,CAAQ,UAAA,CAAW,EAAA,EAAI,OAAA;;;;;;;;;;;iBAoCV,YAAA,CACd,IAAA,EAAM,IAAA,EACN,UAAA,cACC,QAAA;;;;;;;;;;;;;iBA6Ba,wBAAA,CAAA;EAA8B,GAAA;AAAA"}
1
+ {"version":3,"file":"server.d.mts","names":[],"sources":["../../src/appRouter/types.ts","../../src/appRouter/server.ts"],"mappings":";;;KAEY,cAAA,IAAkB,QAAA,UAAkB,SAAA,aAAsB,OAAA;AAAA,UAErD,UAAA;EAFL;EAIV,aAAA;;EAEA,WAAA;EAN4B;EAQ5B,SAAA;EARoE;EAUpE,EAAA;EAV2E;EAc3E,UAAA;EAZyB;EAczB,eAAA;EAIY;EAFZ,eAAA;EA8BsB;EA5BtB,SAAA,GAAY,QAAA;EA4BS;EA1BrB,cAAA,GAAiB,cAAA;EAlBjB;EAsBA,YAAA;EAlBA;;;EAsBA,iBAAA;EAZA;EAgBA,UAAA;EAdY;EAgBZ,UAAA;EAdiB;EAgBjB,YAAA;EARA;EAUA,YAAA;EAJA;EAMA,QAAA;EAFA;EAMA,GAAA;EAAA;EAEA,cAAA,GAAiB,IAAA,CAAK,WAAA;EAAL;EAIjB,IAAA;IACE,aAAA;IACA,OAAA;IACA,OAAA;MACE,aAAA;MACA,MAAA;MACA,IAAA;MACA,OAAA;IAAA;IAEF,eAAA;EAAA;EAKF;EAFA,eAAA;EAIwB;EAFxB,iBAAA;EA+BU;EA7BV,wBAAA;AAAA;AAAA,KA6BU,UAAA,YAAsB,aAAA,GAAgB,aAAA;EAChD,CAAA,EAAG,SAAA,CAAU,EAAA,EAAI,OAAA;EACjB,IAAA,EAAM,IAAA,ECrEsC;EDuE5C,GAAA;AAAA;;;;AArGF;;;iBC8BgB,iBAAA,CAAkB,UAAA,EAAY,UAAA;;;;;;AD5B9C;;;;;;;;;;;;iBCqKsB,IAAA,YACT,aAAA,GAAgB,aAAA,kBACX,SAAA,CAAU,EAAA,cAAA,CAE1B,EAAA,GAAK,EAAA,GAAK,EAAA,IACV,OAAA;EAAW,SAAA,GAAY,OAAA;EAAS,GAAA;AAAA,IAC/B,OAAA,CAAQ,UAAA,CAAW,EAAA,EAAI,OAAA;;;;;;;;;;;iBAoCV,YAAA,CACd,IAAA,EAAM,IAAA,EACN,UAAA,cACC,QAAA;;;;;;;;;;;;;iBA6Ba,wBAAA,CAAA;EAA8B,GAAA;AAAA"}
@@ -15,6 +15,7 @@ function normalizeConfig(userConfig) {
15
15
  defaultNS,
16
16
  ns: userConfig.ns ?? [defaultNS],
17
17
  localeInPath: userConfig.localeInPath ?? true,
18
+ hideDefaultLocale: userConfig.hideDefaultLocale ?? false,
18
19
  localePath: userConfig.localePath ?? "/locales",
19
20
  localeStructure: userConfig.localeStructure ?? "{{lng}}/{{ns}}",
20
21
  localeExtension: userConfig.localeExtension ?? "json",
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","names":[],"sources":["../../src/appRouter/config.ts","../../src/appRouter/server.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n","import { createInstance } from 'i18next'\nimport type { i18n as I18NextClient, Resource, Module, FlatNamespace, KeyPrefix } from 'i18next'\nimport resourcesToBackend from 'i18next-resources-to-backend'\nimport { cache } from 'react'\nimport { headers, cookies } from 'next/headers'\n\nimport type { I18nConfig, NormalizedConfig, GetTResult } from './types'\nimport { normalizeConfig } from './config'\n\nlet _config: NormalizedConfig | null = null\n\n// Module-level singleton: persists across requests within the same server process.\n// This is critical for custom backends (i18next-http-backend, i18next-locize-backend)\n// to avoid re-fetching translations on every request.\n// In serverless environments (Lambda, Cloud Functions, etc.), this lives as long as\n// the warm function instance — backends with reloadInterval will refresh automatically.\nlet _sharedInstance: I18NextClient | null = null\nlet _sharedInstancePromise: Promise<I18NextClient> | null = null\n\nfunction getConfig(): NormalizedConfig {\n if (!_config) {\n throw new Error(\n 'next-i18next: Server module not initialized. Call initServerI18next(config) in your root layout.'\n )\n }\n return _config\n}\n\n/**\n * Initialize the server-side i18next configuration.\n * Call this once in your root layout or a shared setup file.\n */\nexport function initServerI18next(userConfig: I18nConfig): void {\n _config = normalizeConfig(userConfig)\n}\n\nfunction hasCustomBackend(plugins: any[]): boolean {\n return plugins.some((b: Module) => b.type === 'backend')\n}\n\nfunction createResourceBackend(config: NormalizedConfig) {\n if (config.resourceLoader) {\n return resourcesToBackend(config.resourceLoader)\n }\n return resourcesToBackend(async (language: string, namespace: string) => {\n const filePath = `${config.localePath}/${config.localeStructure\n .replace('{{lng}}', language)\n .replace('{{ns}}', namespace)}.${config.localeExtension}`\n\n // Node.js runtime: read from filesystem\n if (typeof process !== 'undefined' && process.versions?.node) {\n try {\n const fs = await import('fs/promises')\n const pathMod = await import('path')\n const resolved = pathMod.resolve(process.cwd(), `public${filePath}`)\n const content = await fs.readFile(resolved, 'utf-8')\n return JSON.parse(content)\n } catch {\n throw new Error(\n `next-i18next: Could not read locale file \"public${filePath}\". ` +\n 'On serverless platforms (Vercel, AWS Lambda, etc.), files in public/ are served via CDN ' +\n 'but are NOT available on the filesystem at runtime. Use the `resourceLoader` option with ' +\n 'dynamic imports instead:\\n\\n' +\n ' resourceLoader: (language, namespace) =>\\n' +\n // eslint-disable-next-line no-template-curly-in-string\n ' import(`./public/locales/${language}/${namespace}.json`)\\n'\n )\n }\n }\n\n // Edge runtime: filesystem not available\n throw new Error(\n `next-i18next: Cannot load locale file \"${filePath}\" in Edge Runtime. ` +\n 'Provide pre-bundled `resources`, a custom `resourceLoader`, or use a custom backend (e.g. i18next-http-backend) via the `use` option.'\n )\n })\n}\n\n/**\n * Get or create the shared i18next instance.\n * The instance is created once and reused across all requests.\n * All languages are preloaded so that getFixedT(lng) works for any supported language.\n * Additional namespaces are loaded on demand and cached in the instance store.\n */\nasync function getSharedInstance(config: NormalizedConfig): Promise<I18NextClient> {\n if (_sharedInstance?.isInitialized) return _sharedInstance\n\n // Deduplicate concurrent init calls (multiple requests arriving while first init is in flight)\n if (_sharedInstancePromise) return _sharedInstancePromise\n\n _sharedInstancePromise = (async () => {\n const i18nInstance = createInstance()\n\n // Add a backend when needed:\n // - No resources provided → backend loads everything\n // - Resources provided with partialBundledLanguages → backend loads the rest\n // - Custom backend in config.use → user handles it, skip default backend\n const partialBundled = config.i18nextOptions?.partialBundledLanguages\n if ((!config.resources || partialBundled) && !hasCustomBackend(config.use)) {\n i18nInstance.use(createResourceBackend(config))\n }\n\n config.use.forEach((plugin: any) => i18nInstance.use(plugin))\n\n await i18nInstance.init({\n // No `lng` — the shared instance is language-neutral.\n // We use getFixedT(lng, ns) to get language-specific translators.\n lng: config.fallbackLng,\n ns: config.ns,\n defaultNS: config.defaultNS,\n fallbackLng: config.fallbackLng,\n supportedLngs: config.supportedLngs,\n nonExplicitSupportedLngs: config.nonExplicitSupportedLngs,\n fallbackNS: config.defaultNS,\n preload: config.supportedLngs, // preload ALL languages upfront\n interpolation: { escapeValue: false },\n ...(config.resources ? { resources: config.resources } : {}),\n ...config.i18nextOptions,\n })\n\n _sharedInstance = i18nInstance\n return i18nInstance\n })()\n\n return _sharedInstancePromise\n}\n\n// Per-request language detection, deduplicated within a single React render\nconst detectLanguage = cache(async (config: NormalizedConfig): Promise<string> => {\n const headerList = await headers()\n const fromHeader = headerList.get(config.headerName)\n if (fromHeader) return fromHeader\n\n const cookieStore = await cookies()\n const cookieValue = cookieStore.get(config.cookieName)?.value\n if (cookieValue) {\n if (config.supportedLngs.includes(cookieValue)) {\n return cookieValue\n }\n // nonExplicitSupportedLngs: e.g. cookie 'en' matches supported 'en-US'\n if (config.nonExplicitSupportedLngs) {\n const prefix = cookieValue.toLowerCase().split('-')[0]\n const match = config.supportedLngs.find(\n l => l.toLowerCase() === prefix || l.toLowerCase().split('-')[0] === prefix\n )\n if (match) return match\n }\n }\n\n return config.fallbackLng\n})\n\n/**\n * Get a translation function for use in Server Components, layouts, and generateMetadata.\n *\n * The underlying i18next instance is a **module-level singleton** that persists across\n * requests. This means custom backends (i18next-http-backend, i18next-locize-backend, etc.)\n * only fetch translations once (or according to their own reloadInterval), not on every request.\n *\n * @example\n * ```tsx\n * import { getT } from 'next-i18next/server'\n *\n * export default async function Page() {\n * const { t, i18n } = await getT('home')\n * return <h1>{t('heading')}</h1>\n * }\n * ```\n */\nexport async function getT<\n Ns extends FlatNamespace = FlatNamespace,\n KPrefix extends KeyPrefix<Ns> = undefined,\n>(\n ns?: Ns | Ns[],\n options: { keyPrefix?: KPrefix; lng?: string } = {},\n): Promise<GetTResult<Ns, KPrefix>> {\n const config = getConfig()\n\n const lng = options.lng || await detectLanguage(config)\n const i18nInstance = await getSharedInstance(config)\n\n // Load additional namespaces on demand if not already loaded\n const nsArray: string[] = ns\n ? (Array.isArray(ns) ? ns as string[] : [ns as string])\n : config.ns\n const missingNs = nsArray.filter(n => !i18nInstance.hasLoadedNamespace(n))\n if (missingNs.length > 0) {\n await i18nInstance.loadNamespaces(missingNs)\n }\n\n const resolvedNs = ns\n ? (Array.isArray(ns) ? ns[0] : ns) as string\n : config.defaultNS\n\n return {\n t: i18nInstance.getFixedT(lng, resolvedNs, options.keyPrefix as string | undefined),\n i18n: i18nInstance,\n lng,\n } as any\n}\n\n/**\n * Extract loaded resources from the server i18next instance for passing to I18nProvider.\n *\n * @example\n * ```tsx\n * const { i18n } = await getT()\n * const resources = getResources(i18n, ['common', 'footer'])\n * return <I18nProvider language={i18n.language} resources={resources}>{children}</I18nProvider>\n * ```\n */\nexport function getResources(\n i18n: I18NextClient,\n namespaces?: string[],\n): Resource {\n const resources: Resource = {}\n const store = i18n.store?.data || {}\n const nsFilter = namespaces ? new Set(namespaces) : null\n\n for (const lng of Object.keys(store)) {\n resources[lng] = {}\n for (const ns of Object.keys(store[lng])) {\n if (!nsFilter || nsFilter.has(ns)) {\n resources[lng][ns] = store[lng][ns]\n }\n }\n }\n\n return resources\n}\n\n/**\n * Helper for generateStaticParams — returns params for all supported languages.\n *\n * @example\n * ```tsx\n * import { generateI18nStaticParams } from 'next-i18next/server'\n *\n * export async function generateStaticParams() {\n * return generateI18nStaticParams()\n * }\n * ```\n */\nexport function generateI18nStaticParams(): { lng: string }[] {\n const config = getConfig()\n return config.supportedLngs.map(lng => ({ lng }))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.mjs","names":[],"sources":["../../src/appRouter/config.ts","../../src/appRouter/server.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n hideDefaultLocale: userConfig.hideDefaultLocale ?? false,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n","import { createInstance } from 'i18next'\nimport type { i18n as I18NextClient, Resource, Module, FlatNamespace, KeyPrefix } from 'i18next'\nimport resourcesToBackend from 'i18next-resources-to-backend'\nimport { cache } from 'react'\nimport { headers, cookies } from 'next/headers'\n\nimport type { I18nConfig, NormalizedConfig, GetTResult } from './types'\nimport { normalizeConfig } from './config'\n\nlet _config: NormalizedConfig | null = null\n\n// Module-level singleton: persists across requests within the same server process.\n// This is critical for custom backends (i18next-http-backend, i18next-locize-backend)\n// to avoid re-fetching translations on every request.\n// In serverless environments (Lambda, Cloud Functions, etc.), this lives as long as\n// the warm function instance — backends with reloadInterval will refresh automatically.\nlet _sharedInstance: I18NextClient | null = null\nlet _sharedInstancePromise: Promise<I18NextClient> | null = null\n\nfunction getConfig(): NormalizedConfig {\n if (!_config) {\n throw new Error(\n 'next-i18next: Server module not initialized. Call initServerI18next(config) in your root layout.'\n )\n }\n return _config\n}\n\n/**\n * Initialize the server-side i18next configuration.\n * Call this once in your root layout or a shared setup file.\n */\nexport function initServerI18next(userConfig: I18nConfig): void {\n _config = normalizeConfig(userConfig)\n}\n\nfunction hasCustomBackend(plugins: any[]): boolean {\n return plugins.some((b: Module) => b.type === 'backend')\n}\n\nfunction createResourceBackend(config: NormalizedConfig) {\n if (config.resourceLoader) {\n return resourcesToBackend(config.resourceLoader)\n }\n return resourcesToBackend(async (language: string, namespace: string) => {\n const filePath = `${config.localePath}/${config.localeStructure\n .replace('{{lng}}', language)\n .replace('{{ns}}', namespace)}.${config.localeExtension}`\n\n // Node.js runtime: read from filesystem\n if (typeof process !== 'undefined' && process.versions?.node) {\n try {\n const fs = await import('fs/promises')\n const pathMod = await import('path')\n const resolved = pathMod.resolve(process.cwd(), `public${filePath}`)\n const content = await fs.readFile(resolved, 'utf-8')\n return JSON.parse(content)\n } catch {\n throw new Error(\n `next-i18next: Could not read locale file \"public${filePath}\". ` +\n 'On serverless platforms (Vercel, AWS Lambda, etc.), files in public/ are served via CDN ' +\n 'but are NOT available on the filesystem at runtime. Use the `resourceLoader` option with ' +\n 'dynamic imports instead:\\n\\n' +\n ' resourceLoader: (language, namespace) =>\\n' +\n // eslint-disable-next-line no-template-curly-in-string\n ' import(`./public/locales/${language}/${namespace}.json`)\\n'\n )\n }\n }\n\n // Edge runtime: filesystem not available\n throw new Error(\n `next-i18next: Cannot load locale file \"${filePath}\" in Edge Runtime. ` +\n 'Provide pre-bundled `resources`, a custom `resourceLoader`, or use a custom backend (e.g. i18next-http-backend) via the `use` option.'\n )\n })\n}\n\n/**\n * Get or create the shared i18next instance.\n * The instance is created once and reused across all requests.\n * All languages are preloaded so that getFixedT(lng) works for any supported language.\n * Additional namespaces are loaded on demand and cached in the instance store.\n */\nasync function getSharedInstance(config: NormalizedConfig): Promise<I18NextClient> {\n if (_sharedInstance?.isInitialized) return _sharedInstance\n\n // Deduplicate concurrent init calls (multiple requests arriving while first init is in flight)\n if (_sharedInstancePromise) return _sharedInstancePromise\n\n _sharedInstancePromise = (async () => {\n const i18nInstance = createInstance()\n\n // Add a backend when needed:\n // - No resources provided → backend loads everything\n // - Resources provided with partialBundledLanguages → backend loads the rest\n // - Custom backend in config.use → user handles it, skip default backend\n const partialBundled = config.i18nextOptions?.partialBundledLanguages\n if ((!config.resources || partialBundled) && !hasCustomBackend(config.use)) {\n i18nInstance.use(createResourceBackend(config))\n }\n\n config.use.forEach((plugin: any) => i18nInstance.use(plugin))\n\n await i18nInstance.init({\n // No `lng` — the shared instance is language-neutral.\n // We use getFixedT(lng, ns) to get language-specific translators.\n lng: config.fallbackLng,\n ns: config.ns,\n defaultNS: config.defaultNS,\n fallbackLng: config.fallbackLng,\n supportedLngs: config.supportedLngs,\n nonExplicitSupportedLngs: config.nonExplicitSupportedLngs,\n fallbackNS: config.defaultNS,\n preload: config.supportedLngs, // preload ALL languages upfront\n interpolation: { escapeValue: false },\n ...(config.resources ? { resources: config.resources } : {}),\n ...config.i18nextOptions,\n })\n\n _sharedInstance = i18nInstance\n return i18nInstance\n })()\n\n return _sharedInstancePromise\n}\n\n// Per-request language detection, deduplicated within a single React render\nconst detectLanguage = cache(async (config: NormalizedConfig): Promise<string> => {\n const headerList = await headers()\n const fromHeader = headerList.get(config.headerName)\n if (fromHeader) return fromHeader\n\n const cookieStore = await cookies()\n const cookieValue = cookieStore.get(config.cookieName)?.value\n if (cookieValue) {\n if (config.supportedLngs.includes(cookieValue)) {\n return cookieValue\n }\n // nonExplicitSupportedLngs: e.g. cookie 'en' matches supported 'en-US'\n if (config.nonExplicitSupportedLngs) {\n const prefix = cookieValue.toLowerCase().split('-')[0]\n const match = config.supportedLngs.find(\n l => l.toLowerCase() === prefix || l.toLowerCase().split('-')[0] === prefix\n )\n if (match) return match\n }\n }\n\n return config.fallbackLng\n})\n\n/**\n * Get a translation function for use in Server Components, layouts, and generateMetadata.\n *\n * The underlying i18next instance is a **module-level singleton** that persists across\n * requests. This means custom backends (i18next-http-backend, i18next-locize-backend, etc.)\n * only fetch translations once (or according to their own reloadInterval), not on every request.\n *\n * @example\n * ```tsx\n * import { getT } from 'next-i18next/server'\n *\n * export default async function Page() {\n * const { t, i18n } = await getT('home')\n * return <h1>{t('heading')}</h1>\n * }\n * ```\n */\nexport async function getT<\n Ns extends FlatNamespace = FlatNamespace,\n KPrefix extends KeyPrefix<Ns> = undefined,\n>(\n ns?: Ns | Ns[],\n options: { keyPrefix?: KPrefix; lng?: string } = {},\n): Promise<GetTResult<Ns, KPrefix>> {\n const config = getConfig()\n\n const lng = options.lng || await detectLanguage(config)\n const i18nInstance = await getSharedInstance(config)\n\n // Load additional namespaces on demand if not already loaded\n const nsArray: string[] = ns\n ? (Array.isArray(ns) ? ns as string[] : [ns as string])\n : config.ns\n const missingNs = nsArray.filter(n => !i18nInstance.hasLoadedNamespace(n))\n if (missingNs.length > 0) {\n await i18nInstance.loadNamespaces(missingNs)\n }\n\n const resolvedNs = ns\n ? (Array.isArray(ns) ? ns[0] : ns) as string\n : config.defaultNS\n\n return {\n t: i18nInstance.getFixedT(lng, resolvedNs, options.keyPrefix as string | undefined),\n i18n: i18nInstance,\n lng,\n } as any\n}\n\n/**\n * Extract loaded resources from the server i18next instance for passing to I18nProvider.\n *\n * @example\n * ```tsx\n * const { i18n } = await getT()\n * const resources = getResources(i18n, ['common', 'footer'])\n * return <I18nProvider language={i18n.language} resources={resources}>{children}</I18nProvider>\n * ```\n */\nexport function getResources(\n i18n: I18NextClient,\n namespaces?: string[],\n): Resource {\n const resources: Resource = {}\n const store = i18n.store?.data || {}\n const nsFilter = namespaces ? new Set(namespaces) : null\n\n for (const lng of Object.keys(store)) {\n resources[lng] = {}\n for (const ns of Object.keys(store[lng])) {\n if (!nsFilter || nsFilter.has(ns)) {\n resources[lng][ns] = store[lng][ns]\n }\n }\n }\n\n return resources\n}\n\n/**\n * Helper for generateStaticParams — returns params for all supported languages.\n *\n * @example\n * ```tsx\n * import { generateI18nStaticParams } from 'next-i18next/server'\n *\n * export async function generateStaticParams() {\n * return generateI18nStaticParams()\n * }\n * ```\n */\nexport function generateI18nStaticParams(): { lng: string }[] {\n const config = getConfig()\n return config.supportedLngs.map(lng => ({ lng }))\n}\n"],"mappings":";;;;;AAMA,SAAgB,gBAAgB,YAA0C;CAExE,MAAM,gBAAgB,WAAW,iBAC/B,WAAW,MAAM,SAAS,QAAQ,MAAc,MAAM,UAAU,IAChE,CAAC,KAAK;CACR,MAAM,cAAc,WAAW,eAC7B,WAAW,MAAM,iBACjB,cAAc;AAEhB,KAAI,CAAC,YACH,OAAM,IAAI,MAAM,gEAAgE;AAElF,KAAI,cAAc,WAAW,EAC3B,OAAM,IAAI,MAAM,mFAAmF;CAGrG,MAAM,YAAY,WAAW,aAAa;AAE1C,QAAO;EACL;EACA;EACA;EACA,IAAI,WAAW,MAAM,CAAC,UAAU;EAChC,cAAc,WAAW,gBAAgB;EACzC,mBAAmB,WAAW,qBAAqB;EACnD,YAAY,WAAW,cAAc;EACrC,iBAAiB,WAAW,mBAAmB;EAC/C,iBAAiB,WAAW,mBAAmB;EAC/C,YAAY,WAAW,cAAc;EACrC,YAAY,WAAW,cAAc;EACrC,cAAc,WAAW,gBAAgB,MAAM,KAAK,KAAK;EACzD,cAAc,WAAW,gBAAgB;GAAC;GAAQ;GAAU;GAAU;EACtE,UAAU,WAAW;EACrB,WAAW,WAAW;EACtB,gBAAgB,WAAW;EAC3B,KAAK,WAAW,OAAO,EAAE;EACzB,gBAAiB,WAAW,kBAAkB,EAAE;EAChD,0BAA0B,WAAW,4BAA4B;EAEjE,MAAM,WAAW;EACjB,iBAAiB,WAAW;EAC5B,mBAAmB,WAAW;EAC/B;;;;ACvCH,IAAI,UAAmC;AAOvC,IAAI,kBAAwC;AAC5C,IAAI,yBAAwD;AAE5D,SAAS,YAA8B;AACrC,KAAI,CAAC,QACH,OAAM,IAAI,MACR,mGACD;AAEH,QAAO;;;;;;AAOT,SAAgB,kBAAkB,YAA8B;AAC9D,WAAU,gBAAgB,WAAW;;AAGvC,SAAS,iBAAiB,SAAyB;AACjD,QAAO,QAAQ,MAAM,MAAc,EAAE,SAAS,UAAU;;AAG1D,SAAS,sBAAsB,QAA0B;AACvD,KAAI,OAAO,eACT,QAAO,mBAAmB,OAAO,eAAe;AAElD,QAAO,mBAAmB,OAAO,UAAkB,cAAsB;EACvE,MAAM,WAAW,GAAG,OAAO,WAAW,GAAG,OAAO,gBAC7C,QAAQ,WAAW,SAAS,CAC5B,QAAQ,UAAU,UAAU,CAAC,GAAG,OAAO;AAG1C,MAAI,OAAO,YAAY,eAAe,QAAQ,UAAU,KACtD,KAAI;GACF,MAAM,KAAK,MAAM,OAAO;GAExB,MAAM,YADU,MAAM,OAAO,SACJ,QAAQ,QAAQ,KAAK,EAAE,SAAS,WAAW;GACpE,MAAM,UAAU,MAAM,GAAG,SAAS,UAAU,QAAQ;AACpD,UAAO,KAAK,MAAM,QAAQ;UACpB;AACN,SAAM,IAAI,MACR,mDAAmD,SAAS;;;;EAO7D;;AAKL,QAAM,IAAI,MACR,0CAA0C,SAAS,gKAEpD;GACD;;;;;;;;AASJ,eAAe,kBAAkB,QAAkD;AACjF,KAAI,iBAAiB,cAAe,QAAO;AAG3C,KAAI,uBAAwB,QAAO;AAEnC,2BAA0B,YAAY;EACpC,MAAM,eAAe,gBAAgB;EAMrC,MAAM,iBAAiB,OAAO,gBAAgB;AAC9C,OAAK,CAAC,OAAO,aAAa,mBAAmB,CAAC,iBAAiB,OAAO,IAAI,CACxE,cAAa,IAAI,sBAAsB,OAAO,CAAC;AAGjD,SAAO,IAAI,SAAS,WAAgB,aAAa,IAAI,OAAO,CAAC;AAE7D,QAAM,aAAa,KAAK;GAGtB,KAAK,OAAO;GACZ,IAAI,OAAO;GACX,WAAW,OAAO;GAClB,aAAa,OAAO;GACpB,eAAe,OAAO;GACtB,0BAA0B,OAAO;GACjC,YAAY,OAAO;GACnB,SAAS,OAAO;GAChB,eAAe,EAAE,aAAa,OAAO;GACrC,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,WAAW,GAAG,EAAE;GAC3D,GAAG,OAAO;GACX,CAAC;AAEF,oBAAkB;AAClB,SAAO;KACL;AAEJ,QAAO;;AAIT,MAAM,iBAAiB,MAAM,OAAO,WAA8C;CAEhF,MAAM,cADa,MAAM,SAAS,EACJ,IAAI,OAAO,WAAW;AACpD,KAAI,WAAY,QAAO;CAGvB,MAAM,eADc,MAAM,SAAS,EACH,IAAI,OAAO,WAAW,EAAE;AACxD,KAAI,aAAa;AACf,MAAI,OAAO,cAAc,SAAS,YAAY,CAC5C,QAAO;AAGT,MAAI,OAAO,0BAA0B;GACnC,MAAM,SAAS,YAAY,aAAa,CAAC,MAAM,IAAI,CAAC;GACpD,MAAM,QAAQ,OAAO,cAAc,MACjC,MAAK,EAAE,aAAa,KAAK,UAAU,EAAE,aAAa,CAAC,MAAM,IAAI,CAAC,OAAO,OACtE;AACD,OAAI,MAAO,QAAO;;;AAItB,QAAO,OAAO;EACd;;;;;;;;;;;;;;;;;;AAmBF,eAAsB,KAIpB,IACA,UAAiD,EAAE,EACjB;CAClC,MAAM,SAAS,WAAW;CAE1B,MAAM,MAAM,QAAQ,OAAO,MAAM,eAAe,OAAO;CACvD,MAAM,eAAe,MAAM,kBAAkB,OAAO;CAMpD,MAAM,aAHoB,KACrB,MAAM,QAAQ,GAAG,GAAG,KAAiB,CAAC,GAAa,GACpD,OAAO,IACe,QAAO,MAAK,CAAC,aAAa,mBAAmB,EAAE,CAAC;AAC1E,KAAI,UAAU,SAAS,EACrB,OAAM,aAAa,eAAe,UAAU;CAG9C,MAAM,aAAa,KACd,MAAM,QAAQ,GAAG,GAAG,GAAG,KAAK,KAC7B,OAAO;AAEX,QAAO;EACL,GAAG,aAAa,UAAU,KAAK,YAAY,QAAQ,UAAgC;EACnF,MAAM;EACN;EACD;;;;;;;;;;;;AAaH,SAAgB,aACd,MACA,YACU;CACV,MAAM,YAAsB,EAAE;CAC9B,MAAM,QAAQ,KAAK,OAAO,QAAQ,EAAE;CACpC,MAAM,WAAW,aAAa,IAAI,IAAI,WAAW,GAAG;AAEpD,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,EAAE;AACpC,YAAU,OAAO,EAAE;AACnB,OAAK,MAAM,MAAM,OAAO,KAAK,MAAM,KAAK,CACtC,KAAI,CAAC,YAAY,SAAS,IAAI,GAAG,CAC/B,WAAU,KAAK,MAAM,MAAM,KAAK;;AAKtC,QAAO;;;;;;;;;;;;;;AAeT,SAAgB,2BAA8C;AAE5D,QADe,WAAW,CACZ,cAAc,KAAI,SAAQ,EAAE,KAAK,EAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-i18next",
3
- "version": "16.0.2",
3
+ "version": "16.0.3",
4
4
  "repository": "git@github.com:i18next/next-i18next.git",
5
5
  "author": "i18next",
6
6
  "funding": [