diff --git a/messages/en.json b/messages/en.json new file mode 100644 index 000000000..841cfe485 --- /dev/null +++ b/messages/en.json @@ -0,0 +1,7 @@ +{ + "CustomerCase": { + "customer": "Customer", + "project": "Project", + "duration": "Duration" + } +} diff --git a/messages/no.json b/messages/no.json new file mode 100644 index 000000000..9bf33abe5 --- /dev/null +++ b/messages/no.json @@ -0,0 +1,7 @@ +{ + "CustomerCase": { + "customer": "Kunde", + "project": "Prosjekt", + "duration": "Varighet" + } +} diff --git a/messages/se.json b/messages/se.json new file mode 100644 index 000000000..36c2c2856 --- /dev/null +++ b/messages/se.json @@ -0,0 +1,7 @@ +{ + "CustomerCase": { + "customer": "Kund", + "project": "Projekt", + "duration": "Varaktighet" + } +} diff --git a/next.config.mjs b/next.config.mjs index e8fee5052..5b91aedf2 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,3 +1,7 @@ +import createNextIntlPlugin from "next-intl/plugin"; + +const withNextIntl = createNextIntlPlugin(); + /** @type {import('next').NextConfig} */ const nextConfig = { images: { @@ -17,4 +21,4 @@ const nextConfig = { }, }; -export default nextConfig; +export default withNextIntl(nextConfig); diff --git a/package-lock.json b/package-lock.json index bc307e2d6..37f1fb773 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@sanity/vision": "^3.39.1", "negotiator": "^0.6.3", "next": "^14.2.15", + "next-intl": "^3.24.0", "next-sanity": "^7.1.4", "next-sanity-image": "^6.1.1", "react": "^18.3.1", @@ -2941,6 +2942,56 @@ "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==", "license": "MIT" }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.3.tgz", + "integrity": "sha512-aElGmleuReGnk2wtYOzYFmNWYoiWWmf1pPPCYg0oiIQSJj0mjc4eUfzUXaSOJ4S8WzI/cLqnCTWjqz904FT2OQ==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.3", + "@formatjs/intl-localematcher": "0.5.7", + "tslib": "2" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz", + "integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.3.tgz", + "integrity": "sha512-9L99QsH14XjOCIp4TmbT8wxuffJxGK8uLNO1zNhLtcZaVXvv626N0s4A2qgRCKG3dfYWx9psvGlFmvyVBa6u/w==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.2.3", + "@formatjs/icu-skeleton-parser": "1.8.7", + "tslib": "2" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.7", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.7.tgz", + "integrity": "sha512-fI+6SmS2g7h3srfAKSWa5dwreU5zNEfon2uFo99OToiLF6yxGE+WikvFSbsvMAYkscucvVmTYNlWlaDPp0n5HA==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.2.3", + "tslib": "2" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.7.tgz", + "integrity": "sha512-GGFtfHGQVFe/niOZp24Kal5b2i36eE2bNL0xi9Sg/yd0TR8aLjcteApZdHmismP5QQax1cMnZM9yWySUUjJteA==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -15888,6 +15939,18 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "10.7.6", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.6.tgz", + "integrity": "sha512-IsMU/hqyy3FJwNJ0hxDfY2heJ7MteSuFvcnCebxRp67di4Fhx1gKKE+qS0bBwUF8yXkX9SsPUhLeX/B6h5SKUA==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.2.3", + "@formatjs/fast-memoize": "2.2.3", + "@formatjs/icu-messageformat-parser": "2.9.3", + "tslib": "2" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -19916,6 +19979,36 @@ } } }, + "node_modules/next-intl": { + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.24.0.tgz", + "integrity": "sha512-48X68QsI92grir2dH1W15yhyVnEjW4c9qmwNt+du+k6mI1QtlE6GyANWHoL4/leTixHv8knZ1y9B/Ys06gmKLg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.5.4", + "negotiator": "^1.0.0", + "use-intl": "^3.24.0" + }, + "peerDependencies": { + "next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0" + } + }, + "node_modules/next-intl/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next-sanity": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/next-sanity/-/next-sanity-7.1.4.tgz", @@ -26799,6 +26892,19 @@ "react": ">=17.0.0" } }, + "node_modules/use-intl": { + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.24.0.tgz", + "integrity": "sha512-lmrARod7yjMYehbyY9xBLjjgnlNcJsl1UAltAPlgspRG7RH6H0JYaGo4C3PZW/BTy0Dgmcvcl8rH/VemzGIhgQ==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^2.2.0", + "intl-messageformat": "^10.5.14" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0" + } + }, "node_modules/use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", diff --git a/package.json b/package.json index 59895efbd..ef184eafd 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@sanity/vision": "^3.39.1", "negotiator": "^0.6.3", "next": "^14.2.15", + "next-intl": "^3.24.0", "next-sanity": "^7.1.4", "next-sanity-image": "^6.1.1", "react": "^18.3.1", diff --git a/src/app/(main)/[lang]/[...path]/page.tsx b/src/app/(main)/[locale]/[...path]/page.tsx similarity index 96% rename from src/app/(main)/[lang]/[...path]/page.tsx rename to src/app/(main)/[locale]/[...path]/page.tsx index 1a4e150eb..b3be90347 100644 --- a/src/app/(main)/[lang]/[...path]/page.tsx +++ b/src/app/(main)/[locale]/[...path]/page.tsx @@ -25,7 +25,7 @@ import { export const dynamic = "force-dynamic"; type Props = { - params: { lang: string; path: string[] }; + params: { locale: string; path: string[] }; }; function seoDataFromPageData( @@ -52,7 +52,7 @@ function seoDataFromPageData( export async function generateMetadata({ params }: Props): Promise { const { perspective } = getDraftModeInfo(); - const language = params.lang; + const language = params.locale; const pageData = await fetchPageDataFromParams({ language, path: params.path, @@ -71,12 +71,12 @@ const Page404 = ( ); async function Page({ params }: Props) { - const { lang, path } = params; + const { locale, path } = params; const { perspective, isDraftMode } = getDraftModeInfo(); const pageData = await fetchPageDataFromParams({ - language: lang, + language: locale, path, perspective: perspective ?? "published", hostname: headers().get("host"), @@ -91,7 +91,7 @@ async function Page({ params }: Props) { return ( <> ( ) { + if (!routing.locales.includes(params.locale as Locale)) { + notFound(); + } + + const messages = await getMessages(); + const { perspective, isDraftMode } = getDraftModeInfo(); const [ @@ -51,7 +60,7 @@ export default async function Layout({ ] = await Promise.all([ loadStudioQuery( NAV_QUERY, - { language: params.lang }, + { language: params.locale }, { perspective }, ), loadStudioQuery(COMPANY_INFO_QUERY, {}, { perspective }), @@ -62,7 +71,7 @@ export default async function Layout({ ), loadStudioQuery( LEGAL_DOCUMENTS_BY_LANG_QUERY, - { language: params.lang }, + { language: params.locale }, { perspective }, ), loadStudioQuery(BRAND_ASSETS_QUERY, {}, { perspective }), @@ -73,29 +82,31 @@ export default async function Layout({ const hasFooterData = hasNavData && initialNav.data.footer; return ( - + - - {children} - {hasFooterData && isDraftMode ? ( - - ) : ( -