diff --git a/apps/web/src/app/[locale]/(admin)/events/page.tsx b/apps/web/src/app/[locale]/(admin)/events/page.tsx
index 901e0cbd0..f63bac478 100644
--- a/apps/web/src/app/[locale]/(admin)/events/page.tsx
+++ b/apps/web/src/app/[locale]/(admin)/events/page.tsx
@@ -6,7 +6,7 @@ import {
PageHeader,
PageTitle,
} from "@/app/components/page-layout";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
export default async function Page({ params }: { params: Params }) {
const { t } = await getTranslation(params.locale);
diff --git a/apps/web/src/app/[locale]/(admin)/menu/page.tsx b/apps/web/src/app/[locale]/(admin)/menu/page.tsx
index 7623c0f99..5cefabea9 100644
--- a/apps/web/src/app/[locale]/(admin)/menu/page.tsx
+++ b/apps/web/src/app/[locale]/(admin)/menu/page.tsx
@@ -8,7 +8,7 @@ import {
PageHeader,
PageTitle,
} from "@/app/components/page-layout";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
export default async function Page({ params }: { params: Params }) {
const { t } = await getTranslation(params.locale);
diff --git a/apps/web/src/app/[locale]/(admin)/page.tsx b/apps/web/src/app/[locale]/(admin)/page.tsx
index fd7aab034..f406c6b29 100644
--- a/apps/web/src/app/[locale]/(admin)/page.tsx
+++ b/apps/web/src/app/[locale]/(admin)/page.tsx
@@ -10,7 +10,7 @@ import {
PageIcon,
PageTitle,
} from "@/app/components/page-layout";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
export default async function Page({ params }: { params: Params }) {
const { t } = await getTranslation(params.locale);
diff --git a/apps/web/src/app/[locale]/(admin)/polls/page.tsx b/apps/web/src/app/[locale]/(admin)/polls/page.tsx
index 497bcd49a..587fd1456 100644
--- a/apps/web/src/app/[locale]/(admin)/polls/page.tsx
+++ b/apps/web/src/app/[locale]/(admin)/polls/page.tsx
@@ -9,7 +9,7 @@ import {
PageIcon,
PageTitle,
} from "@/app/components/page-layout";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
export default async function Page({
params,
diff --git a/apps/web/src/app/[locale]/(admin)/settings/billing/page.tsx b/apps/web/src/app/[locale]/(admin)/settings/billing/page.tsx
index 3e81f2dc2..3efabbaa7 100644
--- a/apps/web/src/app/[locale]/(admin)/settings/billing/page.tsx
+++ b/apps/web/src/app/[locale]/(admin)/settings/billing/page.tsx
@@ -3,8 +3,8 @@ import { notFound } from "next/navigation";
import { BillingPage } from "@/app/[locale]/(admin)/settings/billing/billing-page";
import { Params } from "@/app/[locale]/types";
-import { getTranslation } from "@/app/i18n";
import { env } from "@/env";
+import { getTranslation } from "@/i18n/server";
export default async function Page() {
if (env.NEXT_PUBLIC_SELF_HOSTED === "true") {
diff --git a/apps/web/src/app/[locale]/(admin)/settings/layout.tsx b/apps/web/src/app/[locale]/(admin)/settings/layout.tsx
index 3d621b6ae..2e99fb1d8 100644
--- a/apps/web/src/app/[locale]/(admin)/settings/layout.tsx
+++ b/apps/web/src/app/[locale]/(admin)/settings/layout.tsx
@@ -6,7 +6,7 @@ import {
PageHeader,
PageTitle,
} from "@/app/components/page-layout";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
import { SettingsMenu } from "./settings-menu";
diff --git a/apps/web/src/app/[locale]/(admin)/settings/preferences/page.tsx b/apps/web/src/app/[locale]/(admin)/settings/preferences/page.tsx
index 377df6db4..edd8f174a 100644
--- a/apps/web/src/app/[locale]/(admin)/settings/preferences/page.tsx
+++ b/apps/web/src/app/[locale]/(admin)/settings/preferences/page.tsx
@@ -1,5 +1,5 @@
import { Params } from "@/app/[locale]/types";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
import { PreferencesPage } from "./preferences-page";
diff --git a/apps/web/src/app/[locale]/(admin)/settings/preferences/preferences-page.tsx b/apps/web/src/app/[locale]/(admin)/settings/preferences/preferences-page.tsx
index a6c6cc1b7..3b48cb2cc 100644
--- a/apps/web/src/app/[locale]/(admin)/settings/preferences/preferences-page.tsx
+++ b/apps/web/src/app/[locale]/(admin)/settings/preferences/preferences-page.tsx
@@ -1,7 +1,6 @@
"use client";
import Head from "next/head";
-import { useTranslation } from "@/app/i18n/client";
import { DateTimePreferences } from "@/components/settings/date-time-preferences";
import { LanguagePreference } from "@/components/settings/language-preference";
import {
@@ -10,6 +9,7 @@ import {
SettingsSection,
} from "@/components/settings/settings";
import { Trans } from "@/components/trans";
+import { useTranslation } from "@/i18n/client";
export function PreferencesPage() {
const { t } = useTranslation();
diff --git a/apps/web/src/app/[locale]/(admin)/settings/profile/delete-account-dialog.tsx b/apps/web/src/app/[locale]/(admin)/settings/profile/delete-account-dialog.tsx
index 935fdf1e7..04c2ca7e1 100644
--- a/apps/web/src/app/[locale]/(admin)/settings/profile/delete-account-dialog.tsx
+++ b/apps/web/src/app/[locale]/(admin)/settings/profile/delete-account-dialog.tsx
@@ -15,8 +15,8 @@ import { Input } from "@rallly/ui/input";
import { signOut } from "next-auth/react";
import { useForm } from "react-hook-form";
-import { useTranslation } from "@/app/i18n/client";
import { Trans } from "@/components/trans";
+import { useTranslation } from "@/i18n/client";
import { usePostHog } from "@/utils/posthog";
import { trpc } from "@/utils/trpc/client";
@@ -32,7 +32,7 @@ export function DeleteAccountDialog({
email: "",
},
});
- const { t } = useTranslation("app");
+ const { t } = useTranslation();
const trpcUtils = trpc.useUtils();
const posthog = usePostHog();
const deleteAccount = trpc.user.delete.useMutation({
diff --git a/apps/web/src/app/[locale]/(admin)/settings/profile/page.tsx b/apps/web/src/app/[locale]/(admin)/settings/profile/page.tsx
index 30a7eaa6c..f80ee04fb 100644
--- a/apps/web/src/app/[locale]/(admin)/settings/profile/page.tsx
+++ b/apps/web/src/app/[locale]/(admin)/settings/profile/page.tsx
@@ -1,5 +1,5 @@
import { Params } from "@/app/[locale]/types";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
import { ProfilePage } from "./profile-page";
diff --git a/apps/web/src/app/[locale]/(admin)/settings/profile/profile-picture.tsx b/apps/web/src/app/[locale]/(admin)/settings/profile/profile-picture.tsx
index 44b34aafe..e12a3b42f 100644
--- a/apps/web/src/app/[locale]/(admin)/settings/profile/profile-picture.tsx
+++ b/apps/web/src/app/[locale]/(admin)/settings/profile/profile-picture.tsx
@@ -4,11 +4,11 @@ import * as Sentry from "@sentry/nextjs";
import React, { useState } from "react";
import { z } from "zod";
-import { useTranslation } from "@/app/i18n/client";
import { OptimizedAvatarImage } from "@/components/optimized-avatar-image";
import { Trans } from "@/components/trans";
import { useUser } from "@/components/user-provider";
import { IfCloudHosted } from "@/contexts/environment";
+import { useTranslation } from "@/i18n/client";
import { usePostHog } from "@/utils/posthog";
import { trpc } from "@/utils/trpc/client";
diff --git a/apps/web/src/app/[locale]/(auth)/login/page.tsx b/apps/web/src/app/[locale]/(auth)/login/page.tsx
index 10caefed9..514774f15 100644
--- a/apps/web/src/app/[locale]/(auth)/login/page.tsx
+++ b/apps/web/src/app/[locale]/(auth)/login/page.tsx
@@ -3,8 +3,8 @@ import { Trans } from "react-i18next/TransWithoutContext";
import { LoginForm } from "@/app/[locale]/(auth)/login/login-form";
import { Params } from "@/app/[locale]/types";
-import { getTranslation } from "@/app/i18n";
import { AuthCard } from "@/components/auth/auth-layout";
+import { getTranslation } from "@/i18n/server";
export default async function LoginPage({ params }: { params: Params }) {
const { t } = await getTranslation(params.locale);
diff --git a/apps/web/src/app/[locale]/(auth)/register/page.tsx b/apps/web/src/app/[locale]/(auth)/register/page.tsx
index ffab0cecd..4c5cec707 100644
--- a/apps/web/src/app/[locale]/(auth)/register/page.tsx
+++ b/apps/web/src/app/[locale]/(auth)/register/page.tsx
@@ -1,6 +1,6 @@
import { RegisterForm } from "@/app/[locale]/(auth)/register/register-page";
import { Params } from "@/app/[locale]/types";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
export default async function Page() {
return ;
diff --git a/apps/web/src/app/[locale]/auth/login/page.tsx b/apps/web/src/app/[locale]/auth/login/page.tsx
index 4d5a7b7c4..6c68b3d62 100644
--- a/apps/web/src/app/[locale]/auth/login/page.tsx
+++ b/apps/web/src/app/[locale]/auth/login/page.tsx
@@ -1,7 +1,7 @@
import { notFound } from "next/navigation";
import { z } from "zod";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
import { LoginPage } from "./login-page";
diff --git a/apps/web/src/app/[locale]/invite/[urlId]/page.tsx b/apps/web/src/app/[locale]/invite/[urlId]/page.tsx
index 5799090e3..6a63e0ce6 100644
--- a/apps/web/src/app/[locale]/invite/[urlId]/page.tsx
+++ b/apps/web/src/app/[locale]/invite/[urlId]/page.tsx
@@ -3,7 +3,7 @@ import { Metadata } from "next";
import { notFound } from "next/navigation";
import { InvitePage } from "@/app/[locale]/invite/[urlId]/invite-page";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
import { absoluteUrl } from "@/utils/absolute-url";
export default async function Page() {
diff --git a/apps/web/src/app/[locale]/new/page.tsx b/apps/web/src/app/[locale]/new/page.tsx
index c442a58e0..69e3bed21 100644
--- a/apps/web/src/app/[locale]/new/page.tsx
+++ b/apps/web/src/app/[locale]/new/page.tsx
@@ -3,9 +3,9 @@ import { Trans } from "react-i18next/TransWithoutContext";
import { GroupPollIcon } from "@/app/[locale]/(admin)/app-card";
import { BackButton } from "@/app/[locale]/(admin)/menu/menu-button";
import { Params } from "@/app/[locale]/types";
-import { getTranslation } from "@/app/i18n";
import { CreatePoll } from "@/components/create-poll";
import { UserDropdown } from "@/components/user-dropdown";
+import { getTranslation } from "@/i18n/server";
export default async function Page({ params }: { params: Params }) {
const { t } = await getTranslation(params.locale);
diff --git a/apps/web/src/app/[locale]/not-found.tsx b/apps/web/src/app/[locale]/not-found.tsx
index 5bb139950..b8fc1e183 100644
--- a/apps/web/src/app/[locale]/not-found.tsx
+++ b/apps/web/src/app/[locale]/not-found.tsx
@@ -2,7 +2,7 @@ import { Button } from "@rallly/ui/button";
import { FileSearchIcon } from "lucide-react";
import Link from "next/link";
-import { getTranslation } from "@/app/i18n";
+import { getTranslation } from "@/i18n/server";
export default async function Page() {
// TODO (Luke Vella) [2023-11-03]: not-found doesn't have access to params right now
diff --git a/apps/web/src/app/i18n/client.tsx b/apps/web/src/app/i18n/client.tsx
deleted file mode 100644
index 14987801d..000000000
--- a/apps/web/src/app/i18n/client.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-"use client";
-import i18next, { Namespace } from "i18next";
-import ICU from "i18next-icu";
-import resourcesToBackend from "i18next-resources-to-backend";
-import { useParams } from "next/navigation";
-import React from "react";
-import {
- I18nextProvider,
- initReactI18next,
- useTranslation as useTranslationOrg,
-} from "react-i18next";
-import { useAsync } from "react-use";
-
-import { defaultNS, getOptions } from "./settings";
-
-async function initTranslations(lng: string) {
- const i18n = i18next
- .use(initReactI18next)
- .use(ICU)
- .use(
- resourcesToBackend(
- (language: string, namespace: string) =>
- import(`../../../public/locales/${language}/${namespace}.json`),
- ),
- );
- await i18n.init(getOptions(lng));
-
- return i18n;
-}
-
-export function useTranslation(ns?: Namespace) {
- return useTranslationOrg(ns);
-}
-
-export function I18nProvider({ children }: { children: React.ReactNode }) {
- const params = useParams<{ locale: string }>();
- const locale = params?.locale ?? defaultNS;
-
- const res = useAsync(async () => {
- return await initTranslations(locale);
- });
-
- if (!res.value) {
- return null;
- }
-
- return (
-
- {children}
-
- );
-}
diff --git a/apps/web/src/app/providers.tsx b/apps/web/src/app/providers.tsx
index cc3a96405..bbe6abe43 100644
--- a/apps/web/src/app/providers.tsx
+++ b/apps/web/src/app/providers.tsx
@@ -6,8 +6,8 @@ import { domMax, LazyMotion } from "framer-motion";
import { SessionProvider } from "next-auth/react";
import { useState } from "react";
-import { I18nProvider } from "@/app/i18n/client";
import { UserProvider } from "@/components/user-provider";
+import { I18nProvider } from "@/i18n/client";
import { AppRouter } from "@/trpc/routers";
import { ConnectedDayjsProvider } from "@/utils/dayjs";
import { trpcConfig } from "@/utils/trpc/config";
diff --git a/apps/web/src/components/event-card.tsx b/apps/web/src/components/event-card.tsx
index 785b9f13d..82a8366be 100644
--- a/apps/web/src/components/event-card.tsx
+++ b/apps/web/src/components/event-card.tsx
@@ -5,13 +5,13 @@ import { Icon } from "@rallly/ui/icon";
import dayjs from "dayjs";
import { DotIcon, MapPinIcon, PauseIcon } from "lucide-react";
-import { useTranslation } from "@/app/i18n/client";
import TruncatedLinkify from "@/components/poll/truncated-linkify";
import VoteIcon from "@/components/poll/vote-icon";
import { PollStatusBadge } from "@/components/poll-status";
import { RandomGradientBar } from "@/components/random-gradient-bar";
import { Trans } from "@/components/trans";
import { usePoll } from "@/contexts/poll";
+import { useTranslation } from "@/i18n/client";
function IconGuide() {
return (
diff --git a/apps/web/src/components/poll/desktop-poll.tsx b/apps/web/src/components/poll/desktop-poll.tsx
index 5839144ea..360a5fe4e 100644
--- a/apps/web/src/components/poll/desktop-poll.tsx
+++ b/apps/web/src/components/poll/desktop-poll.tsx
@@ -23,11 +23,11 @@ import {
EmptyStateIcon,
EmptyStateTitle,
} from "@/app/components/empty-state";
-import { useTranslation } from "@/app/i18n/client";
import { TimesShownIn } from "@/components/clock";
import { useVotingForm } from "@/components/poll/voting-form";
import { usePermissions } from "@/contexts/permissions";
import { usePoll } from "@/contexts/poll";
+import { useTranslation } from "@/i18n/client";
import {
useParticipants,
diff --git a/apps/web/src/components/poll/mobile-poll/poll-option.tsx b/apps/web/src/components/poll/mobile-poll/poll-option.tsx
index 2af591962..4ba1f37a1 100644
--- a/apps/web/src/components/poll/mobile-poll/poll-option.tsx
+++ b/apps/web/src/components/poll/mobile-poll/poll-option.tsx
@@ -7,11 +7,11 @@ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import * as React from "react";
import { useToggle } from "react-use";
-import { useTranslation } from "@/app/i18n/client";
import { OptimizedAvatarImage } from "@/components/optimized-avatar-image";
import { useParticipants } from "@/components/participants-provider";
import { usePoll } from "@/contexts/poll";
import { useRole } from "@/contexts/role";
+import { useTranslation } from "@/i18n/client";
import { ConnectedScoreSummary } from "../score-summary";
import VoteIcon from "../vote-icon";
diff --git a/apps/web/src/components/trans.tsx b/apps/web/src/components/trans.tsx
index b0e8886ad..25b5e87fa 100644
--- a/apps/web/src/components/trans.tsx
+++ b/apps/web/src/components/trans.tsx
@@ -1,6 +1,6 @@
import { Trans as BaseTrans } from "react-i18next";
-import { useTranslation } from "@/app/i18n/client";
+import { useTranslation } from "@/i18n/client";
import { I18nNamespaces } from "../../declarations/i18next";
@@ -11,6 +11,6 @@ export const Trans = (props: {
children?: React.ReactNode;
components?: Record | React.ReactElement[];
}) => {
- const { t } = useTranslation("app");
+ const { t } = useTranslation();
return ;
};
diff --git a/apps/web/src/components/user-provider.tsx b/apps/web/src/components/user-provider.tsx
index a9f24c75a..3f1ed62d3 100644
--- a/apps/web/src/components/user-provider.tsx
+++ b/apps/web/src/components/user-provider.tsx
@@ -3,11 +3,11 @@ import { Session } from "next-auth";
import { useSession } from "next-auth/react";
import React from "react";
-import { useTranslation } from "@/app/i18n/client";
import { Spinner } from "@/components/spinner";
import { useSubscription } from "@/contexts/plan";
import { PostHogProvider } from "@/contexts/posthog";
import { PreferencesProvider } from "@/contexts/preferences";
+import { useTranslation } from "@/i18n/client";
import { trpc } from "@/utils/trpc/client";
import { useRequiredContext } from "./use-required-context";
diff --git a/apps/web/src/i18n/client.tsx b/apps/web/src/i18n/client.tsx
new file mode 100644
index 000000000..5e3da29d8
--- /dev/null
+++ b/apps/web/src/i18n/client.tsx
@@ -0,0 +1,34 @@
+"use client";
+import { useParams } from "next/navigation";
+import React from "react";
+import {
+ I18nextProvider,
+ useTranslation as useTranslationOrg,
+} from "react-i18next";
+import useAsync from "react-use/lib/useAsync";
+
+import { initI18next } from "./i18n";
+import { defaultNS } from "./settings";
+
+export function useTranslation() {
+ return useTranslationOrg("app");
+}
+
+export function I18nProvider({ children }: { children: React.ReactNode }) {
+ const params = useParams<{ locale: string }>();
+ const locale = params?.locale ?? defaultNS;
+
+ const res = useAsync(async () => {
+ return await initI18next(locale, "app");
+ });
+
+ if (!res.value) {
+ return null;
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/web/src/app/i18n.ts b/apps/web/src/i18n/i18n.ts
similarity index 56%
rename from apps/web/src/app/i18n.ts
rename to apps/web/src/i18n/i18n.ts
index 68770b424..37b1681d6 100644
--- a/apps/web/src/app/i18n.ts
+++ b/apps/web/src/i18n/i18n.ts
@@ -1,13 +1,15 @@
import { createInstance, Namespace } from "i18next";
+import ICU from "i18next-icu";
import resourcesToBackend from "i18next-resources-to-backend";
import { initReactI18next } from "react-i18next/initReactI18next";
-import { defaultNS, getOptions } from "./i18n/settings";
+import { getOptions } from "./settings";
-const initI18next = async (lng: string, ns: Namespace) => {
+export const initI18next = async (lng: string, ns: Namespace) => {
const i18nInstance = createInstance();
await i18nInstance
.use(initReactI18next)
+ .use(ICU)
.use(
resourcesToBackend(
(language: string, namespace: string) =>
@@ -17,14 +19,3 @@ const initI18next = async (lng: string, ns: Namespace) => {
.init(getOptions(lng, ns));
return i18nInstance;
};
-
-export async function getTranslation(
- locale: string,
- ns: Namespace = defaultNS,
-) {
- const i18nextInstance = await initI18next(locale, ns);
- return {
- t: i18nextInstance.getFixedT(locale, Array.isArray(ns) ? ns[0] : ns),
- i18n: i18nextInstance,
- };
-}
diff --git a/apps/web/src/i18n/server.ts b/apps/web/src/i18n/server.ts
new file mode 100644
index 000000000..ca58e73fb
--- /dev/null
+++ b/apps/web/src/i18n/server.ts
@@ -0,0 +1,16 @@
+import type { Namespace } from "i18next";
+
+import { defaultNS } from "@/i18n/settings";
+
+import { initI18next } from "./i18n";
+
+export async function getTranslation(
+ locale: string,
+ ns: Namespace = defaultNS,
+) {
+ const i18nextInstance = await initI18next(locale, ns);
+ return {
+ t: i18nextInstance.getFixedT(locale, Array.isArray(ns) ? ns[0] : ns),
+ i18n: i18nextInstance,
+ };
+}
diff --git a/apps/web/src/app/i18n/settings.ts b/apps/web/src/i18n/settings.ts
similarity index 95%
rename from apps/web/src/app/i18n/settings.ts
rename to apps/web/src/i18n/settings.ts
index ec240cf21..ee224312a 100644
--- a/apps/web/src/app/i18n/settings.ts
+++ b/apps/web/src/i18n/settings.ts
@@ -10,7 +10,6 @@ export function getOptions(
ns: string | string[] = defaultNS,
): InitOptions {
return {
- // debug: true,
supportedLngs: languages,
fallbackLng,
lng,
diff --git a/apps/web/src/pages/_app.tsx b/apps/web/src/pages/_app.tsx
index b3d67d0ed..71ef713b9 100644
--- a/apps/web/src/pages/_app.tsx
+++ b/apps/web/src/pages/_app.tsx
@@ -11,9 +11,9 @@ import Head from "next/head";
import { SessionProvider, signIn, useSession } from "next-auth/react";
import React from "react";
-import { I18nProvider } from "@/app/i18n/client";
import Maintenance from "@/components/maintenance";
import { UserProvider } from "@/components/user-provider";
+import { I18nProvider } from "@/i18n/client";
import { ConnectedDayjsProvider } from "@/utils/dayjs";
import { trpc } from "@/utils/trpc/client";