From 7ff2661048b6e1b25e3caa9a89be483d484c627e Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Fri, 14 Mar 2025 11:23:46 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20locale=20detection=20(#163?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/landing/package.json | 1 - apps/landing/src/middleware.ts | 13 ++--------- apps/web/package.json | 2 -- apps/web/src/app/api/trpc/[trpc]/route.ts | 4 ++-- apps/web/src/app/guest.ts | 15 ------------- apps/web/src/middleware.ts | 4 ++-- packages/languages/package.json | 13 +++++++++-- .../languages/src/get-preferred-locale.ts | 17 ++++++++++++++ packages/languages/{ => src}/index.ts | 0 packages/languages/{ => src}/languages.json | 0 yarn.lock | 22 +++++++++---------- 11 files changed, 45 insertions(+), 46 deletions(-) delete mode 100644 apps/web/src/app/guest.ts create mode 100644 packages/languages/src/get-preferred-locale.ts rename packages/languages/{ => src}/index.ts (100%) rename packages/languages/{ => src}/languages.json (100%) diff --git a/apps/landing/package.json b/apps/landing/package.json index 41c68c4ea..52160393a 100644 --- a/apps/landing/package.json +++ b/apps/landing/package.json @@ -20,7 +20,6 @@ "@rallly/utils": "*", "@svgr/webpack": "^6.5.1", "@vercel/analytics": "^0.1.8", - "accept-language-parser": "^1.5.0", "dayjs": "^1.11.10", "gray-matter": "^4.0.3", "i18next": "^24.2.2", diff --git a/apps/landing/src/middleware.ts b/apps/landing/src/middleware.ts index 29d13aa01..92a7f51e6 100644 --- a/apps/landing/src/middleware.ts +++ b/apps/landing/src/middleware.ts @@ -1,17 +1,8 @@ import { supportedLngs } from "@rallly/languages"; -import languageParser from "accept-language-parser"; +import { getPreferredLocale } from "@rallly/languages/get-preferred-locale"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; -export async function getLocaleFromHeader(req: NextRequest) { - const headers = req.headers; - const acceptLanguageHeader = headers.get("accept-language"); - const localeFromHeader = acceptLanguageHeader - ? languageParser.pick(supportedLngs, acceptLanguageHeader) - : null; - return localeFromHeader ?? "en"; -} - export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; const localeInPath = supportedLngs.find( @@ -28,7 +19,7 @@ export async function middleware(request: NextRequest) { return; } - const locale = await getLocaleFromHeader(request); + const locale = await getPreferredLocale(request); request.nextUrl.pathname = `/${locale}${pathname}`; if (locale === "en") { diff --git a/apps/web/package.json b/apps/web/package.json index 3a1cbed87..6f763a837 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -47,7 +47,6 @@ "@upstash/ratelimit": "^1.2.1", "@vercel/functions": "^1.5.2", "@vercel/kv": "^2.0.0", - "accept-language-parser": "^1.5.0", "ai": "^4.1.50", "autoprefixer": "^10.4.13", "calendar-link": "^2.6.0", @@ -88,7 +87,6 @@ "@playwright/test": "^1.49.1", "@rallly/eslint-config": "*", "@rallly/tsconfig": "*", - "@types/accept-language-parser": "^1.5.3", "@types/color-hash": "^1.0.2", "@types/lodash": "^4.14.178", "@types/react-big-calendar": "^1.8.8", diff --git a/apps/web/src/app/api/trpc/[trpc]/route.ts b/apps/web/src/app/api/trpc/[trpc]/route.ts index 91d06db74..890d8f771 100644 --- a/apps/web/src/app/api/trpc/[trpc]/route.ts +++ b/apps/web/src/app/api/trpc/[trpc]/route.ts @@ -1,9 +1,9 @@ +import { getPreferredLocale } from "@rallly/languages/get-preferred-locale"; import * as Sentry from "@sentry/nextjs"; import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; import { ipAddress } from "@vercel/functions"; import type { NextRequest } from "next/server"; -import { getLocaleFromHeader } from "@/app/guest"; import { auth } from "@/next-auth"; import type { TRPCContext } from "@/trpc/context"; import { appRouter } from "@/trpc/routers"; @@ -19,7 +19,7 @@ const handler = async (req: NextRequest) => { req, router: appRouter, createContext: async () => { - const locale = await getLocaleFromHeader(req); + const locale = await getPreferredLocale(req); const user = session?.user ? { id: session.user.id, diff --git a/apps/web/src/app/guest.ts b/apps/web/src/app/guest.ts deleted file mode 100644 index 7a9440c25..000000000 --- a/apps/web/src/app/guest.ts +++ /dev/null @@ -1,15 +0,0 @@ -import languages from "@rallly/languages"; -import languageParser from "accept-language-parser"; -import type { NextRequest } from "next/server"; - -const supportedLocales = Object.keys(languages); - -export async function getLocaleFromHeader(req: NextRequest) { - // Check if locale is specified in header - const headers = req.headers; - const acceptLanguageHeader = headers.get("accept-language"); - const localeFromHeader = acceptLanguageHeader - ? languageParser.pick(supportedLocales, acceptLanguageHeader) - : null; - return localeFromHeader ?? "en"; -} diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index 3d8fa2d38..ec63c06c4 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -1,8 +1,8 @@ import languages from "@rallly/languages"; +import { getPreferredLocale } from "@rallly/languages/get-preferred-locale"; import { withPostHog } from "@rallly/posthog/next/middleware"; import { NextResponse } from "next/server"; -import { getLocaleFromHeader } from "@/app/guest"; import { withAuth } from "@/auth/edge"; const supportedLocales = Object.keys(languages); @@ -25,7 +25,7 @@ export const middleware = withAuth(async (req) => { newUrl.pathname = `/${locale}${newUrl.pathname}`; } else { // Check if locale is specified in header - locale = await getLocaleFromHeader(req); + locale = await getPreferredLocale(req); newUrl.pathname = `/${locale}${newUrl.pathname}`; } diff --git a/packages/languages/package.json b/packages/languages/package.json index dafedc47f..d8e9bfe13 100644 --- a/packages/languages/package.json +++ b/packages/languages/package.json @@ -2,6 +2,15 @@ "name": "@rallly/languages", "version": "0.0.0", "private": true, - "main": "index.ts", - "types": "index.ts" + "exports": { + ".": "./src/index.ts", + "./*": "./src/*.ts" + }, + "dependencies": { + "@formatjs/intl-localematcher": "^0.6.0", + "negotiator": "^1.0.0" + }, + "devDependencies": { + "@types/negotiator": "^0.6.3" + } } diff --git a/packages/languages/src/get-preferred-locale.ts b/packages/languages/src/get-preferred-locale.ts new file mode 100644 index 000000000..4f7f652de --- /dev/null +++ b/packages/languages/src/get-preferred-locale.ts @@ -0,0 +1,17 @@ +import languages, { defaultLocale } from "./index"; +import Negotiator from "negotiator"; +import { match } from "@formatjs/intl-localematcher"; +import type { NextRequest } from "next/server"; + +const locales = Object.keys(languages); + +export async function getPreferredLocale(req: NextRequest) { + const preferredLanguages = new Negotiator({ + headers: { + "accept-language": req.headers.get("accept-language") ?? "", + }, + }).languages(); + + const locale = match(preferredLanguages, locales, defaultLocale); + return locale; +} diff --git a/packages/languages/index.ts b/packages/languages/src/index.ts similarity index 100% rename from packages/languages/index.ts rename to packages/languages/src/index.ts diff --git a/packages/languages/languages.json b/packages/languages/src/languages.json similarity index 100% rename from packages/languages/languages.json rename to packages/languages/src/languages.json diff --git a/yarn.lock b/yarn.lock index b970b3937..caa0e145c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2892,7 +2892,7 @@ "@formatjs/ecma402-abstract" "2.3.3" tslib "2" -"@formatjs/intl-localematcher@0.6.0": +"@formatjs/intl-localematcher@0.6.0", "@formatjs/intl-localematcher@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.6.0.tgz#33cf0d33279572c990e02ab75a93122569878082" integrity sha512-4rB4g+3hESy1bHSBG3tDFaMY2CH67iT7yne1e+0CLTsGLDcmoEWWpJjjpWVaYgYfYuohIRuo0E+N536gd2ZHZA== @@ -6267,11 +6267,6 @@ resolved "https://registry.yarnpkg.com/@tsconfig/strictest/-/strictest-2.0.2.tgz#97167bf99c03d0a010d9946ae8a702c4f62e2783" integrity sha512-jt4jIsWKvUvuY6adJnQJlb/UR7DdjC8CjHI/OaSQruj2yX9/K6+KOvDt/vD6udqos/FUk5Op66CvYT7TBLYO5Q== -"@types/accept-language-parser@^1.5.3": - version "1.5.3" - resolved "https://registry.npmjs.org/@types/accept-language-parser/-/accept-language-parser-1.5.3.tgz" - integrity sha512-S8oM29O6nnRC3/+rwYV7GBYIIgNIZ52PCxqBG7OuItq9oATnYWy8FfeLKwvq5F7pIYjeeBSCI7y+l+Z9UEQpVQ== - "@types/accepts@*": version "1.3.5" resolved "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz" @@ -6510,6 +6505,11 @@ dependencies: "@types/node" "*" +"@types/negotiator@^0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@types/negotiator/-/negotiator-0.6.3.tgz#29e8fce64e35f57f6fe9c624f8e4ed304357745a" + integrity sha512-JkXTOdKs5MF086b/pt8C3+yVp3iDUwG635L7oCH6HvJvvr6lSUU5oe/gLXnPEfYRROHjJIPgCV6cuAg8gGkntQ== + "@types/node@*": version "20.8.9" resolved "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz" @@ -7081,11 +7081,6 @@ abbrev@^2.0.0: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== -accept-language-parser@^1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz" - integrity sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw== - accepts@~1.3.4: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -11625,6 +11620,11 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"