feat: localization (#196)

* Started adding locale translations :)

* Added some more translations

* Working on translating even more pages

* More translations

* Added test default locale retrieval

* replace `intl.formatMessage` with custom `t` hook

* add more translations

* improve title syntax

* add more translations

* translate admin config page

* translated error messages

* add language selecter

* minor fixes

* improve language handling

* add upcoming languages

* add `crowdin.yml`

* run formatter

---------

Co-authored-by: Steve Tautonico <stautonico@gmail.com>
This commit is contained in:
Elias Schneider
2023-07-20 15:32:07 +02:00
committed by GitHub
parent 7c5ec8d0ea
commit b9f6e3bd08
68 changed files with 4712 additions and 461 deletions

View File

@@ -1,7 +1,10 @@
import moment from "moment";
export const getExpirationPreview = (
name: string,
messages: {
neverExpires: string;
expiresOn: string;
},
form: {
values: {
never_expires?: boolean;
@@ -13,7 +16,7 @@ export const getExpirationPreview = (
const value = form.values.never_expires
? "never"
: form.values.expiration_num + form.values.expiration_unit;
if (value === "never") return `This ${name} will never expire.`;
if (value === "never") return messages.neverExpires;
const expirationDate = moment()
.add(
@@ -22,5 +25,8 @@ export const getExpirationPreview = (
)
.toDate();
return `This ${name} will expire on ${moment(expirationDate).format("LLL")}`;
return messages.expiresOn.replace(
"{expiration}",
moment(expirationDate).format("LLL")
);
};

View File

@@ -0,0 +1,42 @@
import { setCookie } from "cookies-next";
import { LOCALES } from "../i18n/locales";
const getLocaleByCode = (code: string) => {
return Object.values(LOCALES).find((l) => l.code === code) ?? LOCALES.ENGLISH;
};
// Parse the Accept-Language header and return the first supported language
const getLanguageFromAcceptHeader = (acceptLanguage?: string) => {
if (!acceptLanguage) return "en";
const languages = acceptLanguage.split(",").map((l) => l.split(";")[0]);
const supportedLanguages = Object.values(LOCALES).map((l) => l.code);
for (const language of languages) {
// Try to match the full language code first, then the language code without the region
if (supportedLanguages.includes(language)) {
return language;
} else if (supportedLanguages.includes(language.split("-")[0])) {
return language.split("-")[0];
}
}
return "en";
};
const isLanguageSupported = (code: string) => {
return Object.values(LOCALES).some((l) => l.code === code);
};
const setLanguageCookie = (code: string) => {
setCookie("language", code, {
sameSite: "lax",
expires: new Date(new Date().setFullYear(new Date().getFullYear() + 1)),
});
};
export default {
getLocaleByCode,
getLanguageFromAcceptHeader,
isLanguageSupported,
setLanguageCookie,
};

View File

@@ -1,6 +1,5 @@
export const configVariableToFriendlyName = (variable: string) => {
const splitted = variable.split(/(?=[A-Z])/).join(" ");
return splitted.charAt(0).toUpperCase() + splitted.slice(1);
export const camelToKebab = (camelCaseString: string) => {
return camelCaseString.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
};
export const capitalizeFirstLetter = (string: string) => {

View File

@@ -0,0 +1,35 @@
const defaultPreferences = [
{
key: "colorScheme",
value: "system",
},
{
key: "locale",
value: "system",
},
];
const get = (key: string) => {
if (typeof window !== "undefined") {
const preferences = JSON.parse(localStorage.getItem("preferences") ?? "{}");
return (
preferences[key] ??
defaultPreferences.find((p) => p.key == key)?.value ??
null
);
}
};
const set = (key: string, value: string) => {
if (typeof window !== "undefined") {
const preferences = JSON.parse(localStorage.getItem("preferences") ?? "{}");
preferences[key] = value;
localStorage.setItem("preferences", JSON.stringify(preferences));
}
};
const userPreferences = {
get,
set,
};
export default userPreferences;