Files
formbricks/apps/web/lib/i18n/utils.ts
2026-03-11 10:14:37 +01:00

233 lines
6.0 KiB
TypeScript

import { iso639Languages } from "@formbricks/i18n-utils/src/utils";
import { TI18nString } from "@formbricks/types/i18n";
import { TLanguage } from "@formbricks/types/project";
import { TSurveyLanguage } from "@formbricks/types/surveys/types";
import { structuredClone } from "@/lib/pollyfills/structuredClone";
// Helper function to create an i18nString from a regular string.
export const createI18nString = (
text: string | TI18nString,
languages: string[],
targetLanguageCode?: string
): TI18nString => {
if (typeof text === "object") {
// It's already an i18n object, so clone it
const i18nString: TI18nString = structuredClone(text);
// Add new language keys with empty strings if they don't exist
languages?.forEach((language) => {
if (!(language in i18nString)) {
i18nString[language] = "";
}
});
// Remove language keys that are not in the languages array
Object.keys(i18nString).forEach((key) => {
if (key !== (targetLanguageCode ?? "default") && languages && !languages.includes(key)) {
delete i18nString[key];
}
});
return i18nString;
} else {
// It's a regular string, so create a new i18n object
const i18nString = {
[targetLanguageCode ?? "default"]: text,
};
// Initialize all provided languages with empty strings
languages?.forEach((language) => {
if (language !== (targetLanguageCode ?? "default")) {
i18nString[language] = "";
}
});
return i18nString;
}
};
// Type guard to check if an object is an I18nString
export const isI18nObject = (obj: unknown): obj is TI18nString => {
return typeof obj === "object" && obj !== null && Object.keys(obj).includes("default");
};
export const isLabelValidForAllLanguages = (label: TI18nString, languages: string[]): boolean => {
return languages.every((language) => label[language] && label[language].trim() !== "");
};
export const getLocalizedValue = (value: TI18nString | undefined, languageId: string): string => {
if (!value) {
return "";
}
if (isI18nObject(value)) {
if (value[languageId]) {
return value[languageId];
}
return "";
}
return "";
};
export const extractLanguageCodes = (surveyLanguages: TSurveyLanguage[]): string[] => {
if (!surveyLanguages) return [];
return surveyLanguages.map((surveyLanguage) =>
surveyLanguage.default ? "default" : surveyLanguage.language.code
);
};
export const getEnabledLanguages = (surveyLanguages: TSurveyLanguage[]) => {
return surveyLanguages.filter((surveyLanguage) => surveyLanguage.enabled);
};
export const extractLanguageIds = (languages: TLanguage[]): string[] => {
return languages.map((language) => language.code);
};
export const getLanguageCode = (surveyLanguages: TSurveyLanguage[], languageCode: string | null) => {
if (!surveyLanguages?.length || !languageCode) return "default";
const language = surveyLanguages.find((surveyLanguage) => surveyLanguage.language.code === languageCode);
return language?.default ? "default" : language?.language.code || "default";
};
export const iso639Identifiers = iso639Languages.map((language) => language.code);
// Helper function to add language keys to a multi-language object (e.g. survey or question)
// Iterates over the object recursively and adds empty strings for new language keys
export const addMultiLanguageLabels = (object: unknown, languageSymbols: string[]): any => {
// Helper function to add language keys to a multi-language object
function addLanguageKeys(obj: { default: string; [key: string]: string }) {
languageSymbols.forEach((lang) => {
if (!obj.hasOwnProperty(lang)) {
obj[lang] = ""; // Add empty string for new language keys
}
});
}
// Recursive function to process an object or array
function processObject(obj: unknown) {
if (Array.isArray(obj)) {
obj.forEach((item) => processObject(item));
} else if (obj && typeof obj === "object") {
const record = obj as Record<string, unknown>;
for (const key in record) {
if (record.hasOwnProperty(key)) {
if (key === "default" && typeof record[key] === "string") {
addLanguageKeys(record as unknown as { default: string; [key: string]: string });
} else {
processObject(record[key]);
}
}
}
}
}
// Start processing the question object
processObject(object);
return object;
};
export const appLanguages = [
{
code: "de-DE",
label: {
"en-US": "German",
native: "Deutsch",
},
},
{
code: "en-US",
label: {
"en-US": "English (US)",
native: "English (US)",
},
},
{
code: "es-ES",
label: {
"en-US": "Spanish",
native: "Español",
},
},
{
code: "fr-FR",
label: {
"en-US": "French",
native: "Français",
},
},
{
code: "hu-HU",
label: {
"en-US": "Hungarian",
native: "Magyar",
},
},
{
code: "ja-JP",
label: {
"en-US": "Japanese",
native: "日本語",
},
},
{
code: "nl-NL",
label: {
"en-US": "Dutch",
native: "Nederlands",
},
},
{
code: "pt-BR",
label: {
"en-US": "Portuguese (Brazil)",
native: "Português (Brasil)",
},
},
{
code: "pt-PT",
label: {
"en-US": "Portuguese (Portugal)",
native: "Português (Portugal)",
},
},
{
code: "ro-RO",
label: {
"en-US": "Romanian",
native: "Română",
},
},
{
code: "ru-RU",
label: {
"en-US": "Russian",
native: "Русский",
},
},
{
code: "sv-SE",
label: {
"en-US": "Swedish",
native: "Svenska",
},
},
{
code: "zh-Hans-CN",
label: {
"en-US": "Chinese (Simplified)",
native: "简体中文",
},
},
{
code: "zh-Hant-TW",
label: {
"en-US": "Chinese (Traditional)",
native: "繁體中文",
},
},
];
export const sortedAppLanguages = [...appLanguages].sort((a, b) =>
a.label["en-US"].localeCompare(b.label["en-US"])
);