add dist
This commit is contained in:
+34
@@ -0,0 +1,34 @@
|
||||
import type { LocalizedSlugs } from './localizedPath.js';
|
||||
import type { I18nConfig } from './types.js';
|
||||
/**
|
||||
* A localized field as Payload returns it when queried with `locale: 'all'`:
|
||||
* a map of locale code → value.
|
||||
*/
|
||||
type LocalizedField = Record<string, unknown>;
|
||||
type GetLocalizedSlugsArgs = {
|
||||
config: I18nConfig;
|
||||
/**
|
||||
* The document's localized slug field, as returned by Payload with
|
||||
* `locale: 'all'` — e.g. { pl: 'strona-glowna', en: 'home' }.
|
||||
*/
|
||||
slugField: LocalizedField | null | undefined;
|
||||
};
|
||||
/**
|
||||
* Normalizes a document's localized slug field into the LocalizedSlugs
|
||||
* contract consumed by buildLocalizedPath / switchLocalePath.
|
||||
*
|
||||
* Pure function — the template fetches the document (payload.findByID with
|
||||
* `locale: 'all'`, where the slug field must be `localized: true`) and passes
|
||||
* the raw field in. The plugin never touches the database.
|
||||
*
|
||||
* Keeps only entries whose locale is configured and whose slug is a
|
||||
* non-empty string, so callers get a clean, trustworthy map.
|
||||
*
|
||||
* @example
|
||||
* const doc = await payload.findByID({ collection: 'pages', id, locale: 'all' })
|
||||
* const slugs = getLocalizedSlugs({ slugField: doc.slug, config })
|
||||
* // → { pl: 'strona-glowna', en: 'home' }
|
||||
* switchLocalePath({ slugs, targetLocale: 'en', config }) // → '/en'
|
||||
*/
|
||||
export declare function getLocalizedSlugs({ config, slugField }: GetLocalizedSlugsArgs): LocalizedSlugs;
|
||||
export {};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { isValidLocale } from './helpers.js';
|
||||
/**
|
||||
* Normalizes a document's localized slug field into the LocalizedSlugs
|
||||
* contract consumed by buildLocalizedPath / switchLocalePath.
|
||||
*
|
||||
* Pure function — the template fetches the document (payload.findByID with
|
||||
* `locale: 'all'`, where the slug field must be `localized: true`) and passes
|
||||
* the raw field in. The plugin never touches the database.
|
||||
*
|
||||
* Keeps only entries whose locale is configured and whose slug is a
|
||||
* non-empty string, so callers get a clean, trustworthy map.
|
||||
*
|
||||
* @example
|
||||
* const doc = await payload.findByID({ collection: 'pages', id, locale: 'all' })
|
||||
* const slugs = getLocalizedSlugs({ slugField: doc.slug, config })
|
||||
* // → { pl: 'strona-glowna', en: 'home' }
|
||||
* switchLocalePath({ slugs, targetLocale: 'en', config }) // → '/en'
|
||||
*/ export function getLocalizedSlugs({ config, slugField }) {
|
||||
const result = {};
|
||||
if (!slugField || typeof slugField !== 'object') {
|
||||
return result;
|
||||
}
|
||||
for (const [locale, value] of Object.entries(slugField)){
|
||||
if (!isValidLocale(locale, config)) {
|
||||
continue;
|
||||
}
|
||||
if (typeof value !== 'string') {
|
||||
continue;
|
||||
}
|
||||
const slug = value.trim();
|
||||
if (!slug) {
|
||||
continue;
|
||||
}
|
||||
result[locale] = slug;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=getLocalizedSlugs.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/i18n/getLocalizedSlugs.ts"],"sourcesContent":["import type { LocalizedSlugs } from './localizedPath.js'\nimport type { I18nConfig } from './types.js'\n\nimport { isValidLocale } from './helpers.js'\n\n/**\n * A localized field as Payload returns it when queried with `locale: 'all'`:\n * a map of locale code → value.\n */\ntype LocalizedField = Record<string, unknown>\n\ntype GetLocalizedSlugsArgs = {\n config: I18nConfig\n /**\n * The document's localized slug field, as returned by Payload with\n * `locale: 'all'` — e.g. { pl: 'strona-glowna', en: 'home' }.\n */\n slugField: LocalizedField | null | undefined\n}\n\n/**\n * Normalizes a document's localized slug field into the LocalizedSlugs\n * contract consumed by buildLocalizedPath / switchLocalePath.\n *\n * Pure function — the template fetches the document (payload.findByID with\n * `locale: 'all'`, where the slug field must be `localized: true`) and passes\n * the raw field in. The plugin never touches the database.\n *\n * Keeps only entries whose locale is configured and whose slug is a\n * non-empty string, so callers get a clean, trustworthy map.\n *\n * @example\n * const doc = await payload.findByID({ collection: 'pages', id, locale: 'all' })\n * const slugs = getLocalizedSlugs({ slugField: doc.slug, config })\n * // → { pl: 'strona-glowna', en: 'home' }\n * switchLocalePath({ slugs, targetLocale: 'en', config }) // → '/en'\n */\nexport function getLocalizedSlugs({ config, slugField }: GetLocalizedSlugsArgs): LocalizedSlugs {\n const result: LocalizedSlugs = {}\n\n if (!slugField || typeof slugField !== 'object') {\n return result\n }\n\n for (const [locale, value] of Object.entries(slugField)) {\n if (!isValidLocale(locale, config)) {continue}\n if (typeof value !== 'string') {continue}\n\n const slug = value.trim()\n if (!slug) {continue}\n\n result[locale] = slug\n }\n\n return result\n}\n"],"names":["isValidLocale","getLocalizedSlugs","config","slugField","result","locale","value","Object","entries","slug","trim"],"mappings":"AAGA,SAASA,aAAa,QAAQ,eAAc;AAiB5C;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,kBAAkB,EAAEC,MAAM,EAAEC,SAAS,EAAyB;IAC5E,MAAMC,SAAyB,CAAC;IAEhC,IAAI,CAACD,aAAa,OAAOA,cAAc,UAAU;QAC/C,OAAOC;IACT;IAEA,KAAK,MAAM,CAACC,QAAQC,MAAM,IAAIC,OAAOC,OAAO,CAACL,WAAY;QACvD,IAAI,CAACH,cAAcK,QAAQH,SAAS;YAAC;QAAQ;QAC7C,IAAI,OAAOI,UAAU,UAAU;YAAC;QAAQ;QAExC,MAAMG,OAAOH,MAAMI,IAAI;QACvB,IAAI,CAACD,MAAM;YAAC;QAAQ;QAEpBL,MAAM,CAACC,OAAO,GAAGI;IACnB;IAEA,OAAOL;AACT"}
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
import type { I18nConfig, LocaleDefinition } from './types.js';
|
||||
/**
|
||||
* Returns all configured locale codes.
|
||||
*/
|
||||
export declare function getLocaleCodes(config: I18nConfig): string[];
|
||||
/**
|
||||
* Returns the default locale code.
|
||||
*/
|
||||
export declare function getDefaultLocale(config: I18nConfig): string;
|
||||
/**
|
||||
* Checks if a string is a valid configured locale code.
|
||||
*/
|
||||
export declare function isValidLocale(code: string, config: I18nConfig): boolean;
|
||||
/**
|
||||
* Returns the full locale definition for a given code, or undefined if not found.
|
||||
*/
|
||||
export declare function getLocaleDefinition(code: string, config: I18nConfig): LocaleDefinition | undefined;
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Returns all configured locale codes.
|
||||
*/ export function getLocaleCodes(config) {
|
||||
return config.locales.map((locale)=>locale.code);
|
||||
}
|
||||
/**
|
||||
* Returns the default locale code.
|
||||
*/ export function getDefaultLocale(config) {
|
||||
return config.defaultLocale;
|
||||
}
|
||||
/**
|
||||
* Checks if a string is a valid configured locale code.
|
||||
*/ export function isValidLocale(code, config) {
|
||||
return config.locales.some((locale)=>locale.code === code);
|
||||
}
|
||||
/**
|
||||
* Returns the full locale definition for a given code, or undefined if not found.
|
||||
*/ export function getLocaleDefinition(code, config) {
|
||||
return config.locales.find((locale)=>locale.code === code);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=helpers.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/i18n/helpers.ts"],"sourcesContent":["import type { I18nConfig, LocaleDefinition } from './types.js'\n\n/**\n * Returns all configured locale codes.\n */\nexport function getLocaleCodes(config: I18nConfig): string[] {\n return config.locales.map((locale) => locale.code)\n}\n\n/**\n * Returns the default locale code.\n */\nexport function getDefaultLocale(config: I18nConfig): string {\n return config.defaultLocale\n}\n\n/**\n * Checks if a string is a valid configured locale code.\n */\nexport function isValidLocale(code: string, config: I18nConfig): boolean {\n return config.locales.some((locale) => locale.code === code)\n}\n\n/**\n * Returns the full locale definition for a given code, or undefined if not found.\n */\nexport function getLocaleDefinition(\n code: string,\n config: I18nConfig,\n): LocaleDefinition | undefined {\n return config.locales.find((locale) => locale.code === code)\n}\n"],"names":["getLocaleCodes","config","locales","map","locale","code","getDefaultLocale","defaultLocale","isValidLocale","some","getLocaleDefinition","find"],"mappings":"AAEA;;CAEC,GACD,OAAO,SAASA,eAAeC,MAAkB;IAC/C,OAAOA,OAAOC,OAAO,CAACC,GAAG,CAAC,CAACC,SAAWA,OAAOC,IAAI;AACnD;AAEA;;CAEC,GACD,OAAO,SAASC,iBAAiBL,MAAkB;IACjD,OAAOA,OAAOM,aAAa;AAC7B;AAEA;;CAEC,GACD,OAAO,SAASC,cAAcH,IAAY,EAAEJ,MAAkB;IAC5D,OAAOA,OAAOC,OAAO,CAACO,IAAI,CAAC,CAACL,SAAWA,OAAOC,IAAI,KAAKA;AACzD;AAEA;;CAEC,GACD,OAAO,SAASK,oBACdL,IAAY,EACZJ,MAAkB;IAElB,OAAOA,OAAOC,OAAO,CAACS,IAAI,CAAC,CAACP,SAAWA,OAAOC,IAAI,KAAKA;AACzD"}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
export { getLocalizedSlugs } from './getLocalizedSlugs.js';
|
||||
export { getDefaultLocale, getLocaleCodes, getLocaleDefinition, isValidLocale } from './helpers.js';
|
||||
export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './localeMiddleware.js';
|
||||
export type { LocaleMiddlewareResult } from './localeMiddleware.js';
|
||||
export { buildLocalizationConfig } from './localizationConfig.js';
|
||||
export { buildLocalizedPath, switchLocalePath } from './localizedPath.js';
|
||||
export type { LocalizedSlugs } from './localizedPath.js';
|
||||
export { LOCALE_COOKIE_NAME, matchAcceptLanguage, negotiateLocale } from './negotiateLocale.js';
|
||||
export type { I18nConfig, LocaleDefinition } from './types.js';
|
||||
export { validateI18nConfig } from './validation.js';
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
export { getLocalizedSlugs } from './getLocalizedSlugs.js';
|
||||
export { getDefaultLocale, getLocaleCodes, getLocaleDefinition, isValidLocale } from './helpers.js';
|
||||
export { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './localeMiddleware.js';
|
||||
export { buildLocalizationConfig } from './localizationConfig.js';
|
||||
export { buildLocalizedPath, switchLocalePath } from './localizedPath.js';
|
||||
export { LOCALE_COOKIE_NAME, matchAcceptLanguage, negotiateLocale } from './negotiateLocale.js';
|
||||
export { validateI18nConfig } from './validation.js';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/i18n/index.ts"],"sourcesContent":["export { getLocalizedSlugs } from './getLocalizedSlugs.js'\nexport { getDefaultLocale, getLocaleCodes, getLocaleDefinition, isValidLocale } from './helpers.js'\nexport { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from './localeMiddleware.js'\nexport type { LocaleMiddlewareResult } from './localeMiddleware.js'\nexport { buildLocalizationConfig } from './localizationConfig.js'\nexport { buildLocalizedPath, switchLocalePath } from './localizedPath.js'\nexport type { LocalizedSlugs } from './localizedPath.js'\nexport { LOCALE_COOKIE_NAME, matchAcceptLanguage, negotiateLocale } from './negotiateLocale.js'\nexport type { I18nConfig, LocaleDefinition } from './types.js'\nexport { validateI18nConfig } from './validation.js'\n"],"names":["getLocalizedSlugs","getDefaultLocale","getLocaleCodes","getLocaleDefinition","isValidLocale","createLocaleMiddleware","DEFAULT_MIDDLEWARE_MATCHER","buildLocalizationConfig","buildLocalizedPath","switchLocalePath","LOCALE_COOKIE_NAME","matchAcceptLanguage","negotiateLocale","validateI18nConfig"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,yBAAwB;AAC1D,SAASC,gBAAgB,EAAEC,cAAc,EAAEC,mBAAmB,EAAEC,aAAa,QAAQ,eAAc;AACnG,SAASC,sBAAsB,EAAEC,0BAA0B,QAAQ,wBAAuB;AAE1F,SAASC,uBAAuB,QAAQ,0BAAyB;AACjE,SAASC,kBAAkB,EAAEC,gBAAgB,QAAQ,qBAAoB;AAEzE,SAASC,kBAAkB,EAAEC,mBAAmB,EAAEC,eAAe,QAAQ,uBAAsB;AAE/F,SAASC,kBAAkB,QAAQ,kBAAiB"}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import type { I18nConfig } from './types.js';
|
||||
/**
|
||||
* Minimal request shape the middleware reads. Kept structural so the plugin
|
||||
* doesn't hard-depend on next/server types; a Next.js `NextRequest` satisfies it.
|
||||
*/
|
||||
type MiddlewareRequest = {
|
||||
cookies: {
|
||||
get: (name: string) => {
|
||||
value: string;
|
||||
} | undefined;
|
||||
};
|
||||
headers: {
|
||||
get: (name: string) => null | string;
|
||||
};
|
||||
nextUrl: {
|
||||
clone: () => URL;
|
||||
pathname: string;
|
||||
search: string;
|
||||
};
|
||||
url: string;
|
||||
};
|
||||
/**
|
||||
* What the factory returns — the caller (in Next next-middleware.ts) decides how to
|
||||
* act: `redirect` means send a 307 to `location` and set the locale cookie;
|
||||
* `next` means let the request pass through untouched.
|
||||
*/
|
||||
export type LocaleMiddlewareResult = {
|
||||
cookie: {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
location: string;
|
||||
type: 'redirect';
|
||||
} | {
|
||||
type: 'next';
|
||||
};
|
||||
type CreateLocaleMiddlewareArgs = {
|
||||
config: I18nConfig;
|
||||
/** Cookie name for the locale choice. Defaults to LOCALE_COOKIE_NAME. */
|
||||
cookieName?: string;
|
||||
};
|
||||
/**
|
||||
* Builds locale-routing logic for Next.js middleware.
|
||||
*
|
||||
* Behavior:
|
||||
* - path already starts with a valid locale (/pl/...) → pass through
|
||||
* - any other path (/, /o-nas) → redirect to /{locale}{path}, where locale
|
||||
* comes from negotiateLocale (cookie → Accept-Language → default)
|
||||
* - the chosen locale is written to a cookie so the next visit is stable
|
||||
*
|
||||
* The plugin returns a decision; the thin next-middleware.ts in the client project
|
||||
* turns it into a NextResponse. This keeps all logic in the plugin while
|
||||
* respecting that next-middleware.ts must physically live in the client app.
|
||||
*
|
||||
* @example
|
||||
* // next-middleware.ts (client project) — one wiring file, no logic:
|
||||
* import { NextResponse } from 'next/server'
|
||||
* import { localeMiddleware } from './ipal.middleware' // created from this factory
|
||||
* export function middleware(req) {
|
||||
* const r = localeMiddleware(req)
|
||||
* if (r.type === 'next') return NextResponse.next()
|
||||
* const res = NextResponse.redirect(r.location)
|
||||
* res.cookies.set(r.cookie.name, r.cookie.value)
|
||||
* return res
|
||||
* }
|
||||
*/
|
||||
export declare function createLocaleMiddleware({ config, cookieName, }: CreateLocaleMiddlewareArgs): (request: MiddlewareRequest) => LocaleMiddlewareResult;
|
||||
/**
|
||||
* Default Next.js middleware matcher: run on everything except API routes, the
|
||||
* admin panel, Next internals, and files with an extension (static assets).
|
||||
*/
|
||||
export declare const DEFAULT_MIDDLEWARE_MATCHER: string[];
|
||||
export {};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import { isValidLocale, LOCALE_COOKIE_NAME, negotiateLocale } from '../i18n/index.js';
|
||||
/**
|
||||
* First path segment of a URL pathname, or '' for root.
|
||||
* '/pl/o-nas' → 'pl', '/o-nas' → 'o-nas', '/' → ''.
|
||||
*/ function firstSegment(pathname) {
|
||||
return pathname.split('/').filter(Boolean)[0] ?? '';
|
||||
}
|
||||
/**
|
||||
* Builds locale-routing logic for Next.js middleware.
|
||||
*
|
||||
* Behavior:
|
||||
* - path already starts with a valid locale (/pl/...) → pass through
|
||||
* - any other path (/, /o-nas) → redirect to /{locale}{path}, where locale
|
||||
* comes from negotiateLocale (cookie → Accept-Language → default)
|
||||
* - the chosen locale is written to a cookie so the next visit is stable
|
||||
*
|
||||
* The plugin returns a decision; the thin next-middleware.ts in the client project
|
||||
* turns it into a NextResponse. This keeps all logic in the plugin while
|
||||
* respecting that next-middleware.ts must physically live in the client app.
|
||||
*
|
||||
* @example
|
||||
* // next-middleware.ts (client project) — one wiring file, no logic:
|
||||
* import { NextResponse } from 'next/server'
|
||||
* import { localeMiddleware } from './ipal.middleware' // created from this factory
|
||||
* export function middleware(req) {
|
||||
* const r = localeMiddleware(req)
|
||||
* if (r.type === 'next') return NextResponse.next()
|
||||
* const res = NextResponse.redirect(r.location)
|
||||
* res.cookies.set(r.cookie.name, r.cookie.value)
|
||||
* return res
|
||||
* }
|
||||
*/ export function createLocaleMiddleware({ config, cookieName = LOCALE_COOKIE_NAME }) {
|
||||
return function localeMiddleware(request) {
|
||||
const { pathname } = request.nextUrl;
|
||||
// Already locale-prefixed → nothing to do
|
||||
if (isValidLocale(firstSegment(pathname), config)) {
|
||||
return {
|
||||
type: 'next'
|
||||
};
|
||||
}
|
||||
// Resolve the locale to use
|
||||
const locale = negotiateLocale({
|
||||
acceptLanguage: request.headers.get('accept-language'),
|
||||
config,
|
||||
cookieLocale: request.cookies.get(cookieName)?.value ?? null
|
||||
});
|
||||
// Redirect to the locale-prefixed path, preserving the rest
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = `/${locale}${pathname === '/' ? '' : pathname}`;
|
||||
return {
|
||||
type: 'redirect',
|
||||
cookie: {
|
||||
name: cookieName,
|
||||
value: locale
|
||||
},
|
||||
location: url.toString()
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Default Next.js middleware matcher: run on everything except API routes, the
|
||||
* admin panel, Next internals, and files with an extension (static assets).
|
||||
*/ export const DEFAULT_MIDDLEWARE_MATCHER = [
|
||||
'/((?!api|admin|_next|.*\\..*).*)'
|
||||
];
|
||||
|
||||
//# sourceMappingURL=localeMiddleware.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/i18n/localeMiddleware.ts"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nimport { isValidLocale, LOCALE_COOKIE_NAME, negotiateLocale } from '../i18n/index.js'\n\n/**\n * Minimal request shape the middleware reads. Kept structural so the plugin\n * doesn't hard-depend on next/server types; a Next.js `NextRequest` satisfies it.\n */\ntype MiddlewareRequest = {\n cookies: { get: (name: string) => { value: string } | undefined }\n headers: { get: (name: string) => null | string }\n nextUrl: { clone: () => URL; pathname: string; search: string }\n url: string\n}\n\n/**\n * What the factory returns — the caller (in Next next-middleware.ts) decides how to\n * act: `redirect` means send a 307 to `location` and set the locale cookie;\n * `next` means let the request pass through untouched.\n */\nexport type LocaleMiddlewareResult =\n { cookie: { name: string; value: string }; location: string; type: 'redirect' } | { type: 'next' }\n\ntype CreateLocaleMiddlewareArgs = {\n config: I18nConfig\n /** Cookie name for the locale choice. Defaults to LOCALE_COOKIE_NAME. */\n cookieName?: string\n}\n\n/**\n * First path segment of a URL pathname, or '' for root.\n * '/pl/o-nas' → 'pl', '/o-nas' → 'o-nas', '/' → ''.\n */\nfunction firstSegment(pathname: string): string {\n return pathname.split('/').filter(Boolean)[0] ?? ''\n}\n\n/**\n * Builds locale-routing logic for Next.js middleware.\n *\n * Behavior:\n * - path already starts with a valid locale (/pl/...) → pass through\n * - any other path (/, /o-nas) → redirect to /{locale}{path}, where locale\n * comes from negotiateLocale (cookie → Accept-Language → default)\n * - the chosen locale is written to a cookie so the next visit is stable\n *\n * The plugin returns a decision; the thin next-middleware.ts in the client project\n * turns it into a NextResponse. This keeps all logic in the plugin while\n * respecting that next-middleware.ts must physically live in the client app.\n *\n * @example\n * // next-middleware.ts (client project) — one wiring file, no logic:\n * import { NextResponse } from 'next/server'\n * import { localeMiddleware } from './ipal.middleware' // created from this factory\n * export function middleware(req) {\n * const r = localeMiddleware(req)\n * if (r.type === 'next') return NextResponse.next()\n * const res = NextResponse.redirect(r.location)\n * res.cookies.set(r.cookie.name, r.cookie.value)\n * return res\n * }\n */\nexport function createLocaleMiddleware({\n config,\n cookieName = LOCALE_COOKIE_NAME,\n}: CreateLocaleMiddlewareArgs) {\n return function localeMiddleware(request: MiddlewareRequest): LocaleMiddlewareResult {\n const { pathname } = request.nextUrl\n\n // Already locale-prefixed → nothing to do\n if (isValidLocale(firstSegment(pathname), config)) {\n return { type: 'next' }\n }\n\n // Resolve the locale to use\n const locale = negotiateLocale({\n acceptLanguage: request.headers.get('accept-language'),\n config,\n cookieLocale: request.cookies.get(cookieName)?.value ?? null,\n })\n\n // Redirect to the locale-prefixed path, preserving the rest\n const url = request.nextUrl.clone()\n url.pathname = `/${locale}${pathname === '/' ? '' : pathname}`\n\n return {\n type: 'redirect',\n cookie: { name: cookieName, value: locale },\n location: url.toString(),\n }\n }\n}\n\n/**\n * Default Next.js middleware matcher: run on everything except API routes, the\n * admin panel, Next internals, and files with an extension (static assets).\n */\nexport const DEFAULT_MIDDLEWARE_MATCHER = ['/((?!api|admin|_next|.*\\\\..*).*)']\n"],"names":["isValidLocale","LOCALE_COOKIE_NAME","negotiateLocale","firstSegment","pathname","split","filter","Boolean","createLocaleMiddleware","config","cookieName","localeMiddleware","request","nextUrl","type","locale","acceptLanguage","headers","get","cookieLocale","cookies","value","url","clone","cookie","name","location","toString","DEFAULT_MIDDLEWARE_MATCHER"],"mappings":"AAEA,SAASA,aAAa,EAAEC,kBAAkB,EAAEC,eAAe,QAAQ,mBAAkB;AA2BrF;;;CAGC,GACD,SAASC,aAAaC,QAAgB;IACpC,OAAOA,SAASC,KAAK,CAAC,KAAKC,MAAM,CAACC,QAAQ,CAAC,EAAE,IAAI;AACnD;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASC,uBAAuB,EACrCC,MAAM,EACNC,aAAaT,kBAAkB,EACJ;IAC3B,OAAO,SAASU,iBAAiBC,OAA0B;QACzD,MAAM,EAAER,QAAQ,EAAE,GAAGQ,QAAQC,OAAO;QAEpC,0CAA0C;QAC1C,IAAIb,cAAcG,aAAaC,WAAWK,SAAS;YACjD,OAAO;gBAAEK,MAAM;YAAO;QACxB;QAEA,4BAA4B;QAC5B,MAAMC,SAASb,gBAAgB;YAC7Bc,gBAAgBJ,QAAQK,OAAO,CAACC,GAAG,CAAC;YACpCT;YACAU,cAAcP,QAAQQ,OAAO,CAACF,GAAG,CAACR,aAAaW,SAAS;QAC1D;QAEA,4DAA4D;QAC5D,MAAMC,MAAMV,QAAQC,OAAO,CAACU,KAAK;QACjCD,IAAIlB,QAAQ,GAAG,CAAC,CAAC,EAAEW,SAASX,aAAa,MAAM,KAAKA,UAAU;QAE9D,OAAO;YACLU,MAAM;YACNU,QAAQ;gBAAEC,MAAMf;gBAAYW,OAAON;YAAO;YAC1CW,UAAUJ,IAAIK,QAAQ;QACxB;IACF;AACF;AAEA;;;CAGC,GACD,OAAO,MAAMC,6BAA6B;IAAC;CAAmC,CAAA"}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import type { Config } from 'payload';
|
||||
import type { I18nConfig } from './types.js';
|
||||
type PayloadLocalizationConfig = NonNullable<Config['localization']>;
|
||||
/**
|
||||
* Transforms validated i18n config into Payload's localization object.
|
||||
* Pure function — no side effects, no validation (caller validates first).
|
||||
*/
|
||||
export declare function buildLocalizationConfig(config: I18nConfig): PayloadLocalizationConfig;
|
||||
export {};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Transforms validated i18n config into Payload's localization object.
|
||||
* Pure function — no side effects, no validation (caller validates first).
|
||||
*/ export function buildLocalizationConfig(config) {
|
||||
const { defaultLocale, fallback = true, locales } = config;
|
||||
return {
|
||||
defaultLocale,
|
||||
fallback,
|
||||
locales: locales.map((locale)=>({
|
||||
code: locale.code,
|
||||
label: locale.label,
|
||||
...locale.rtl && {
|
||||
rtl: true
|
||||
}
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=localizationConfig.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/i18n/localizationConfig.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { I18nConfig } from './types.js'\n\ntype PayloadLocalizationConfig = NonNullable<Config['localization']>\n\n/**\n * Transforms validated i18n config into Payload's localization object.\n * Pure function — no side effects, no validation (caller validates first).\n */\nexport function buildLocalizationConfig(config: I18nConfig): PayloadLocalizationConfig {\n const { defaultLocale, fallback = true, locales } = config\n\n return {\n defaultLocale,\n fallback,\n locales: locales.map((locale) => ({\n code: locale.code,\n label: locale.label,\n ...(locale.rtl && { rtl: true }),\n })),\n }\n}\n"],"names":["buildLocalizationConfig","config","defaultLocale","fallback","locales","map","locale","code","label","rtl"],"mappings":"AAMA;;;CAGC,GACD,OAAO,SAASA,wBAAwBC,MAAkB;IACxD,MAAM,EAAEC,aAAa,EAAEC,WAAW,IAAI,EAAEC,OAAO,EAAE,GAAGH;IAEpD,OAAO;QACLC;QACAC;QACAC,SAASA,QAAQC,GAAG,CAAC,CAACC,SAAY,CAAA;gBAChCC,MAAMD,OAAOC,IAAI;gBACjBC,OAAOF,OAAOE,KAAK;gBACnB,GAAIF,OAAOG,GAAG,IAAI;oBAAEA,KAAK;gBAAK,CAAC;YACjC,CAAA;IACF;AACF"}
|
||||
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
import type { I18nConfig } from './types.js';
|
||||
/**
|
||||
* Minimal shape the path builder needs from a document.
|
||||
*
|
||||
* The plugin doesn't know the client's Pages type, so it depends only on
|
||||
* this contract: a map of locale code → slug for that locale. The template
|
||||
* supplies it (e.g. by reading the localized slug field across locales).
|
||||
*/
|
||||
export type LocalizedSlugs = Record<string, string>;
|
||||
type BuildPathArgs = {
|
||||
config: I18nConfig;
|
||||
/**
|
||||
* Slug that represents the site root (served at /{locale} with no trailing
|
||||
* segment). Defaults to 'home'. Matched against the slug in the target locale.
|
||||
*/
|
||||
homeSlug?: string;
|
||||
/** Target locale to build the path for */
|
||||
locale: string;
|
||||
/**
|
||||
* Localized segment the document lives under, e.g.
|
||||
* `{ pl: 'artykuly', en: 'articles' }` → /pl/artykuly/moj-post.
|
||||
*
|
||||
* These are the slugs of the collection's archive page, so the prefix is
|
||||
* whatever an editor named that page — and it differs per locale for free.
|
||||
* A document under a prefix is never the home page, so homeSlug is ignored.
|
||||
*/
|
||||
prefix?: LocalizedSlugs;
|
||||
/** slug per locale, e.g. { pl: 'strona-glowna', en: 'home' } */
|
||||
slugs: LocalizedSlugs;
|
||||
};
|
||||
/**
|
||||
* Builds a locale-prefixed path for a document in a target locale.
|
||||
*
|
||||
* Always prefixes the locale: /{locale} or /{locale}/{slug}. The home slug
|
||||
* collapses to the locale root. Returns undefined if the document has no slug
|
||||
* in the target locale (caller decides fallback behavior).
|
||||
*
|
||||
* @example
|
||||
* buildLocalizedPath({ slugs: { pl: 'strona-glowna', en: 'home' }, locale: 'en', config })
|
||||
* // → '/en' (home slug collapses to root)
|
||||
*
|
||||
* buildLocalizedPath({ slugs: { pl: 'o-nas', en: 'about' }, locale: 'en', config })
|
||||
* // → '/en/about'
|
||||
*
|
||||
* buildLocalizedPath({
|
||||
* slugs: { pl: 'moj-post', en: 'my-post' },
|
||||
* prefix: { pl: 'artykuly', en: 'articles' },
|
||||
* locale: 'en',
|
||||
* config,
|
||||
* })
|
||||
* // → '/en/articles/my-post'
|
||||
*/
|
||||
export declare function buildLocalizedPath({ config, homeSlug, locale, prefix, slugs, }: BuildPathArgs): string | undefined;
|
||||
type SwitchLocaleArgs = {
|
||||
config: I18nConfig;
|
||||
homeSlug?: string;
|
||||
prefix?: LocalizedSlugs;
|
||||
slugs: LocalizedSlugs;
|
||||
targetLocale: string;
|
||||
};
|
||||
/**
|
||||
* Resolves the equivalent path for the same document in a different locale —
|
||||
* the language-switcher use case (/pl/strona-glowna → /en/home).
|
||||
*
|
||||
* Never dead-ends on a 404. When the document has no slug in the target locale,
|
||||
* falls back to the archive it belongs to (/en/articles) if there is one, and
|
||||
* to the locale root otherwise — the closest place the visitor would want.
|
||||
*/
|
||||
export declare function switchLocalePath({ config, homeSlug, prefix, slugs, targetLocale, }: SwitchLocaleArgs): string;
|
||||
export {};
|
||||
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
import { isValidLocale } from './helpers.js';
|
||||
/**
|
||||
* Builds a locale-prefixed path for a document in a target locale.
|
||||
*
|
||||
* Always prefixes the locale: /{locale} or /{locale}/{slug}. The home slug
|
||||
* collapses to the locale root. Returns undefined if the document has no slug
|
||||
* in the target locale (caller decides fallback behavior).
|
||||
*
|
||||
* @example
|
||||
* buildLocalizedPath({ slugs: { pl: 'strona-glowna', en: 'home' }, locale: 'en', config })
|
||||
* // → '/en' (home slug collapses to root)
|
||||
*
|
||||
* buildLocalizedPath({ slugs: { pl: 'o-nas', en: 'about' }, locale: 'en', config })
|
||||
* // → '/en/about'
|
||||
*
|
||||
* buildLocalizedPath({
|
||||
* slugs: { pl: 'moj-post', en: 'my-post' },
|
||||
* prefix: { pl: 'artykuly', en: 'articles' },
|
||||
* locale: 'en',
|
||||
* config,
|
||||
* })
|
||||
* // → '/en/articles/my-post'
|
||||
*/ export function buildLocalizedPath({ config, homeSlug = 'home', locale, prefix, slugs }) {
|
||||
if (!isValidLocale(locale, config)) {
|
||||
return undefined;
|
||||
}
|
||||
const slug = slugs[locale];
|
||||
if (!slug) {
|
||||
return undefined;
|
||||
}
|
||||
if (prefix) {
|
||||
const segment = prefix[locale];
|
||||
// No archive slug in this locale means the entry is unreachable there —
|
||||
// there's no path to point at, so hreflang should omit it rather than
|
||||
// invent /en/moj-post.
|
||||
if (!segment) {
|
||||
return undefined;
|
||||
}
|
||||
return `/${locale}/${segment}/${slug}`;
|
||||
}
|
||||
if (slug === homeSlug) {
|
||||
return `/${locale}`;
|
||||
}
|
||||
return `/${locale}/${slug}`;
|
||||
}
|
||||
/**
|
||||
* Resolves the equivalent path for the same document in a different locale —
|
||||
* the language-switcher use case (/pl/strona-glowna → /en/home).
|
||||
*
|
||||
* Never dead-ends on a 404. When the document has no slug in the target locale,
|
||||
* falls back to the archive it belongs to (/en/articles) if there is one, and
|
||||
* to the locale root otherwise — the closest place the visitor would want.
|
||||
*/ export function switchLocalePath({ config, homeSlug = 'home', prefix, slugs, targetLocale }) {
|
||||
const path = buildLocalizedPath({
|
||||
config,
|
||||
homeSlug,
|
||||
locale: targetLocale,
|
||||
prefix,
|
||||
slugs
|
||||
});
|
||||
if (path) {
|
||||
return path;
|
||||
}
|
||||
const archiveSegment = prefix?.[targetLocale];
|
||||
if (archiveSegment) {
|
||||
return `/${targetLocale}/${archiveSegment}`;
|
||||
}
|
||||
return `/${targetLocale}`;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=localizedPath.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/i18n/localizedPath.ts"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nimport { isValidLocale } from './helpers.js'\n\n/**\n * Minimal shape the path builder needs from a document.\n *\n * The plugin doesn't know the client's Pages type, so it depends only on\n * this contract: a map of locale code → slug for that locale. The template\n * supplies it (e.g. by reading the localized slug field across locales).\n */\nexport type LocalizedSlugs = Record<string, string>\n\ntype BuildPathArgs = {\n config: I18nConfig\n /**\n * Slug that represents the site root (served at /{locale} with no trailing\n * segment). Defaults to 'home'. Matched against the slug in the target locale.\n */\n homeSlug?: string\n /** Target locale to build the path for */\n locale: string\n /**\n * Localized segment the document lives under, e.g.\n * `{ pl: 'artykuly', en: 'articles' }` → /pl/artykuly/moj-post.\n *\n * These are the slugs of the collection's archive page, so the prefix is\n * whatever an editor named that page — and it differs per locale for free.\n * A document under a prefix is never the home page, so homeSlug is ignored.\n */\n prefix?: LocalizedSlugs\n /** slug per locale, e.g. { pl: 'strona-glowna', en: 'home' } */\n slugs: LocalizedSlugs\n}\n\n/**\n * Builds a locale-prefixed path for a document in a target locale.\n *\n * Always prefixes the locale: /{locale} or /{locale}/{slug}. The home slug\n * collapses to the locale root. Returns undefined if the document has no slug\n * in the target locale (caller decides fallback behavior).\n *\n * @example\n * buildLocalizedPath({ slugs: { pl: 'strona-glowna', en: 'home' }, locale: 'en', config })\n * // → '/en' (home slug collapses to root)\n *\n * buildLocalizedPath({ slugs: { pl: 'o-nas', en: 'about' }, locale: 'en', config })\n * // → '/en/about'\n *\n * buildLocalizedPath({\n * slugs: { pl: 'moj-post', en: 'my-post' },\n * prefix: { pl: 'artykuly', en: 'articles' },\n * locale: 'en',\n * config,\n * })\n * // → '/en/articles/my-post'\n */\nexport function buildLocalizedPath({\n config,\n homeSlug = 'home',\n locale,\n prefix,\n slugs,\n}: BuildPathArgs): string | undefined {\n if (!isValidLocale(locale, config)) {\n return undefined\n }\n\n const slug = slugs[locale]\n if (!slug) {\n return undefined\n }\n\n if (prefix) {\n const segment = prefix[locale]\n // No archive slug in this locale means the entry is unreachable there —\n // there's no path to point at, so hreflang should omit it rather than\n // invent /en/moj-post.\n if (!segment) {\n return undefined\n }\n return `/${locale}/${segment}/${slug}`\n }\n\n if (slug === homeSlug) {\n return `/${locale}`\n }\n\n return `/${locale}/${slug}`\n}\n\ntype SwitchLocaleArgs = {\n config: I18nConfig\n homeSlug?: string\n prefix?: LocalizedSlugs\n slugs: LocalizedSlugs\n targetLocale: string\n}\n\n/**\n * Resolves the equivalent path for the same document in a different locale —\n * the language-switcher use case (/pl/strona-glowna → /en/home).\n *\n * Never dead-ends on a 404. When the document has no slug in the target locale,\n * falls back to the archive it belongs to (/en/articles) if there is one, and\n * to the locale root otherwise — the closest place the visitor would want.\n */\nexport function switchLocalePath({\n config,\n homeSlug = 'home',\n prefix,\n slugs,\n targetLocale,\n}: SwitchLocaleArgs): string {\n const path = buildLocalizedPath({ config, homeSlug, locale: targetLocale, prefix, slugs })\n if (path) {return path}\n\n const archiveSegment = prefix?.[targetLocale]\n if (archiveSegment) {return `/${targetLocale}/${archiveSegment}`}\n\n return `/${targetLocale}`\n}\n"],"names":["isValidLocale","buildLocalizedPath","config","homeSlug","locale","prefix","slugs","undefined","slug","segment","switchLocalePath","targetLocale","path","archiveSegment"],"mappings":"AAEA,SAASA,aAAa,QAAQ,eAAc;AAiC5C;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,SAASC,mBAAmB,EACjCC,MAAM,EACNC,WAAW,MAAM,EACjBC,MAAM,EACNC,MAAM,EACNC,KAAK,EACS;IACd,IAAI,CAACN,cAAcI,QAAQF,SAAS;QAClC,OAAOK;IACT;IAEA,MAAMC,OAAOF,KAAK,CAACF,OAAO;IAC1B,IAAI,CAACI,MAAM;QACT,OAAOD;IACT;IAEA,IAAIF,QAAQ;QACV,MAAMI,UAAUJ,MAAM,CAACD,OAAO;QAC9B,wEAAwE;QACxE,sEAAsE;QACtE,uBAAuB;QACvB,IAAI,CAACK,SAAS;YACZ,OAAOF;QACT;QACA,OAAO,CAAC,CAAC,EAAEH,OAAO,CAAC,EAAEK,QAAQ,CAAC,EAAED,MAAM;IACxC;IAEA,IAAIA,SAASL,UAAU;QACrB,OAAO,CAAC,CAAC,EAAEC,QAAQ;IACrB;IAEA,OAAO,CAAC,CAAC,EAAEA,OAAO,CAAC,EAAEI,MAAM;AAC7B;AAUA;;;;;;;CAOC,GACD,OAAO,SAASE,iBAAiB,EAC/BR,MAAM,EACNC,WAAW,MAAM,EACjBE,MAAM,EACNC,KAAK,EACLK,YAAY,EACK;IACjB,MAAMC,OAAOX,mBAAmB;QAAEC;QAAQC;QAAUC,QAAQO;QAAcN;QAAQC;IAAM;IACxF,IAAIM,MAAM;QAAC,OAAOA;IAAI;IAEtB,MAAMC,iBAAiBR,QAAQ,CAACM,aAAa;IAC7C,IAAIE,gBAAgB;QAAC,OAAO,CAAC,CAAC,EAAEF,aAAa,CAAC,EAAEE,gBAAgB;IAAA;IAEhE,OAAO,CAAC,CAAC,EAAEF,cAAc;AAC3B"}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import type { I18nConfig } from './types.js';
|
||||
/** Cookie name the template uses to persist a visitor's locale choice. */
|
||||
export declare const LOCALE_COOKIE_NAME = "ipal-locale";
|
||||
type NegotiateLocaleArgs = {
|
||||
/** Raw Accept-Language header value */
|
||||
acceptLanguage?: null | string;
|
||||
config: I18nConfig;
|
||||
/** Value of the locale cookie, if present (from LOCALE_COOKIE_NAME) */
|
||||
cookieLocale?: null | string;
|
||||
};
|
||||
/**
|
||||
* Resolves which locale to serve, in priority order:
|
||||
* 1. Cookie (explicit prior choice)
|
||||
* 2. Accept-Language header (best match against configured locales)
|
||||
* 3. Configured default locale
|
||||
*
|
||||
* Pure function — the template feeds it request data and acts on the result
|
||||
* (redirect, cookie set). No Next.js or request objects here.
|
||||
*/
|
||||
export declare function negotiateLocale({ acceptLanguage, config, cookieLocale, }: NegotiateLocaleArgs): string;
|
||||
/**
|
||||
* Parses an Accept-Language header and returns the best-matching configured
|
||||
* locale, or undefined if none match.
|
||||
*
|
||||
* Matches on the primary subtag (e.g. "en-US" matches configured "en"),
|
||||
* respecting the header's quality-value ordering.
|
||||
*/
|
||||
export declare function matchAcceptLanguage(acceptLanguage: null | string | undefined, config: I18nConfig): string | undefined;
|
||||
export {};
|
||||
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
import { getLocaleCodes, isValidLocale } from './helpers.js';
|
||||
/** Cookie name the template uses to persist a visitor's locale choice. */ export const LOCALE_COOKIE_NAME = 'ipal-locale';
|
||||
/**
|
||||
* Resolves which locale to serve, in priority order:
|
||||
* 1. Cookie (explicit prior choice)
|
||||
* 2. Accept-Language header (best match against configured locales)
|
||||
* 3. Configured default locale
|
||||
*
|
||||
* Pure function — the template feeds it request data and acts on the result
|
||||
* (redirect, cookie set). No Next.js or request objects here.
|
||||
*/ export function negotiateLocale({ acceptLanguage, config, cookieLocale }) {
|
||||
// 1. Explicit prior choice wins
|
||||
if (cookieLocale && isValidLocale(cookieLocale, config)) {
|
||||
return cookieLocale;
|
||||
}
|
||||
// 2. Best match from Accept-Language
|
||||
const fromHeader = matchAcceptLanguage(acceptLanguage, config);
|
||||
if (fromHeader) {
|
||||
return fromHeader;
|
||||
}
|
||||
// 3. Fall back to configured default
|
||||
return config.defaultLocale;
|
||||
}
|
||||
/**
|
||||
* Parses an Accept-Language header and returns the best-matching configured
|
||||
* locale, or undefined if none match.
|
||||
*
|
||||
* Matches on the primary subtag (e.g. "en-US" matches configured "en"),
|
||||
* respecting the header's quality-value ordering.
|
||||
*/ export function matchAcceptLanguage(acceptLanguage, config) {
|
||||
if (!acceptLanguage) {
|
||||
return undefined;
|
||||
}
|
||||
const available = getLocaleCodes(config);
|
||||
const ranked = parseAcceptLanguage(acceptLanguage);
|
||||
for (const tag of ranked){
|
||||
// Exact match (e.g. "pt-BR" === "pt-BR")
|
||||
const exact = available.find((code)=>code.toLowerCase() === tag);
|
||||
if (exact) {
|
||||
return exact;
|
||||
}
|
||||
// Primary-subtag match (e.g. "en-us" → "en")
|
||||
const primary = tag.split('-')[0];
|
||||
const partial = available.find((code)=>code.toLowerCase().split('-')[0] === primary);
|
||||
if (partial) {
|
||||
return partial;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Parses an Accept-Language header into locale tags ordered by descending
|
||||
* quality value. Tags are lowercased for comparison.
|
||||
*
|
||||
* "en-US,en;q=0.9,pl;q=0.8" → ["en-us", "en", "pl"]
|
||||
*/ function parseAcceptLanguage(header) {
|
||||
return header.split(',').map((part)=>{
|
||||
const [tag, ...params] = part.trim().split(';');
|
||||
const qParam = params.find((p)=>p.trim().startsWith('q='));
|
||||
const quality = qParam ? parseFloat(qParam.split('=')[1]) : 1;
|
||||
return {
|
||||
quality: Number.isNaN(quality) ? 0 : quality,
|
||||
tag: tag.trim().toLowerCase()
|
||||
};
|
||||
}).filter((entry)=>entry.tag && entry.tag !== '*').sort((a, b)=>b.quality - a.quality).map((entry)=>entry.tag);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=negotiateLocale.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Single locale definition provided by the client project.
|
||||
*/
|
||||
export type LocaleDefinition = {
|
||||
/** BCP-47 language code, e.g. 'pl', 'en', 'de' */
|
||||
code: string;
|
||||
/** Human-readable label, e.g. 'Polski', 'English' */
|
||||
label: string;
|
||||
/** Right-to-left script (defaults to false) */
|
||||
rtl?: boolean;
|
||||
};
|
||||
/**
|
||||
* Full i18n configuration passed through plugin options.
|
||||
*/
|
||||
export type I18nConfig = {
|
||||
defaultLocale: string;
|
||||
/** Enable locale fallback when content is missing (defaults to true) */
|
||||
fallback?: boolean;
|
||||
locales: [LocaleDefinition, ...LocaleDefinition[]];
|
||||
};
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Single locale definition provided by the client project.
|
||||
*/ /**
|
||||
* Full i18n configuration passed through plugin options.
|
||||
*/ export { };
|
||||
|
||||
//# sourceMappingURL=types.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/i18n/types.ts"],"sourcesContent":["/**\n * Single locale definition provided by the client project.\n */\nexport type LocaleDefinition = {\n /** BCP-47 language code, e.g. 'pl', 'en', 'de' */\n code: string\n /** Human-readable label, e.g. 'Polski', 'English' */\n label: string\n /** Right-to-left script (defaults to false) */\n rtl?: boolean\n}\n\n/**\n * Full i18n configuration passed through plugin options.\n */\nexport type I18nConfig = {\n defaultLocale: string\n /** Enable locale fallback when content is missing (defaults to true) */\n fallback?: boolean\n locales: [LocaleDefinition, ...LocaleDefinition[]]\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GAUD;;CAEC,GACD,WAKC"}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
import type { I18nConfig } from './types.js';
|
||||
/**
|
||||
* Validates i18n config at plugin initialization.
|
||||
* Throws descriptive errors — fail fast, no silent defaults.
|
||||
*/
|
||||
export declare function validateI18nConfig(config: I18nConfig): void;
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
const LOCALE_CODE_PATTERN = /^[a-z]{2,3}(-[A-Z]{2})?$/;
|
||||
/**
|
||||
* Validates i18n config at plugin initialization.
|
||||
* Throws descriptive errors — fail fast, no silent defaults.
|
||||
*/ export function validateI18nConfig(config) {
|
||||
const { defaultLocale, locales } = config;
|
||||
if (!locales?.length) {
|
||||
throw new Error('[ipal] i18n: "locales" must contain at least one locale.');
|
||||
}
|
||||
const codes = new Set();
|
||||
for (const locale of locales){
|
||||
if (!locale.code || !locale.label) {
|
||||
throw new Error(`[ipal] i18n: Every locale must have "code" and "label". Received: ${JSON.stringify(locale)}`);
|
||||
}
|
||||
if (!LOCALE_CODE_PATTERN.test(locale.code)) {
|
||||
throw new Error(`[ipal] i18n: Invalid locale code "${locale.code}". Expected format: "pl", "en", "pt-BR".`);
|
||||
}
|
||||
if (codes.has(locale.code)) {
|
||||
throw new Error(`[ipal] i18n: Duplicate locale code "${locale.code}".`);
|
||||
}
|
||||
codes.add(locale.code);
|
||||
}
|
||||
if (!codes.has(defaultLocale)) {
|
||||
throw new Error(`[ipal] i18n: defaultLocale "${defaultLocale}" not found in locales [${[
|
||||
...codes
|
||||
].join(', ')}].`);
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=validation.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/i18n/validation.ts"],"sourcesContent":["import type { I18nConfig } from './types.js'\n\nconst LOCALE_CODE_PATTERN = /^[a-z]{2,3}(-[A-Z]{2})?$/\n\n/**\n * Validates i18n config at plugin initialization.\n * Throws descriptive errors — fail fast, no silent defaults.\n */\nexport function validateI18nConfig(config: I18nConfig): void {\n const { defaultLocale, locales } = config\n\n if (!locales?.length) {\n throw new Error('[ipal] i18n: \"locales\" must contain at least one locale.')\n }\n\n const codes = new Set<string>()\n\n for (const locale of locales) {\n if (!locale.code || !locale.label) {\n throw new Error(\n `[ipal] i18n: Every locale must have \"code\" and \"label\". Received: ${JSON.stringify(locale)}`,\n )\n }\n\n if (!LOCALE_CODE_PATTERN.test(locale.code)) {\n throw new Error(\n `[ipal] i18n: Invalid locale code \"${locale.code}\". Expected format: \"pl\", \"en\", \"pt-BR\".`,\n )\n }\n\n if (codes.has(locale.code)) {\n throw new Error(`[ipal] i18n: Duplicate locale code \"${locale.code}\".`)\n }\n\n codes.add(locale.code)\n }\n\n if (!codes.has(defaultLocale)) {\n throw new Error(\n `[ipal] i18n: defaultLocale \"${defaultLocale}\" not found in locales [${[...codes].join(', ')}].`,\n )\n }\n}\n"],"names":["LOCALE_CODE_PATTERN","validateI18nConfig","config","defaultLocale","locales","length","Error","codes","Set","locale","code","label","JSON","stringify","test","has","add","join"],"mappings":"AAEA,MAAMA,sBAAsB;AAE5B;;;CAGC,GACD,OAAO,SAASC,mBAAmBC,MAAkB;IACnD,MAAM,EAAEC,aAAa,EAAEC,OAAO,EAAE,GAAGF;IAEnC,IAAI,CAACE,SAASC,QAAQ;QACpB,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAMC,QAAQ,IAAIC;IAElB,KAAK,MAAMC,UAAUL,QAAS;QAC5B,IAAI,CAACK,OAAOC,IAAI,IAAI,CAACD,OAAOE,KAAK,EAAE;YACjC,MAAM,IAAIL,MACR,CAAC,kEAAkE,EAAEM,KAAKC,SAAS,CAACJ,SAAS;QAEjG;QAEA,IAAI,CAACT,oBAAoBc,IAAI,CAACL,OAAOC,IAAI,GAAG;YAC1C,MAAM,IAAIJ,MACR,CAAC,kCAAkC,EAAEG,OAAOC,IAAI,CAAC,wCAAwC,CAAC;QAE9F;QAEA,IAAIH,MAAMQ,GAAG,CAACN,OAAOC,IAAI,GAAG;YAC1B,MAAM,IAAIJ,MAAM,CAAC,oCAAoC,EAAEG,OAAOC,IAAI,CAAC,EAAE,CAAC;QACxE;QAEAH,MAAMS,GAAG,CAACP,OAAOC,IAAI;IACvB;IAEA,IAAI,CAACH,MAAMQ,GAAG,CAACZ,gBAAgB;QAC7B,MAAM,IAAIG,MACR,CAAC,4BAA4B,EAAEH,cAAc,wBAAwB,EAAE;eAAII;SAAM,CAACU,IAAI,CAAC,MAAM,EAAE,CAAC;IAEpG;AACF"}
|
||||
Reference in New Issue
Block a user