refactor: move from next-international to next-intl (#1368)

* refactor: move from next-international to next-intl

* refactor: restructure translation package,

* chore: change i18n-allay framework to next-intl

* fix: add missing bold html tag to translation

* fix: format issue

* fix: address deepsource issues

* fix: remove international-types dependency

* fix: lint and typecheck issues

* fix: typecheck issue

* fix: typecheck issue

* fix: issue with translations
This commit is contained in:
Meier Lukas
2024-10-26 22:46:14 +02:00
committed by GitHub
parent db198c6dab
commit 4502569223
33 changed files with 331 additions and 160 deletions

View File

@@ -1,13 +0,0 @@
"use client";
import { createI18nClient } from "next-international/client";
import { languageMapping } from "./lang";
import enTranslation from "./lang/en";
export const { useI18n, useScopedI18n, useCurrentLocale, useChangeLocale, I18nProviderClient } = createI18nClient(
languageMapping(),
{
fallbackLocale: enTranslation,
},
);

View File

@@ -0,0 +1,19 @@
"use client";
import { useMessages, useTranslations } from "next-intl";
import type { TranslationObject } from "../type";
export { useChangeLocale } from "./use-change-locale";
export { useCurrentLocale } from "./use-current-locale";
export const { useI18n, useScopedI18n } = {
useI18n: useTranslations,
useScopedI18n: useTranslations,
};
export const { useI18nMessages } = {
useI18nMessages: () => useMessages() as TranslationObject,
};
export { useTranslations };

View File

@@ -0,0 +1,25 @@
import { useTransition } from "react";
import { usePathname, useRouter } from "next/navigation";
import type { SupportedLanguage } from "../config";
import { useCurrentLocale } from "./use-current-locale";
export const useChangeLocale = () => {
const currentLocale = useCurrentLocale();
const router = useRouter();
const pathname = usePathname();
const [isPending, startTransition] = useTransition();
return {
changeLocale: (newLocale: SupportedLanguage) => {
if (newLocale === currentLocale) {
return;
}
startTransition(() => {
router.replace(`/${newLocale}/${pathname}`);
});
},
isPending,
};
};

View File

@@ -0,0 +1,5 @@
import { useLocale } from "next-intl";
import type { SupportedLanguage } from "../config";
export const useCurrentLocale = () => useLocale() as SupportedLanguage;

View File

@@ -0,0 +1,26 @@
import { objectKeys } from "@homarr/common";
export const localeConfigurations = {
de: {
name: "Deutsch",
translatedName: "German",
flagIcon: "de",
},
en: {
name: "English",
translatedName: "English",
flagIcon: "us",
},
} satisfies Record<
string,
{
name: string;
translatedName: string;
flagIcon: string;
}
>;
export const supportedLanguages = objectKeys(localeConfigurations);
export type SupportedLanguage = (typeof supportedLanguages)[number];
export const defaultLocale = "en" satisfies SupportedLanguage;

View File

@@ -1,14 +1,11 @@
import type { SupportedLanguage } from "./config";
import { supportedLanguages } from "./config";
import type { stringOrTranslation, TranslationFunction } from "./type";
export * from "./type";
export * from "./locale-attributes";
export const supportedLanguages = ["en", "de"] as const;
export type SupportedLanguage = (typeof supportedLanguages)[number];
export const defaultLocale = "en";
export { languageMapping } from "./lang";
export type { TranslationKeys } from "./lang";
export * from "./config";
export { createLanguageMapping } from "./mapping";
export type { TranslationKeys } from "./mapping";
export const translateIfNecessary = (t: TranslationFunction, value: stringOrTranslation | undefined) => {
if (typeof value === "function") {
@@ -16,3 +13,7 @@ export const translateIfNecessary = (t: TranslationFunction, value: stringOrTran
}
return value;
};
export const isLocaleSupported = (locale: string): locale is SupportedLanguage => {
return supportedLanguages.includes(locale as SupportedLanguage);
};

View File

@@ -706,7 +706,7 @@ export default {
},
},
},
mantineReactTable: MRT_Localization_EN,
mantineReactTable: MRT_Localization_EN as Readonly<Record<keyof typeof MRT_Localization_EN, string>>,
},
section: {
dynamic: {
@@ -1205,11 +1205,11 @@ export default {
},
integration: {
noData: "No integration found",
description: "Click {here} to create a new integration",
description: "Click <here></here> to create a new integration",
},
app: {
noData: "No app found",
description: "Click {here} to create a new app",
description: "Click <here></here> to create a new app",
},
error: {
action: {
@@ -1842,7 +1842,7 @@ export default {
copy: {
title: "Copy invite",
description:
"Your invitation has been generated. After this modal closes, you'll not be able to copy this link anymore. If you do no longer wish to invite said person, you can delete this invitation any time.",
"Your invitation has been generated. After this modal closes, <b>you'll not be able to copy this link anymore.</b> If you do no longer wish to invite said person, you can delete this invitation any time.",
link: "Invitation link",
button: "Copy & close",
},

View File

@@ -1,21 +0,0 @@
import type { SupportedLanguage } from ".";
export const localeAttributes: Record<
SupportedLanguage,
{
name: string;
translatedName: string;
flagIcon: string;
}
> = {
de: {
name: "Deutsch",
translatedName: "German",
flagIcon: "de",
},
en: {
name: "English",
translatedName: "English",
flagIcon: "us",
},
};

View File

@@ -1,9 +1,9 @@
import { supportedLanguages } from ".";
import { supportedLanguages } from "./config";
const _enTranslations = () => import("./lang/en");
type EnTranslation = typeof _enTranslations;
export const languageMapping = () => {
export const createLanguageMapping = () => {
const mapping: Record<string, unknown> = {};
for (const language of supportedLanguages) {

View File

@@ -1,9 +1,10 @@
import { createI18nMiddleware } from "next-international/middleware";
import createMiddleware from "next-intl/middleware";
import { defaultLocale, supportedLanguages } from ".";
import { routing } from "./routing";
export const I18nMiddleware = createI18nMiddleware({
locales: supportedLanguages,
defaultLocale,
urlMappingStrategy: "rewrite",
});
export const I18nMiddleware = createMiddleware(routing);
export const config = {
// Match only internationalized pathnames
matcher: ["/", "/(de|en)/:path*"],
};

View File

@@ -0,0 +1,34 @@
import deepmerge from "deepmerge";
import { getRequestConfig } from "next-intl/server";
import { isLocaleSupported } from ".";
import type { SupportedLanguage } from "./config";
import { createLanguageMapping } from "./mapping";
import { routing } from "./routing";
// This file is referenced in the `next.config.js` file. See https://next-intl-docs.vercel.app/docs/usage/configuration
export default getRequestConfig(async ({ requestLocale }) => {
let currentLocale = await requestLocale;
if (!currentLocale || !isLocaleSupported(currentLocale)) {
currentLocale = routing.defaultLocale;
}
const typedLocale = currentLocale as SupportedLanguage;
const languageMap = createLanguageMapping();
const currentMessages = (await languageMap[typedLocale]()).default;
// Fallback to default locale if the current locales messages if not all messages are present
if (currentLocale !== routing.defaultLocale) {
const fallbackMessages = (await languageMap[routing.defaultLocale]()).default;
return {
locale: currentLocale,
messages: deepmerge(fallbackMessages, currentMessages),
};
}
return {
locale: currentLocale,
messages: currentMessages,
};
});

View File

@@ -0,0 +1,11 @@
import { defineRouting } from "next-intl/routing";
import { defaultLocale, supportedLanguages } from "./config";
export const routing = defineRouting({
locales: supportedLanguages,
defaultLocale,
localePrefix: {
mode: "never", // Rewrite the URL with locale parameter but without shown in url
},
});

View File

@@ -1,8 +1,8 @@
import { createI18nServer } from "next-international/server";
import { getTranslations } from "next-intl/server";
import { languageMapping } from "./lang";
import enTranslation from "./lang/en";
export const { getI18n, getScopedI18n } = {
getI18n: getTranslations,
getScopedI18n: getTranslations,
};
export const { getI18n, getScopedI18n, getStaticParams } = createI18nServer(languageMapping(), {
fallbackLocale: enTranslation,
});
export { getMessages as getI18nMessages } from "next-intl/server";

View File

@@ -1,9 +1,19 @@
import type { NamespaceKeys, NestedKeyOf } from "next-intl";
import type { RemoveReadonly } from "@homarr/common/types";
import type { useI18n, useScopedI18n } from "./client";
import type enTranslation from "./lang/en";
export type TranslationFunction = ReturnType<typeof useI18n>;
export type ScopedTranslationFunction<T extends Parameters<typeof useScopedI18n>[0]> = ReturnType<
typeof useScopedI18n<T>
>;
export type TranslationFunction = ReturnType<typeof useI18n<never>>;
export type ScopedTranslationFunction<
NestedKey extends NamespaceKeys<IntlMessages, NestedKeyOf<IntlMessages>> = never,
> = ReturnType<typeof useScopedI18n<NestedKey>>;
export type TranslationObject = typeof enTranslation;
export type stringOrTranslation = string | ((t: TranslationFunction) => string);
declare global {
// Use type safe message keys with `next-intl`
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface IntlMessages extends RemoveReadonly<TranslationObject> {}
}