Skip to content

Commit

Permalink
lingui & pseudo-LOCALE setup #96
Browse files Browse the repository at this point in the history
  • Loading branch information
djobbo committed Oct 22, 2023
1 parent 71d09ca commit 589a036
Show file tree
Hide file tree
Showing 16 changed files with 1,233 additions and 275 deletions.
10 changes: 8 additions & 2 deletions app/app/[locale]/_landing/DiscordCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import { ArrowSmRightIcon, DiscordIcon, KofiIcon } from "ui/icons"
import { Button } from "ui/base/Button"
import { Image } from "@/components/Image"
import { Trans } from "@lingui/macro"
import { clamp } from "common/helpers/math"
import { useEffect, useRef } from "react"

const DISCORD_MEMBERS = "3.8k+"

export const DiscordCard = () => {
const containerRef = useRef<HTMLDivElement>(null)
const cardRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -70,13 +73,16 @@ export const DiscordCard = () => {
containerClassName="w-20 h-20 rounded-3xl -mt-10 ml-6 border-8 border-bgVar2 overflow-hidden"
/>
<span className="text-sm ml-2 mt-2 text-textVar1 text-center">
3.8k+ discord members, and growing!
{/* {t`${DISCORD_MEMBERS} discord members, and growing!`} */}
<Trans>
{DISCORD_MEMBERS} discord members, and growing!
</Trans>
</span>
</div>
<div className="h-64 flex flex-col justify-between">
<div className="p-6 flex flex-col gap-2 text-sm">
<p className="uppercase text-textVar1 font-semibold">
Question of the day
<Trans>Question of the day</Trans>
</p>
<p>
Which weapon(s) do you enjoy playing the most? and
Expand Down
50 changes: 29 additions & 21 deletions app/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { BackToTopButton } from "@/components/BackToTopButton"
import { FeatureFlagsProvider } from "@/store/useFeatures"
import { KBarProvider } from "@/components/search/KBarProvider"
import { LayoutContent } from "./_layout/LayoutContent"
import { LinguiProvider } from "@/i18n/LinguiProvider"
import { type Metadata } from "next"
import { Montserrat } from "next/font/google"
import { type ReactNode } from "react"
import { SideNavProvider } from "@/providers/SideNavProvider"
import { TRPCProvider } from "../_trpc/TRPCProvider"
import { Toaster } from "react-hot-toast"
import { dir } from "i18next"
import { initServerTranslations } from "@/i18n/i18n"

const montserrat = Montserrat({ subsets: ["latin"] })

Expand Down Expand Up @@ -81,28 +83,34 @@ export default async function RootLayout({
children,
params,
}: RootLayoutProps) {
const i18nSetupData = await initServerTranslations(params)

return (
<TRPCProvider>
<FeatureFlagsProvider>
<AuthProvider>
<html lang={params.locale} dir={dir(params.locale)}>
<body className={montserrat.className}>
{/* TODO: reactivate GAScripts Google Analytics */}
{/* <GAScripts /> */}
<LinguiProvider {...i18nSetupData}>
<TRPCProvider>
<FeatureFlagsProvider>
<AuthProvider>
<html lang={params.locale} dir={dir(params.locale)}>
<body className={montserrat.className}>
{/* TODO: reactivate GAScripts Google Analytics */}
{/* <GAScripts /> */}

{/* TODO: change kbar for shadcn ui search */}
<KBarProvider actions={[]} options={{}}>
<SideNavProvider>
<Toaster />
<LayoutContent>{children}</LayoutContent>
{/* <Searchbox /> */}
<BackToTopButton />
</SideNavProvider>
</KBarProvider>
</body>
</html>
</AuthProvider>
</FeatureFlagsProvider>
</TRPCProvider>
{/* TODO: change kbar for shadcn ui search */}
<KBarProvider actions={[]} options={{}}>
<SideNavProvider>
<Toaster />
<LayoutContent>
{children}
</LayoutContent>
{/* <Searchbox /> */}
<BackToTopButton />
</SideNavProvider>
</KBarProvider>
</body>
</html>
</AuthProvider>
</FeatureFlagsProvider>
</TRPCProvider>
</LinguiProvider>
)
}
12 changes: 6 additions & 6 deletions app/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { LandingFavorites } from "./_landing/LandingFavorites"
import { SearchButton } from "@/components/search/SearchButton"
import { SectionTitle } from "@/components/layout/SectionTitle"
import { Suspense } from "react"
import { Trans } from "@lingui/macro"
import { WeeklyRotation } from "./_landing/WeeklyRotation"
import { cn } from "@/lib/utils"
import { css } from "ui/theme"
import { initServerTranslations } from "@/i18n/i18n"
import Link from "next/link"

const landingClassName = css({
Expand All @@ -29,9 +29,9 @@ type HomeProps = {
}
}

export default async function Home({ params: { locale } }: HomeProps) {
const { t } = await initServerTranslations({ locale })
const t = (str: string) => str

Check failure on line 32 in app/app/[locale]/page.tsx

View workflow job for this annotation

GitHub Actions / Analyze (Check Lint, pnpm ci:lint)

't' is assigned a value but never used. Allowed unused vars must match /^_/u

export default async function Home({ params: { locale } }: HomeProps) {

Check failure on line 34 in app/app/[locale]/page.tsx

View workflow job for this annotation

GitHub Actions / Analyze (Check Lint, pnpm ci:lint)

'locale' is defined but never used. Allowed unused args must match /^_/u
return (
<>
<div className="flex flex-col items-center justify-center lg:gap-16 lg:flex-row">
Expand Down Expand Up @@ -78,23 +78,23 @@ export default async function Home({ params: { locale } }: HomeProps) {
<div className="mt-8 flex items-center gap-3 sm:gap-6 flex-col sm:flex-row">
<SearchButton />
<span className="text-textVar1 text-sm sm:text-base">
{t`or`}
<Trans>or</Trans>
</span>
<div className="flex items-center gap-2">
<Button
as="a"
href="/rankings"
className="whitespace-nowrap font-semibold"
>
{t`View rankings`}
<Trans>View rankings</Trans>
</Button>
<Button
as="a"
buttonStyle="outline"
href="/rankings/2v2"
className="whitespace-nowrap font-semibold"
>
{t`2v2`}
<Trans>2v2</Trans>
</Button>
</div>
</div>
Expand Down
27 changes: 27 additions & 0 deletions app/i18n/LinguiProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client"

import { I18nProvider } from "@lingui/react"
import { type I18nSetupData } from "./i18n"
import { type ReactNode } from "react"
import { setupI18n } from "@lingui/core"

type LinguiProviderProps = I18nSetupData & {
children: ReactNode
}

export function LinguiProvider({
children,
messages,
locale,
}: LinguiProviderProps) {
return (
<I18nProvider
i18n={setupI18n({
messages,
locale,
})}
>
{children}
</I18nProvider>
)
}
16 changes: 16 additions & 0 deletions app/i18n/TransRSC.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react"

import { TransNoContext, type TransProps } from "@lingui/react/server"
import { getI18n } from "./i18n"

export const Trans = (props: TransProps) => {
const i18n = getI18n()

if (!i18n) {
throw new Error(
"Lingui for RSC is not initialized. Use `setI18n()` first in root of your RSC tree.",
)
}

return <TransNoContext {...props} lingui={{ i18n }} />
}
85 changes: 40 additions & 45 deletions app/i18n/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,47 @@
import { type Locale, defaultLocale, locales } from "./i18nConfig"
import { type ResourceLanguage, createInstance } from "i18next"
import { initReactI18next } from "react-i18next/initReactI18next"
import { z } from "zod"
import resourcesToBackend from "i18next-resources-to-backend"
import { type I18n, setupI18n } from "@lingui/core"
import { type Locale, localeSchema } from "./i18nConfig"
import { cache } from "react"
import { logError } from "logger"

const localeSchema = z.enum(locales).catch(defaultLocale)
const getLocaleCtx = cache((): { current: I18n | undefined } => {
return { current: undefined }
})

export const setI18n = (locale: I18n) => {
getLocaleCtx().current = locale
}

export const getI18n = (): I18n | undefined => {
return getLocaleCtx().current
}

const loadCatalog = async (locale: string) => {
try {
const catalog = await import(`@lingui/loader!./locales/${locale}.po`)
return catalog.messages
} catch (e) {
logError(`No catalog for locale "${locale}"`)
return {}
}
}

export type I18nSetupData = {
locale: Locale
messages: Partial<Record<Locale, Record<string, string>>>
}

export const initServerTranslations = async (params: { locale: string }) => {
const locale = localeSchema.parse(params.locale)
const i18nInstance = await initTranslations(locale)
const catalog = await loadCatalog(locale)

return i18nInstance
}
const i18nSetupData: I18nSetupData = {
locale,
messages: { [locale]: catalog },
}

const i18n = setupI18n(i18nSetupData)

setI18n(i18n)

const namespaces = ["translation"] as const

const en = {
translation: {
"hello world": "Hello World",
"View rankings": "View rankings",
},
} satisfies ResourceLanguage

export const initTranslations = async (locale: Locale) => {
const i18nInstance = createInstance()

await i18nInstance
.use(initReactI18next)
.use(
resourcesToBackend({
en,
fr: {
translation: {
"hello world": "Bonjour le monde",
"View rankings": "Voir le classement",
},
},
}),
)
.init({
lng: locale,
fallbackLng: defaultLocale,
supportedLngs: locales,
defaultNS: namespaces[0],
fallbackNS: namespaces[0],
ns: namespaces,
preload: typeof window === "undefined" ? locales : [],
})

return i18nInstance
return i18nSetupData
}
31 changes: 26 additions & 5 deletions app/i18n/i18nConfig.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
export const locales = ["en", "fr", "de", "ja"] as const
export type Locale = (typeof locales)[number]
export const defaultLocale: Locale = "en"
import { z } from "zod"

export const i18nConfig = {
const IS_DEV = process.env.NODE_ENV !== "production"

const locales = ["en", "fr"] as const
const devLocales = [...locales, "pseudo-LOCALE"] as const
export type Locale = (typeof locales)[number] | (typeof devLocales)[number]

type I18nConfig = {
locales: Locale[]
defaultLocale: Locale
}

const i18nDevConfig: I18nConfig = {
locales: [...devLocales],
defaultLocale: "en",
}

const i18nProdConfig: I18nConfig = {
locales: [...locales],
defaultLocale,
defaultLocale: "en",
}

export const i18nConfig = IS_DEV ? i18nDevConfig : i18nProdConfig

export const localeSchema =
process.env.NODE_ENV === "production"
? z.enum(locales).catch("en")
: z.enum(devLocales).catch("en")
34 changes: 34 additions & 0 deletions app/i18n/locales/en.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2023-10-22 00:59+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: en\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"

#: app/[locale]/_landing/DiscordCard.tsx:77
msgid "{DISCORD_MEMBERS} discord members, and growing!"
msgstr "{DISCORD_MEMBERS} discord members, and growing!"

#: app/[locale]/page.tsx:97
msgid "2v2"
msgstr "2v2"

#: app/[locale]/page.tsx:81
msgid "or"
msgstr "or"

#: app/[locale]/_landing/DiscordCard.tsx:85
msgid "Question of the day"
msgstr "Question of the day"

#: app/[locale]/page.tsx:89
msgid "View rankings"
msgstr "View rankings"
34 changes: 34 additions & 0 deletions app/i18n/locales/fr.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2023-10-22 00:59+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: fr\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"

#: app/[locale]/_landing/DiscordCard.tsx:77
msgid "{DISCORD_MEMBERS} discord members, and growing!"
msgstr "{DISCORD_MEMBERS} membres sur Discord, et ça continue !"

#: app/[locale]/page.tsx:97
msgid "2v2"
msgstr "2v2"

#: app/[locale]/page.tsx:81
msgid "or"
msgstr "ou"

#: app/[locale]/_landing/DiscordCard.tsx:85
msgid "Question of the day"
msgstr "Question du jour"

#: app/[locale]/page.tsx:89
msgid "View rankings"
msgstr "Voir le classement"
Loading

0 comments on commit 589a036

Please sign in to comment.