Merged in feat/sw-2333-package-and-sas-i18n (pull request #2538)
feat(SW-2333): I18n for multiple apps and packages * Set upp i18n in partner-sas * Adapt lokalise workflow to monorepo * Fix layout props Approved-by: Linus Flood
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import { trpc } from "@scandic-hotels/trpc/client"
|
||||
|
||||
export function ClientComponent() {
|
||||
const intl = useIntl()
|
||||
const { data, isLoading } = trpc.autocomplete.destinations.useQuery({
|
||||
lang: Lang.en,
|
||||
includeTypes: ["hotels"],
|
||||
@@ -15,6 +18,10 @@ export function ClientComponent() {
|
||||
<p>client component</p>
|
||||
<p>Data: {JSON.stringify(data?.hits?.hotels[0]?.name)}</p>
|
||||
<p>Is loading: {isLoading ? "Yes" : "No"}</p>
|
||||
<p>Translated text: </p>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "All-day breakfast",
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,13 @@ import "@scandic-hotels/design-system/fonts.css"
|
||||
import "@/public/_static/css/design-system-new-deprecated.css"
|
||||
import "./globals.css"
|
||||
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import { TrpcProvider } from "@scandic-hotels/trpc/Provider"
|
||||
|
||||
import { getMessages } from "../i18n"
|
||||
import ClientIntlProvider from "../i18n/Provider"
|
||||
import { setLang } from "../i18n/serverContext"
|
||||
|
||||
import type { Metadata } from "next"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -12,11 +17,24 @@ export const metadata: Metadata = {
|
||||
description: "Generated by create next app",
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
type LangParams = {
|
||||
lang: Lang
|
||||
}
|
||||
|
||||
type RootLayoutProps = {
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
params: Promise<LangParams>
|
||||
}
|
||||
|
||||
export default async function RootLayout(props: RootLayoutProps) {
|
||||
// const params = await props.params
|
||||
const params = { lang: Lang.sv }
|
||||
|
||||
const { children } = props
|
||||
|
||||
setLang(params.lang)
|
||||
const messages = await getMessages(params.lang)
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -26,8 +44,14 @@ export default function RootLayout({
|
||||
<link rel="stylesheet" href="/_static/css/scandic.css" />
|
||||
</head>
|
||||
<body className="scandic">
|
||||
{/* TODO handle onError */}
|
||||
<TrpcProvider>{children}</TrpcProvider>
|
||||
<ClientIntlProvider
|
||||
defaultLocale={Lang.en}
|
||||
locale={params.lang}
|
||||
messages={messages}
|
||||
>
|
||||
{/* TODO handle onError */}
|
||||
<TrpcProvider>{children}</TrpcProvider>
|
||||
</ClientIntlProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -4,11 +4,13 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { serverClient } from "@/lib/trpc"
|
||||
|
||||
import { getIntl } from "../i18n"
|
||||
import { ClientComponent } from "./ClientComponent"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
export default async function Home() {
|
||||
const intl = await getIntl()
|
||||
const caller = await serverClient()
|
||||
const destinations = await caller.autocomplete.destinations({
|
||||
lang: Lang.en,
|
||||
@@ -24,6 +26,9 @@ export default async function Home() {
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<p>hello world with data: {hotel}</p>
|
||||
</Typography>
|
||||
<Typography>
|
||||
<p>{intl.formatMessage({ defaultMessage: "Map of the city" })}</p>
|
||||
</Typography>
|
||||
<hr />
|
||||
<ClientComponent />
|
||||
<hr />
|
||||
|
||||
43
apps/partner-sas/i18n/Provider.tsx
Normal file
43
apps/partner-sas/i18n/Provider.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client"
|
||||
|
||||
import { type IntlConfig, IntlProvider } from "react-intl"
|
||||
|
||||
import { logger } from "@scandic-hotels/common/logger"
|
||||
|
||||
type ClientIntlProviderProps = React.PropsWithChildren<
|
||||
Pick<IntlConfig, "defaultLocale" | "locale" | "messages">
|
||||
>
|
||||
|
||||
const logged: Record<string, boolean> = {}
|
||||
|
||||
export default function ClientIntlProvider({
|
||||
children,
|
||||
locale,
|
||||
defaultLocale,
|
||||
messages,
|
||||
}: ClientIntlProviderProps) {
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={locale}
|
||||
defaultLocale={defaultLocale}
|
||||
messages={messages}
|
||||
onError={(err) => {
|
||||
let msg = err.message
|
||||
|
||||
if (err.code === "MISSING_TRANSLATION") {
|
||||
const id = err.descriptor?.id
|
||||
if (id) {
|
||||
msg = id
|
||||
}
|
||||
}
|
||||
|
||||
if (!logged[msg]) {
|
||||
logged[msg] = true
|
||||
logger.warn("IntlProvider", err)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
)
|
||||
}
|
||||
7930
apps/partner-sas/i18n/dictionaries/da.json
Normal file
7930
apps/partner-sas/i18n/dictionaries/da.json
Normal file
File diff suppressed because it is too large
Load Diff
7903
apps/partner-sas/i18n/dictionaries/de.json
Normal file
7903
apps/partner-sas/i18n/dictionaries/de.json
Normal file
File diff suppressed because it is too large
Load Diff
7995
apps/partner-sas/i18n/dictionaries/en.json
Normal file
7995
apps/partner-sas/i18n/dictionaries/en.json
Normal file
File diff suppressed because it is too large
Load Diff
7963
apps/partner-sas/i18n/dictionaries/fi.json
Normal file
7963
apps/partner-sas/i18n/dictionaries/fi.json
Normal file
File diff suppressed because it is too large
Load Diff
7961
apps/partner-sas/i18n/dictionaries/no.json
Normal file
7961
apps/partner-sas/i18n/dictionaries/no.json
Normal file
File diff suppressed because it is too large
Load Diff
20
apps/partner-sas/i18n/dictionaries/pl.json
Normal file
20
apps/partner-sas/i18n/dictionaries/pl.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"WcBpRV": [
|
||||
{
|
||||
"type": 1,
|
||||
"value": "roomSizeMin"
|
||||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": "-"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"value": "roomSizeMax"
|
||||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " m²"
|
||||
}
|
||||
]
|
||||
}
|
||||
7925
apps/partner-sas/i18n/dictionaries/sv.json
Normal file
7925
apps/partner-sas/i18n/dictionaries/sv.json
Normal file
File diff suppressed because it is too large
Load Diff
35
apps/partner-sas/i18n/index.ts
Normal file
35
apps/partner-sas/i18n/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import "server-only"
|
||||
|
||||
import { createIntl, createIntlCache } from "@formatjs/intl"
|
||||
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
import { getLang } from "./serverContext"
|
||||
|
||||
import type { IntlShape } from "react-intl"
|
||||
|
||||
const cache = createIntlCache()
|
||||
|
||||
const instances: Partial<Record<Lang, IntlShape>> = {}
|
||||
|
||||
export async function getMessages(lang: Lang): Promise<Record<string, string>> {
|
||||
return (await import(`./dictionaries/${lang}.json`)).default
|
||||
}
|
||||
|
||||
export async function getIntl(options?: { lang: Lang | undefined }) {
|
||||
const lang = options?.lang || (await getLang())
|
||||
|
||||
if (!instances[lang]) {
|
||||
const messages = await getMessages(lang)
|
||||
instances[lang] = createIntl<React.ReactNode>(
|
||||
{
|
||||
defaultLocale: Lang.en,
|
||||
locale: lang,
|
||||
messages,
|
||||
},
|
||||
cache
|
||||
)
|
||||
}
|
||||
|
||||
return instances[lang]
|
||||
}
|
||||
34
apps/partner-sas/i18n/serverContext.ts
Normal file
34
apps/partner-sas/i18n/serverContext.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import "server-only"
|
||||
|
||||
import { headers } from "next/headers"
|
||||
import { cache } from "react"
|
||||
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import { languageSchema } from "@scandic-hotels/common/utils/languages"
|
||||
|
||||
const getRef = cache(() => ({ current: undefined as Lang | undefined }))
|
||||
|
||||
/**
|
||||
* Set the language for the current request
|
||||
*
|
||||
* It works kind of like React's context,
|
||||
* but on the server side, per request.
|
||||
*
|
||||
* @param newLang
|
||||
*/
|
||||
export function setLang(newLang: Lang) {
|
||||
const parseResult = languageSchema.safeParse(newLang)
|
||||
getRef().current = parseResult.success ? parseResult.data : Lang.en
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global language set for the current request
|
||||
*/
|
||||
export async function getLang(): Promise<Lang> {
|
||||
const contextLang = getRef().current
|
||||
const headersList = await headers()
|
||||
const headerLang = headersList.get("x-lang") as Lang
|
||||
|
||||
const l = contextLang || headerLang || Lang.en
|
||||
return l
|
||||
}
|
||||
@@ -27,6 +27,17 @@ const nextConfig: NextConfig = {
|
||||
|
||||
return config
|
||||
},
|
||||
|
||||
experimental: {
|
||||
swcPlugins: [
|
||||
[
|
||||
"@swc/plugin-formatjs",
|
||||
{
|
||||
ast: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default Sentry.withSentryConfig(nextConfig, {
|
||||
|
||||
@@ -15,14 +15,18 @@
|
||||
"test:e2e": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formatjs/intl": "^3.1.6",
|
||||
"@netlify/plugin-nextjs": "^5.11.2",
|
||||
"@scandic-hotels/booking-flow": "workspace:*",
|
||||
"@scandic-hotels/design-system": "workspace:*",
|
||||
"@scandic-hotels/trpc": "workspace:*",
|
||||
"@sentry/nextjs": "^8.41.0",
|
||||
"@swc/plugin-formatjs": "^3.2.2",
|
||||
"next": "15.3.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
"react-dom": "^19.0.0",
|
||||
"react-intl": "^7.1.11",
|
||||
"server-only": "^0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
@@ -35,6 +39,7 @@
|
||||
"@types/react-dom": "19.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.0",
|
||||
"@typescript-eslint/parser": "^8.32.0",
|
||||
"babel-plugin-formatjs": "^10.5.39",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.2",
|
||||
"eslint-plugin-formatjs": "^5.3.1",
|
||||
|
||||
Reference in New Issue
Block a user