middleware.ts 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import { defineMiddleware } from "astro:middleware"
  2. import { exactLocale, matchLocale } from "./i18n/locales"
  3. function docsAlias(pathname: string) {
  4. const hit = /^\/docs\/([^/]+)(\/.*)?$/.exec(pathname)
  5. if (!hit) return null
  6. const value = hit[1] ?? ""
  7. const tail = hit[2] ?? ""
  8. const locale = exactLocale(value)
  9. if (!locale) return null
  10. const next = locale === "root" ? `/docs${tail}` : `/docs/${locale}${tail}`
  11. if (next === pathname) return null
  12. return {
  13. path: next,
  14. locale,
  15. }
  16. }
  17. function cookie(locale: string) {
  18. const value = locale === "root" ? "en" : locale
  19. return `oc_locale=${encodeURIComponent(value)}; Path=/; Max-Age=31536000; SameSite=Lax`
  20. }
  21. function redirect(url: URL, path: string, locale?: string) {
  22. const next = new URL(url.toString())
  23. next.pathname = path
  24. const headers = new Headers({
  25. Location: next.toString(),
  26. })
  27. if (locale) headers.set("Set-Cookie", cookie(locale))
  28. return new Response(null, {
  29. status: 302,
  30. headers,
  31. })
  32. }
  33. function localeFromCookie(header: string | null) {
  34. if (!header) return null
  35. const raw = header
  36. .split(";")
  37. .map((x) => x.trim())
  38. .find((x) => x.startsWith("oc_locale="))
  39. ?.slice("oc_locale=".length)
  40. if (!raw) return null
  41. return matchLocale(raw)
  42. }
  43. function localeFromAcceptLanguage(header: string | null) {
  44. if (!header) return "root"
  45. const items = header
  46. .split(",")
  47. .map((raw) => raw.trim())
  48. .filter(Boolean)
  49. .map((raw) => {
  50. const parts = raw.split(";").map((x) => x.trim())
  51. const lang = parts[0] ?? ""
  52. const q = parts
  53. .slice(1)
  54. .find((x) => x.startsWith("q="))
  55. ?.slice(2)
  56. return {
  57. lang,
  58. q: q ? Number.parseFloat(q) : 1,
  59. }
  60. })
  61. .sort((a, b) => b.q - a.q)
  62. const locale = items
  63. .map((item) => item.lang)
  64. .filter((lang) => lang && lang !== "*")
  65. .map((lang) => matchLocale(lang))
  66. .find((lang) => lang)
  67. return locale ?? "root"
  68. }
  69. export const onRequest = defineMiddleware((ctx, next) => {
  70. const alias = docsAlias(ctx.url.pathname)
  71. if (alias) {
  72. return redirect(ctx.url, alias.path, alias.locale)
  73. }
  74. if (ctx.url.pathname !== "/docs" && ctx.url.pathname !== "/docs/") return next()
  75. const locale =
  76. localeFromCookie(ctx.request.headers.get("cookie")) ??
  77. localeFromAcceptLanguage(ctx.request.headers.get("accept-language"))
  78. if (!locale || locale === "root") return next()
  79. return redirect(ctx.url, `/docs/${locale}/`)
  80. })