From d5c5f4fc175173307c32929f05a12d46b362c37c Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 4 May 2026 23:33:29 +0100 Subject: [PATCH] Add country localization --- messages/en-gb.json | 101 +++++ messages/en-ie.json | 101 +++++ messages/en-us.json | 101 +++++ middleware.ts | 9 + next.config.ts | 5 +- package-lock.json | 709 +++++++++++++++++++++++++++++++- package.json | 1 + src/app/[locale]/layout.tsx | 80 ++++ src/app/{ => [locale]}/page.tsx | 259 +++++++----- src/app/layout.tsx | 21 - src/i18n/navigation.ts | 6 + src/i18n/request.ts | 16 + src/i18n/routing.ts | 11 + 13 files changed, 1297 insertions(+), 123 deletions(-) create mode 100644 messages/en-gb.json create mode 100644 messages/en-ie.json create mode 100644 messages/en-us.json create mode 100644 middleware.ts create mode 100644 src/app/[locale]/layout.tsx rename src/app/{ => [locale]}/page.tsx (66%) delete mode 100644 src/app/layout.tsx create mode 100644 src/i18n/navigation.ts create mode 100644 src/i18n/request.ts create mode 100644 src/i18n/routing.ts diff --git a/messages/en-gb.json b/messages/en-gb.json new file mode 100644 index 0000000..4487f4f --- /dev/null +++ b/messages/en-gb.json @@ -0,0 +1,101 @@ +{ + "Meta": { + "title": "Kairas | Web design studio for UK brands", + "description": "Kairas is a web design firm crafting refined websites, brand systems, and digital products for UK teams." + }, + "Home": { + "nav": { + "studio": "Studio", + "services": "Services", + "work": "Work", + "notes": "Notes", + "contact": "hello@kairas.io" + }, + "localeSwitcher": { + "label": "Country", + "options": { + "en-ie": "Ireland", + "en-gb": "United Kingdom", + "en-us": "United States" + } + }, + "hero": { + "kicker": "Web design firm for UK brands", + "siteUrl": "kairas.io", + "words": ["Websites", "with quiet", "force."], + "interfaceLabel": "Selected interface", + "brandLabel": "Brand system", + "interfaceCopy": "Layouts tuned for calm reading and decisive action.", + "ticker": ["Strategy", "Design", "Build"], + "copy": "Kairas designs and builds refined websites for UK founders, studios, and service brands that care about taste, clarity, and commercial performance." + }, + "marquee": ["Strategy", "Design", "Development", "Systems", "Launch"], + "studio": { + "eyebrow": "About us", + "title": "We shape digital homes for UK brands that need to feel exact, intentional, and easy to trust.", + "copy": "Our work sits between design studio and front-end craft. We use strong typography, generous pacing, and disciplined systems to make every page feel composed without becoming static." + }, + "servicesTitle": "Services", + "services": [ + { + "number": "01", + "title": "Brand-led websites", + "text": "Identity, UX, interface design, and front-end systems for companies that need their site to carry the weight of the brand." + }, + { + "number": "02", + "title": "Editorial product pages", + "text": "Launch pages, case studies, and content structures that make complex offers feel considered, useful, and easy to move through." + }, + { + "number": "03", + "title": "Design systems", + "text": "Reusable components, visual rules, and interaction patterns that help teams ship new pages without losing quality." + } + ], + "work": { + "eyebrow": "Selected work", + "title": "Places where the brand can breathe.", + "items": [ + ["Atelier North", "Architecture portfolio", "2026"], + ["Vellum Labs", "SaaS website", "2025"], + ["Morrow House", "Hospitality booking", "2025"], + ["Plainform", "Brand system", "2024"] + ] + }, + "process": { + "title": "Process", + "steps": [ + { + "title": "Read", + "text": "We clarify the offer, audience, proof, and moments where a visitor needs confidence." + }, + { + "title": "Compose", + "text": "We design the system: type, pacing, components, motion notes, and content hierarchy." + }, + { + "title": "Ship", + "text": "We build responsive pages with practical handoff, analytics, and room to grow." + } + ] + }, + "notes": { + "title": "Notes", + "eyebrow": "Studio journal", + "items": [ + ["2026.04.18", "Essay", "Designing quieter conversion paths for premium service brands"], + ["2026.03.02", "Studio", "Kairas opens a focused website sprint for early-stage teams"], + ["2026.01.14", "Guide", "What belongs above the fold when the work is the proof"] + ] + }, + "footer": { + "brand": "Kairas", + "headline": "Build the site your brand has been waiting for.", + "siteUrl": "kairas.io", + "email": "hello@kairas.io", + "services": "Strategy / Design / Development", + "copyright": "© 2026 Kairas" + } + } +} diff --git a/messages/en-ie.json b/messages/en-ie.json new file mode 100644 index 0000000..24b815b --- /dev/null +++ b/messages/en-ie.json @@ -0,0 +1,101 @@ +{ + "Meta": { + "title": "Kairas | Web design studio in Ireland", + "description": "Kairas is an Ireland-based web design firm crafting refined websites, brand systems, and digital products." + }, + "Home": { + "nav": { + "studio": "Studio", + "services": "Services", + "work": "Work", + "notes": "Notes", + "contact": "hello@kairas.io" + }, + "localeSwitcher": { + "label": "Country", + "options": { + "en-ie": "Ireland", + "en-gb": "United Kingdom", + "en-us": "United States" + } + }, + "hero": { + "kicker": "Web design firm in Ireland", + "siteUrl": "kairas.io", + "words": ["Websites", "with quiet", "force."], + "interfaceLabel": "Selected interface", + "brandLabel": "Brand system", + "interfaceCopy": "Layouts tuned for calm reading and decisive action.", + "ticker": ["Strategy", "Design", "Build"], + "copy": "Kairas designs and builds refined websites for founders, studios, and service brands across Ireland that care about taste, clarity, and commercial performance." + }, + "marquee": ["Strategy", "Design", "Development", "Systems", "Launch"], + "studio": { + "eyebrow": "About us", + "title": "We shape digital homes for Irish brands that need to feel exact, intentional, and easy to trust.", + "copy": "Our work sits between design studio and front-end craft. We use strong typography, generous pacing, and disciplined systems to make every page feel composed without becoming static." + }, + "servicesTitle": "Services", + "services": [ + { + "number": "01", + "title": "Brand-led websites", + "text": "Identity, UX, interface design, and front-end systems for companies that need their site to carry the weight of the brand." + }, + { + "number": "02", + "title": "Editorial product pages", + "text": "Launch pages, case studies, and content structures that make complex offers feel considered, useful, and easy to move through." + }, + { + "number": "03", + "title": "Design systems", + "text": "Reusable components, visual rules, and interaction patterns that help teams ship new pages without losing quality." + } + ], + "work": { + "eyebrow": "Selected work", + "title": "Places where the brand can breathe.", + "items": [ + ["Atelier North", "Architecture portfolio", "2026"], + ["Vellum Labs", "SaaS website", "2025"], + ["Morrow House", "Hospitality booking", "2025"], + ["Plainform", "Brand system", "2024"] + ] + }, + "process": { + "title": "Process", + "steps": [ + { + "title": "Read", + "text": "We clarify the offer, audience, proof, and moments where a visitor needs confidence." + }, + { + "title": "Compose", + "text": "We design the system: type, pacing, components, motion notes, and content hierarchy." + }, + { + "title": "Ship", + "text": "We build responsive pages with practical handoff, analytics, and room to grow." + } + ] + }, + "notes": { + "title": "Notes", + "eyebrow": "Studio journal", + "items": [ + ["2026.04.18", "Essay", "Designing quieter conversion paths for premium service brands"], + ["2026.03.02", "Studio", "Kairas opens a focused website sprint for early-stage teams"], + ["2026.01.14", "Guide", "What belongs above the fold when the work is the proof"] + ] + }, + "footer": { + "brand": "Kairas", + "headline": "Build the site your brand has been waiting for.", + "siteUrl": "kairas.io", + "email": "hello@kairas.io", + "services": "Strategy / Design / Development", + "copyright": "© 2026 Kairas" + } + } +} diff --git a/messages/en-us.json b/messages/en-us.json new file mode 100644 index 0000000..501a814 --- /dev/null +++ b/messages/en-us.json @@ -0,0 +1,101 @@ +{ + "Meta": { + "title": "Kairas | Web design studio for US teams", + "description": "Kairas is a web design firm crafting refined websites, brand systems, and digital products for US founders and service brands." + }, + "Home": { + "nav": { + "studio": "Studio", + "services": "Services", + "work": "Work", + "notes": "Notes", + "contact": "hello@kairas.io" + }, + "localeSwitcher": { + "label": "Country", + "options": { + "en-ie": "Ireland", + "en-gb": "United Kingdom", + "en-us": "United States" + } + }, + "hero": { + "kicker": "Web design firm for US teams", + "siteUrl": "kairas.io", + "words": ["Websites", "with quiet", "force."], + "interfaceLabel": "Selected interface", + "brandLabel": "Brand system", + "interfaceCopy": "Layouts tuned for calm reading and decisive action.", + "ticker": ["Strategy", "Design", "Build"], + "copy": "Kairas designs and builds refined websites for US founders, studios, and service brands that care about taste, clarity, and commercial performance." + }, + "marquee": ["Strategy", "Design", "Development", "Systems", "Launch"], + "studio": { + "eyebrow": "About us", + "title": "We shape digital homes for US teams that need to feel exact, intentional, and easy to trust.", + "copy": "Our work sits between design studio and front-end craft. We use strong typography, generous pacing, and disciplined systems to make every page feel composed without becoming static." + }, + "servicesTitle": "Services", + "services": [ + { + "number": "01", + "title": "Brand-led websites", + "text": "Identity, UX, interface design, and front-end systems for companies that need their site to carry the weight of the brand." + }, + { + "number": "02", + "title": "Editorial product pages", + "text": "Launch pages, case studies, and content structures that make complex offers feel considered, useful, and easy to move through." + }, + { + "number": "03", + "title": "Design systems", + "text": "Reusable components, visual rules, and interaction patterns that help teams ship new pages without losing quality." + } + ], + "work": { + "eyebrow": "Selected work", + "title": "Places where the brand can breathe.", + "items": [ + ["Atelier North", "Architecture portfolio", "2026"], + ["Vellum Labs", "SaaS website", "2025"], + ["Morrow House", "Hospitality booking", "2025"], + ["Plainform", "Brand system", "2024"] + ] + }, + "process": { + "title": "Process", + "steps": [ + { + "title": "Read", + "text": "We clarify the offer, audience, proof, and moments where a visitor needs confidence." + }, + { + "title": "Compose", + "text": "We design the system: type, pacing, components, motion notes, and content hierarchy." + }, + { + "title": "Ship", + "text": "We build responsive pages with practical handoff, analytics, and room to grow." + } + ] + }, + "notes": { + "title": "Notes", + "eyebrow": "Studio journal", + "items": [ + ["2026.04.18", "Essay", "Designing quieter conversion paths for premium service brands"], + ["2026.03.02", "Studio", "Kairas opens a focused website sprint for early-stage teams"], + ["2026.01.14", "Guide", "What belongs above the fold when the work is the proof"] + ] + }, + "footer": { + "brand": "Kairas", + "headline": "Build the site your brand has been waiting for.", + "siteUrl": "kairas.io", + "email": "hello@kairas.io", + "services": "Strategy / Design / Development", + "copyright": "© 2026 Kairas" + } + } +} diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 0000000..f681a2b --- /dev/null +++ b/middleware.ts @@ -0,0 +1,9 @@ +import createMiddleware from "next-intl/middleware"; +import {routing} from "./src/i18n/routing"; + +export default createMiddleware(routing); + +export const config = { + matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"], +}; + diff --git a/next.config.ts b/next.config.ts index cb651cd..0f56720 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,5 +1,8 @@ import type { NextConfig } from "next"; +import createNextIntlPlugin from "next-intl/plugin"; const nextConfig: NextConfig = {}; -export default nextConfig; +const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts"); + +export default withNextIntl(nextConfig); diff --git a/package-lock.json b/package-lock.json index 448f3bf..04f2f49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "animejs": "^4.4.1", "next": "latest", + "next-intl": "^4.11.0", "react": "latest", "react-dom": "latest" }, @@ -467,6 +468,36 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@formatjs/fast-memoize": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.3.tgz", + "integrity": "sha512-Ocd1vPuD68rW6BJDuAOtnnc1GPeVepY5kZXML1psGVFQ+1Q8CfkftT3Tnam+Mxx97Pz08jIEDCotl/GV+Naccg==", + "license": "MIT" + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.6.tgz", + "integrity": "sha512-04ZjRIeQCnR/h32wBP9/S7rkyy1hLAs2fXJcNwc7hseJd//K9TMBqK0ukb4dXqnALKQ9m5ruZeOD2qqEkK9ixg==", + "license": "MIT", + "dependencies": { + "@formatjs/icu-skeleton-parser": "2.1.6" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.6.tgz", + "integrity": "sha512-9f1VQ2kaaLHK0WPU1OrAmiNKCKJwyoDmwNzQXbUa6XtFBOgHZ4YZURE8sSedHmMr0kvpB75OtplB0hMYkfdwfg==", + "license": "MIT" + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.5.tgz", + "integrity": "sha512-TEW/NR367c3PcQ2AXfkNig9jC740+qbkM0LgKl7UCE7Xtv7C5Uk1mvlu86MjQZBmscUai8HSWjcEETpwaVvJ6A==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "3.1.3" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1254,6 +1285,313 @@ "node": ">=12.4.0" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1261,6 +1599,210 @@ "dev": true, "license": "MIT" }, + "node_modules/@schummar/icu-type-parser": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", + "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", + "license": "MIT" + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.33.tgz", + "integrity": "sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.33.tgz", + "integrity": "sha512-/Il4QHSOhV4FekbsDtkrNmKbsX26oSysvgrRswa/RYOHXAkwXDbB4jaeKq6PsJLSPkzJ2KzQ061gtBnk0vNHfA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.33.tgz", + "integrity": "sha512-C64hBnBxq4viOPQ8hlx+2lJ23bzZBGnjw7ryALmS+0Q3zHmwO8lw1/DArLENw4Q18/0w5wdEO1k3m1wWNtKGqQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.33.tgz", + "integrity": "sha512-TRJfnJbX3jqpxRDRoieMzRiCBS5jOmXNb3iQXmcgjFEHKLnAgK1RZRU8Cq1MsPqO4jAJp/ld1G4O3fXuxv85uw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.33.tgz", + "integrity": "sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.33.tgz", + "integrity": "sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.33.tgz", + "integrity": "sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.33.tgz", + "integrity": "sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.33.tgz", + "integrity": "sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.33.tgz", + "integrity": "sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.33.tgz", + "integrity": "sha512-gtyvzSNR8DHKfFEA2uqb8Ld1myqi6uEg2jyeUq3ikn5ytYs7H8RpZYC8mdy4NXr8hfcdJfCLXPlYaqqfBXpoEQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.33.tgz", + "integrity": "sha512-d6fRqQSkJI+kmMEBWaDQ7TMl8+YjLYbwRUPZQ9DY0ORBJeTzOrG0twvfvlZ2xgw6jA0ScQKgfBm4vHLSLl5Hqg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1270,6 +1812,15 @@ "tslib": "^2.8.0" } }, + "node_modules/@swc/types": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@tailwindcss/node": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", @@ -2825,7 +3376,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3926,6 +4476,21 @@ "hermes-estree": "0.25.1" } }, + "node_modules/icu-minify": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.11.0.tgz", + "integrity": "sha512-XRvblCwLqWXio5ZLcmDqXvJv7alSACK6UjXuuMOdQWB//d25AQX6xlVlI1FEbc3Q6iPLXXo6HaVLn8LcAFhn1Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/icu-messageformat-parser": "^3.4.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3978,6 +4543,16 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.3.tgz", + "integrity": "sha512-kZthTU+3WLcoWoRg5j6LOkN1TeUBtmkX0OIwSAbcHVIfQAEbGVdmANM8u6GL3eUDOqLwheYoXMUshAh1UdeXlQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/fast-memoize": "3.1.3", + "@formatjs/icu-messageformat-parser": "3.5.6" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4140,7 +4715,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4186,7 +4760,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4984,6 +5557,15 @@ "dev": true, "license": "MIT" }, + "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": { "version": "16.2.4", "resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz", @@ -5037,6 +5619,94 @@ } } }, + "node_modules/next-intl": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.11.0.tgz", + "integrity": "sha512-Chp8rgEVUYOX/bCtYy+PXH6lDX3X+GPT9sR9HScHroL283em/4urP9btfdHEMEHJJXdq2W/5wDaDDtWONPdNSA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.8.1", + "@parcel/watcher": "^2.4.1", + "@swc/core": "^1.15.2", + "icu-minify": "^4.11.0", + "negotiator": "^1.0.0", + "next-intl-swc-plugin-extractor": "^4.11.0", + "po-parser": "^2.1.1", + "use-intl": "^4.11.0" + }, + "peerDependencies": { + "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/next-intl-swc-plugin-extractor": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.11.0.tgz", + "integrity": "sha512-WUGBSxGNd8eQ0rAsJHFmRw2H7+SZAXQIY/HAnYM57JaUsj5D2vx4KOz4zFtXlyKDtsw9awHfgWVvBae2/RDF9A==", + "license": "MIT" + }, + "node_modules/next-intl/node_modules/@swc/core": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.33.tgz", + "integrity": "sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.26" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.33", + "@swc/core-darwin-x64": "1.15.33", + "@swc/core-linux-arm-gnueabihf": "1.15.33", + "@swc/core-linux-arm64-gnu": "1.15.33", + "@swc/core-linux-arm64-musl": "1.15.33", + "@swc/core-linux-ppc64-gnu": "1.15.33", + "@swc/core-linux-s390x-gnu": "1.15.33", + "@swc/core-linux-x64-gnu": "1.15.33", + "@swc/core-linux-x64-musl": "1.15.33", + "@swc/core-win32-arm64-msvc": "1.15.33", + "@swc/core-win32-ia32-msvc": "1.15.33", + "@swc/core-win32-x64-msvc": "1.15.33" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/next-intl/node_modules/@swc/helpers": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -5065,6 +5735,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/node-exports-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", @@ -5341,6 +6017,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/po-parser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz", + "integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==", + "license": "MIT" + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -6434,6 +7116,27 @@ "punycode": "^2.1.0" } }, + "node_modules/use-intl": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.11.0.tgz", + "integrity": "sha512-7ILhTLuo3fnSKhoTGDk5X9591pjtWr6qB4inrlvGkN9OEyKhoiG73GZFoLSs68wz3BsSGtoWa62iWvrYEYU+iA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^3.1.0", + "@schummar/icu-type-parser": "1.21.5", + "icu-minify": "^4.11.0", + "intl-messageformat": "^11.1.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 11c76cd..50007d4 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "animejs": "^4.4.1", "next": "latest", + "next-intl": "^4.11.0", "react": "latest", "react-dom": "latest" }, diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx new file mode 100644 index 0000000..fb32825 --- /dev/null +++ b/src/app/[locale]/layout.tsx @@ -0,0 +1,80 @@ +import type {Metadata} from "next"; +import {NextIntlClientProvider, hasLocale} from "next-intl"; +import {notFound} from "next/navigation"; +import "../globals.css"; +import {routing, type Locale} from "@/i18n/routing"; + +type LocaleLayoutProps = Readonly<{ + children: React.ReactNode; + params: Promise<{locale: string}>; +}>; + +type LocaleMessages = { + Meta: { + title: string; + description: string; + }; +}; + +const alternates = Object.fromEntries([ + ...routing.locales.map((locale) => [locale, `/${locale}`]), + ["x-default", `/${routing.defaultLocale}`], +]); + +async function getLocaleMessages(locale: Locale): Promise { + return (await import(`../../../messages/${locale}.json`)).default; +} + +export function generateStaticParams() { + return routing.locales.map((locale) => ({locale})); +} + +export async function generateMetadata({ + params, +}: Pick): Promise { + const {locale} = await params; + + if (!hasLocale(routing.locales, locale)) { + notFound(); + } + + const messages = await getLocaleMessages(locale); + + return { + metadataBase: new URL("https://kairas.io"), + title: messages.Meta.title, + description: messages.Meta.description, + alternates: { + canonical: `/${locale}`, + languages: alternates, + }, + openGraph: { + title: messages.Meta.title, + description: messages.Meta.description, + url: `/${locale}`, + siteName: "Kairas", + locale: locale.replace("-", "_"), + type: "website", + }, + }; +} + +export default async function LocaleLayout({ + children, + params, +}: LocaleLayoutProps) { + const {locale} = await params; + + if (!hasLocale(routing.locales, locale)) { + notFound(); + } + + return ( + + + {children} + + + ); +} + diff --git a/src/app/page.tsx b/src/app/[locale]/page.tsx similarity index 66% rename from src/app/page.tsx rename to src/app/[locale]/page.tsx index 519aebf..b0ed9b1 100644 --- a/src/app/page.tsx +++ b/src/app/[locale]/page.tsx @@ -1,41 +1,37 @@ "use client"; -import { useEffect, useRef } from "react"; -import { animate, createTimeline, stagger } from "animejs"; +import {useEffect, useRef} from "react"; +import {animate, createTimeline, stagger} from "animejs"; +import {useLocale, useTranslations} from "next-intl"; +import {Link} from "@/i18n/navigation"; +import {routing, type Locale} from "@/i18n/routing"; -const services = [ - { - number: "01", - title: "Brand-led websites", - text: "Identity, UX, interface design, and front-end systems for companies that need their site to carry the weight of the brand.", - }, - { - number: "02", - title: "Editorial product pages", - text: "Launch pages, case studies, and content structures that make complex offers feel considered, useful, and easy to move through.", - }, - { - number: "03", - title: "Design systems", - text: "Reusable components, visual rules, and interaction patterns that help teams ship new pages without losing quality.", - }, -]; +type Service = { + number: string; + title: string; + text: string; +}; -const work = [ - ["Atelier North", "Architecture portfolio", "2026"], - ["Vellum Labs", "SaaS website", "2025"], - ["Morrow House", "Hospitality booking", "2025"], - ["Plainform", "Brand system", "2024"], -]; +type WorkItem = [string, string, string]; +type NoteItem = [string, string, string]; -const notes = [ - ["2026.04.18", "Essay", "Designing quieter conversion paths for premium service brands"], - ["2026.03.02", "Studio", "Kairas opens a focused website sprint for early-stage teams"], - ["2026.01.14", "Guide", "What belongs above the fold when the work is the proof"], -]; +type ProcessStep = { + title: string; + text: string; +}; export default function Home() { const rootRef = useRef(null); + const locale = useLocale() as Locale; + const t = useTranslations("Home"); + + const heroWords = t.raw("hero.words") as string[]; + const tickerItems = t.raw("hero.ticker") as string[]; + const marqueeItems = t.raw("marquee") as string[]; + const services = t.raw("services") as Service[]; + const work = t.raw("work.items") as WorkItem[]; + const processSteps = t.raw("process.steps") as ProcessStep[]; + const notes = t.raw("notes.items") as NoteItem[]; useEffect(() => { const root = rootRef.current; @@ -179,7 +175,7 @@ export default function Home() { observer.unobserve(target); }); }, - { threshold: 0.18 }, + {threshold: 0.18}, ); root.querySelectorAll("[data-reveal]").forEach((element) => { @@ -207,7 +203,7 @@ export default function Home() { observer.disconnect(); animations.forEach((animation) => animation.revert()); }; - }, []); + }, [locale]); return (
-
-
+
- Web design firm + + {t("hero.kicker")} + - kairas.io + {t("hero.siteUrl")}

- Websites - with quiet - force. + {heroWords.map((word) => ( + + {word} + + ))}

@@ -262,18 +310,20 @@ export default function Home() {
- Selected interface + {t("hero.interfaceLabel")} 01 / 04
- Brand system + {t("hero.brandLabel")}
-

KA

+

+ KA +

- Layouts tuned for calm reading and decisive action. + {t("hero.interfaceCopy")}

@@ -290,33 +340,27 @@ export default function Home() {
- Strategy - Design - Build + {tickerItems.map((item) => ( + + {item} + + ))}

- Kairas designs and builds refined websites for founders, studios, - and service brands that care about taste, clarity, and commercial - performance. + {t("hero.copy")}

- {Array.from({ length: 2 }).map((_, group) => ( + {Array.from({length: 2}).map((_, group) => (
- Strategy - / - Design - / - Development - / - Systems - / - Launch + {marqueeItems.map((item) => ( + {item} + ))} /
))} @@ -329,24 +373,29 @@ export default function Home() { className="motion-hidden relative z-10 border-y border-[var(--line)] bg-[var(--paper)]" >
-

About us

+

+ {t("studio.eyebrow")} +

- We shape digital homes for brands that need to feel exact, - intentional, and easy to trust. + {t("studio.title")}

- Our work sits between design studio and front-end craft. We use - strong typography, generous pacing, and disciplined systems to - make every page feel composed without becoming static. + {t("studio.copy")}

-
+
-

Services

+

+ {t("servicesTitle")} +

03
@@ -355,7 +404,9 @@ export default function Home() { key={service.title} className="grid gap-6 border-b border-[var(--line)] py-9 md:grid-cols-[0.22fr_0.78fr] lg:grid-cols-[0.18fr_0.38fr_0.44fr]" > - {service.number} + + {service.number} +

{service.title}

{service.text} @@ -366,14 +417,18 @@ export default function Home() {

-
+

- Selected work + {t("work.eyebrow")}

- Places where the brand can breathe. + {t("work.title")}

@@ -396,34 +451,40 @@ export default function Home() {
-
-

Process

+
+

+ {t("process.title")} +

- {["Read", "Compose", "Ship"].map((step, index) => ( -
+ {processSteps.map((step, index) => ( +
{String(index + 1).padStart(2, "0")} -

{step}

+

{step.title}

- {index === 0 && - "We clarify the offer, audience, proof, and moments where a visitor needs confidence."} - {index === 1 && - "We design the system: type, pacing, components, motion notes, and content hierarchy."} - {index === 2 && - "We build responsive pages with practical handoff, analytics, and room to grow."} + {step.text}

))}
-
+
-

Notes

+

+ {t("notes.title")} +

- Studio journal + {t("notes.eyebrow")}

@@ -450,16 +511,18 @@ export default function Home() { className="motion-hidden relative z-10 mx-auto grid max-w-[1500px] gap-12 px-5 py-16 sm:px-8 lg:grid-cols-[1fr_auto] lg:px-12" >
-

Kairas

+

+ {t("footer.brand")} +

- Build the site your brand has been waiting for. + {t("footer.headline")}

- kairas.io - hello@kairas.io -

Strategy / Design / Development

-

© 2026 Kairas

+ {t("footer.siteUrl")} + {t("footer.email")} +

{t("footer.services")}

+

{t("footer.copyright")}

diff --git a/src/app/layout.tsx b/src/app/layout.tsx deleted file mode 100644 index d269add..0000000 --- a/src/app/layout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { Metadata } from "next"; -import "./globals.css"; - -export const metadata: Metadata = { - metadataBase: new URL("https://kairas.io"), - title: "Kairas | Web design studio", - description: - "Kairas is a web design firm crafting refined websites, brand systems, and digital products.", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); -} diff --git a/src/i18n/navigation.ts b/src/i18n/navigation.ts new file mode 100644 index 0000000..b7e135d --- /dev/null +++ b/src/i18n/navigation.ts @@ -0,0 +1,6 @@ +import {createNavigation} from "next-intl/navigation"; +import {routing} from "./routing"; + +export const {Link, redirect, usePathname, useRouter, getPathname} = + createNavigation(routing); + diff --git a/src/i18n/request.ts b/src/i18n/request.ts new file mode 100644 index 0000000..744a5ba --- /dev/null +++ b/src/i18n/request.ts @@ -0,0 +1,16 @@ +import {getRequestConfig} from "next-intl/server"; +import {hasLocale} from "next-intl"; +import {routing} from "./routing"; + +export default getRequestConfig(async ({requestLocale}) => { + const requested = await requestLocale; + const locale = hasLocale(routing.locales, requested) + ? requested + : routing.defaultLocale; + + return { + locale, + messages: (await import(`../../messages/${locale}.json`)).default, + }; +}); + diff --git a/src/i18n/routing.ts b/src/i18n/routing.ts new file mode 100644 index 0000000..cc37562 --- /dev/null +++ b/src/i18n/routing.ts @@ -0,0 +1,11 @@ +import {defineRouting} from "next-intl/routing"; + +export const routing = defineRouting({ + locales: ["en-ie", "en-gb", "en-us"], + defaultLocale: "en-ie", + localePrefix: "always", + localeDetection: true, +}); + +export type Locale = (typeof routing.locales)[number]; +