{"version":3,"sources":["../src/i18n/config.ts"],"sourcesContent":["import path from \"node:path\";\nimport type {\n  FarmI18nDetectionSignal,\n  FarmI18nDirection,\n  FarmI18nUserConfig,\n  ResolvedFarmI18nConfig,\n} from \"./types\";\n\nexport const DEFAULT_FARM_I18N_COOKIE = \"farm_locale\";\nexport const DEFAULT_FARM_I18N_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;\n\nconst DEFAULT_DETECTION: readonly FarmI18nDetectionSignal[] = [\"url\", \"cookie\", \"accept-language\"];\n\nexport function resolveFarmI18nConfig(\n  input: FarmI18nUserConfig | false | undefined,\n  options: { root?: string; mode?: \"development\" | \"production\"; basePath?: string } = {},\n): ResolvedFarmI18nConfig {\n  const root = options.root || process.cwd();\n  const basePath = options.basePath || \"/\";\n  const strictByDefault = options.mode === \"production\";\n\n  if (!input) {\n    return {\n      enabled: false,\n      basePath,\n      locales: [\"en\"],\n      defaultLocale: \"en\",\n      messages: path.join(root, \"src/messages\"),\n      routing: \"none\",\n      detection: [],\n      fallbackLocale: \"en\",\n      strict: strictByDefault,\n      cookie: {\n        name: DEFAULT_FARM_I18N_COOKIE,\n        maxAge: DEFAULT_FARM_I18N_COOKIE_MAX_AGE,\n        path: \"/\",\n        sameSite: \"lax\",\n        secure: options.mode === \"production\",\n      },\n      direction: {},\n    };\n  }\n\n  if (!Array.isArray(input.locales) || input.locales.length === 0) {\n    throw new Error(\"i18n.locales must contain at least one locale.\");\n  }\n\n  const locales = input.locales.map(canonicalizeLocale);\n  if (new Set(locales).size !== locales.length) {\n    throw new Error(\"i18n.locales must not contain duplicate locales.\");\n  }\n\n  const defaultLocale = canonicalizeLocale(input.defaultLocale);\n  if (!locales.includes(defaultLocale)) {\n    throw new Error(`i18n.defaultLocale \"${defaultLocale}\" must be included in i18n.locales.`);\n  }\n\n  const fallbackLocale = canonicalizeLocale(input.fallbackLocale || defaultLocale);\n  if (!locales.includes(fallbackLocale)) {\n    throw new Error(`i18n.fallbackLocale \"${fallbackLocale}\" must be included in i18n.locales.`);\n  }\n\n  const detection = resolveDetection(input);\n  const sameSite = input.cookie?.sameSite ?? \"lax\";\n  if (sameSite !== \"lax\" && sameSite !== \"strict\" && sameSite !== \"none\") {\n    throw new Error('i18n.cookie.sameSite must be \"lax\", \"strict\", or \"none\".');\n  }\n  const direction: Record<string, FarmI18nDirection> = {};\n  for (const [rawLocale, value] of Object.entries(input.direction || {})) {\n    const locale = canonicalizeLocale(rawLocale);\n    if (!locales.includes(locale)) {\n      throw new Error(`i18n.direction contains unknown locale \"${rawLocale}\".`);\n    }\n    if (value !== \"ltr\" && value !== \"rtl\") {\n      throw new Error(`i18n.direction.${rawLocale} must be \"ltr\" or \"rtl\".`);\n    }\n    direction[locale] = value;\n  }\n\n  return {\n    enabled: true,\n    basePath,\n    locales,\n    defaultLocale,\n    messages: path.resolve(root, input.messages || \"src/messages\"),\n    routing: input.routing || \"prefix-except-default\",\n    detection,\n    fallbackLocale,\n    strict: input.strict ?? strictByDefault,\n    cookie: {\n      name: input.cookie?.name?.trim() || DEFAULT_FARM_I18N_COOKIE,\n      maxAge: normalizePositiveInteger(\n        input.cookie?.maxAge,\n        DEFAULT_FARM_I18N_COOKIE_MAX_AGE,\n        \"i18n.cookie.maxAge\",\n      ),\n      path: normalizeCookiePath(input.cookie?.path),\n      sameSite,\n      secure: input.cookie?.secure ?? options.mode === \"production\",\n    },\n    direction,\n  };\n}\n\nexport function resolveFarmI18nMessagePath(\n  config: Pick<ResolvedFarmI18nConfig, \"messages\">,\n  locale: string,\n): string {\n  return config.messages.includes(\"{locale}\")\n    ? config.messages.split(\"{locale}\").join(locale)\n    : path.join(config.messages, `${locale}.json`);\n}\n\nexport function isFarmI18nCatalogFile(\n  config: Pick<ResolvedFarmI18nConfig, \"enabled\" | \"messages\" | \"locales\">,\n  file: string,\n): boolean {\n  if (!config.enabled) return false;\n  const normalizedFile = file.replace(/\\\\/g, \"/\");\n  return config.locales.some(\n    (locale) => resolveFarmI18nMessagePath(config, locale).replace(/\\\\/g, \"/\") === normalizedFile,\n  );\n}\n\nexport function canonicalizeLocale(locale: string): string {\n  if (!locale || typeof locale !== \"string\") {\n    throw new Error(\"Farm i18n locales must be non-empty strings.\");\n  }\n\n  try {\n    return Intl.getCanonicalLocales(locale)[0]!;\n  } catch {\n    throw new Error(`Invalid i18n locale \"${locale}\".`);\n  }\n}\n\nfunction resolveDetection(input: FarmI18nUserConfig): readonly FarmI18nDetectionSignal[] {\n  if (input.detection === false || input.localeDetection === false) {\n    return [\"url\"];\n  }\n\n  const detection = input.detection || DEFAULT_DETECTION;\n  const allowed = new Set<FarmI18nDetectionSignal>([\"url\", \"cookie\", \"accept-language\"]);\n  const unique: FarmI18nDetectionSignal[] = [];\n\n  for (const signal of detection) {\n    if (!allowed.has(signal)) {\n      throw new Error(`Unsupported i18n detection signal \"${signal}\".`);\n    }\n    if (!unique.includes(signal)) unique.push(signal);\n  }\n\n  return unique;\n}\n\nfunction normalizePositiveInteger(\n  value: number | undefined,\n  fallback: number,\n  name: string,\n): number {\n  if (value === undefined) return fallback;\n  if (!Number.isInteger(value) || value < 0) {\n    throw new Error(`${name} must be a positive integer.`);\n  }\n  return value;\n}\n\nfunction normalizeCookiePath(value: string | undefined): string {\n  if (value === undefined) return \"/\";\n  if (typeof value !== \"string\") {\n    throw new Error(\"i18n.cookie.path must be a root-relative pathname.\");\n  }\n\n  const hasUnstableCharacters = (candidate: string) =>\n    candidate.includes(\"\\\\\") ||\n    Array.from(candidate).some((character) => {\n      const code = character.charCodeAt(0);\n      return code <= 31 || (code >= 127 && code <= 159);\n    });\n\n  if (hasUnstableCharacters(value)) {\n    throw new Error(\"i18n.cookie.path cannot contain backslashes or control characters.\");\n  }\n\n  const pathname = value.trim();\n  if (\n    !pathname ||\n    !pathname.startsWith(\"/\") ||\n    pathname.startsWith(\"//\") ||\n    pathname.includes(\";\") ||\n    pathname.includes(\"?\") ||\n    pathname.includes(\"#\")\n  ) {\n    throw new Error(\n      \"i18n.cookie.path must be a root-relative pathname without attributes, a query, or a hash.\",\n    );\n  }\n\n  for (const segment of pathname.split(\"/\")) {\n    let decoded = segment;\n    try {\n      decoded = decodeURIComponent(segment);\n    } catch {\n      // Malformed escapes remain literal and cannot conceal a separator or dot segment.\n    }\n    if (hasUnstableCharacters(decoded)) {\n      throw new Error(\"i18n.cookie.path cannot contain backslashes or control characters.\");\n    }\n    if (decoded.includes(\"/\")) {\n      throw new Error(\"i18n.cookie.path cannot contain percent-encoded path separators.\");\n    }\n    if (decoded === \".\" || decoded === \"..\") {\n      throw new Error('i18n.cookie.path cannot contain \".\" or \"..\" path segments.');\n    }\n  }\n\n  return pathname;\n}\n"],"mappings":"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","names":[]}