⬆️ Upgrade i18next (#1592)

This commit is contained in:
Luke Vella 2025-03-01 15:56:56 +00:00 committed by GitHub
parent 8c84a92a58
commit bff2dd3a20
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 56 additions and 65 deletions

View file

@ -1,21 +1,19 @@
import "react-i18next";
import "i18next";
import type blog from "../public/locales/en/blog.json";
import type common from "../public/locales/en/common.json";
import type home from "../public/locales/en/home.json";
import type pricing from "../public/locales/en/pricing.json";
interface I18nNamespaces {
common: typeof common;
home: typeof home;
pricing: typeof pricing;
blog: typeof blog;
}
declare module "i18next" {
interface CustomTypeOptions {
defaultNS: "common";
resources: I18nNamespaces;
resources: {
common: typeof common;
home: typeof home;
pricing: typeof pricing;
blog: typeof blog;
};
returnNull: false;
}
}

View file

@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

View file

@ -23,7 +23,7 @@
"accept-language-parser": "^1.5.0",
"dayjs": "^1.11.10",
"gray-matter": "^4.0.3",
"i18next": "^22.4.9",
"i18next": "^24.2.2",
"i18next-icu": "^2.3.0",
"intl-messageformat": "^10.3.4",
"lodash": "^4.17.21",
@ -31,7 +31,7 @@
"next-i18next": "^13.0.3",
"next-mdx-remote": "^5.0.0",
"next-seo": "^6.1.0",
"react-i18next": "^12.1.4",
"react-i18next": "^15.4.1",
"react-use": "^17.4.0"
},
"devDependencies": {

View file

@ -6,7 +6,7 @@ import { BigTestimonial, Marketing, MentionedBy } from "@/components/marketing";
import { getTranslation } from "@/i18n/server";
export default async function Page({ params }: { params: { locale: string } }) {
const { t } = await getTranslation(params.locale, ["common", "home"]);
const { t } = await getTranslation(params.locale, "home");
return (
<Marketing>
<MarketingHero

View file

@ -4,17 +4,21 @@ import { BigTestimonial, Marketing, MentionedBy } from "@/components/marketing";
import { getTranslation } from "@/i18n/server";
export default async function Page({ params }: { params: { locale: string } }) {
const { t } = await getTranslation(params.locale, ["common", "home"]);
const { t } = await getTranslation(params.locale, ["home", "common"]);
return (
<Marketing>
<MarketingHero
title={t("home:headline", {
title={t("headline", {
defaultValue: "Ditch the back-and-forth emails",
ns: "home",
})}
description={t("home:subheading", {
description={t("subheading", {
defaultValue: "Streamline your scheduling process and save time",
ns: "home",
})}
callToAction={t("getStarted", {
ns: "common",
})}
callToAction={t("getStarted")}
/>
<Bonus t={t} />
<BigTestimonial />
@ -30,10 +34,11 @@ export async function generateMetadata({
}) {
const { t } = await getTranslation(params.locale, "home");
return {
title: t("home:metaTitle", {
title: t("metaTitle", {
defaultValue: "Rallly: Group Scheduling Tool",
ns: "home",
}),
description: t("home:metaDescription", {
description: t("metaDescription", {
defaultValue:
"Create polls and vote to find the best day or time. A free alternative to Doodle.",
}),

View file

@ -8,7 +8,7 @@ import { linkToApp } from "@/lib/linkToApp";
import { PriceTables } from "./pricing-table";
const FAQ = async ({ t }: { t: TFunction }) => {
const FAQ = async ({ t }: { t: TFunction<"pricing"> }) => {
return (
<section>
<h2 className="text-2xl font-bold">
@ -123,7 +123,7 @@ const FAQ = async ({ t }: { t: TFunction }) => {
};
export default async function Page({ params }: { params: { locale: string } }) {
const { t } = await getTranslation(params.locale, ["common", "pricing"]);
const { t } = await getTranslation(params.locale, "pricing");
return (
<article className="mx-auto max-w-3xl space-y-6">
<header className="space-y-2 sm:p-6 sm:text-center">

View file

@ -10,7 +10,7 @@ import { Trans } from "react-i18next/TransWithoutContext";
import { BonusItem } from "@/components/home/bonus-item";
export async function Bonus({ t }: { t: TFunction }) {
export async function Bonus({ t }: { t: TFunction<"home" | "common"> }) {
const userCount = await prisma.user.count();
return (
<div className="mx-auto flex flex-wrap justify-center gap-2 whitespace-nowrap text-center sm:grid-cols-4 sm:gap-4 sm:gap-x-8">

View file

@ -21,13 +21,14 @@ const initI18next = async (lng: string, ns: Namespace) => {
return i18nInstance;
};
export async function getTranslation(
export async function getTranslation<Ns extends Namespace>(
locale: string,
ns: Namespace = defaultNS,
ns?: Ns,
) {
const i18nextInstance = await initI18next(locale, ns);
const fixedNs = ns ?? defaultNS;
const i18nextInstance = await initI18next(locale, fixedNs);
return {
t: i18nextInstance.getFixedT(locale, Array.isArray(ns) ? ns[0] : ns),
t: i18nextInstance.getFixedT<Ns>(locale),
i18n: i18nextInstance,
};
}

View file

@ -1,5 +1,5 @@
import allLanguages from "@rallly/languages";
import type { InitOptions } from "i18next";
import type { InitOptions, Namespace } from "i18next";
export const fallbackLng = "en";
export const languages = Object.keys(allLanguages);
@ -7,7 +7,7 @@ export const defaultNS = "common";
export function getOptions(
lng = fallbackLng,
ns: string | string[] = defaultNS,
ns: Namespace = defaultNS,
): InitOptions {
return {
// debug: true,

View file

@ -54,7 +54,7 @@
"cookie": "^0.7.0",
"crypto": "^1.0.1",
"dayjs": "^1.11.10",
"i18next": "^22.4.9",
"i18next": "^24.2.2",
"i18next-icu": "^2.3.0",
"i18next-resources-to-backend": "^1.1.4",
"ics": "^3.1.0",
@ -75,7 +75,7 @@
"react-big-calendar": "^1.8.1",
"react-hook-form": "^7.42.1",
"react-hook-form-persist": "^3.0.0",
"react-i18next": "^12.1.4",
"react-i18next": "^15.4.1",
"react-remove-scroll": "^2.5.6",
"react-use": "^17.4.0",
"smoothscroll-polyfill": "^0.4.4",

View file

@ -6,41 +6,25 @@ import { checkApiAuthorization } from "@/utils/api-auth";
/**
* Remove polls and corresponding data that have been marked deleted for more than 7 days.
*/
export async function POST(req: Request) {
export async function POST() {
const unauthorized = checkApiAuthorization();
if (unauthorized) return unauthorized;
const options = (await req.json()) as { take?: number } | undefined;
// First get the ids of all the polls that have been marked as deleted for at least 7 days
const deletedPolls = await prisma.poll.findMany({
select: {
id: true,
},
const deletedPolls = await prisma.poll.deleteMany({
where: {
deleted: true,
deletedAt: {
lt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
},
},
take: options?.take ?? 1000,
});
const deletedPollIds = deletedPolls.map((poll) => poll.id);
const { count: deletedPollCount } = await prisma.poll.deleteMany({
where: {
id: {
in: deletedPollIds,
},
},
});
return NextResponse.json({
success: true,
summary: {
deleted: {
polls: deletedPollCount,
polls: deletedPolls.count,
},
},
});

View file

@ -1,5 +1,5 @@
import allLanguages from "@rallly/languages";
import type { InitOptions } from "i18next";
import type { InitOptions, Namespace } from "i18next";
export const fallbackLng = "en";
export const languages = Object.keys(allLanguages);
@ -7,7 +7,7 @@ export const defaultNS = "app";
export function getOptions(
lng = fallbackLng,
ns: string | string[] = defaultNS,
ns: Namespace = defaultNS,
): InitOptions {
return {
supportedLngs: languages,

View file

@ -20,9 +20,6 @@ const i18nDefaultConfig: InitOptions = {
ns: ["emails"],
fallbackNS: "emails",
defaultNS: "emails",
interpolation: {
escapeValue: false,
},
} as const;
export type I18nInstance = typeof i18nInstance;

View file

@ -9,7 +9,7 @@ export type EmailContext = {
domain: string;
supportEmail: string;
i18n: I18nInstance;
t: TFunction<"emails", undefined, "emails">;
t: TFunction;
};
export type TemplateName = keyof EmailTemplates;

View file

@ -2271,6 +2271,13 @@
dependencies:
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.23.2", "@babel/runtime@^7.25.0":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.9.tgz#aa4c6facc65b9cb3f87d75125ffd47781b475433"
integrity sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.23.5":
version "7.23.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
@ -10011,12 +10018,12 @@ i18next@*:
dependencies:
"@babel/runtime" "^7.20.6"
i18next@^22.4.9:
version "22.4.10"
resolved "https://registry.npmjs.org/i18next/-/i18next-22.4.10.tgz"
integrity sha512-3EqgGK6fAJRjnGgfkNSStl4mYLCjUoJID338yVyLMj5APT67HUtWoqSayZewiiC5elzMUB1VEUwcmSCoeQcNEA==
i18next@^24.2.2:
version "24.2.2"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-24.2.2.tgz#3ba3d213302068d569142737f03f30929de696de"
integrity sha512-NE6i86lBCKRYZa5TaUDkU5S4HFgLIEJRLr3Whf2psgaxBleQ2LC1YW1Vc+SCgkAW7VEzndT6al6+CzegSUHcTQ==
dependencies:
"@babel/runtime" "^7.20.6"
"@babel/runtime" "^7.23.2"
iconv-lite@0.4.24:
version "0.4.24"
@ -12578,12 +12585,12 @@ react-hook-form@^7.42.1:
resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.43.2.tgz"
integrity sha512-NvD3Oe2Y9hhqo2R4I4iJigDzSLpdMnzUpNMxlnzTbdiT7NT3BW0GxWCzEtwPudZMUPbZhNcSy1EcGAygyhDORg==
react-i18next@^12.1.4:
version "12.2.0"
resolved "https://registry.npmjs.org/react-i18next/-/react-i18next-12.2.0.tgz"
integrity sha512-5XeVgSygaGfyFmDd2WcXvINRw2WEC1XviW1LXY/xLOEMzsCFRwKqfnHN+hUjla8ZipbVJR27GCMSuTr0BhBBBQ==
react-i18next@^15.4.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.4.1.tgz#33f3e89c2f6c68e2bfcbf9aa59986ad42fe78758"
integrity sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==
dependencies:
"@babel/runtime" "^7.20.6"
"@babel/runtime" "^7.25.0"
html-parse-stringify "^3.0.1"
react-is@^16.13.1, react-is@^16.7.0: