chore: removed i18n-utils dependency from surveys package (#7223)

Co-authored-by: TheodorTomas <theodortomas@gmail.com>
This commit is contained in:
Dhruwang Jariwala
2026-02-13 13:38:18 +05:30
committed by GitHub
parent 091b78d1e3
commit 36f091bc73
9 changed files with 305 additions and 11 deletions

View File

@@ -38,7 +38,11 @@ const getBadgeConfig = (
}
};
export const EnterpriseLicenseStatus = ({ status, gracePeriodEnd, environmentId }: EnterpriseLicenseStatusProps) => {
export const EnterpriseLicenseStatus = ({
status,
gracePeriodEnd,
environmentId,
}: EnterpriseLicenseStatusProps) => {
const { t } = useTranslation();
const router = useRouter();
const [isRechecking, setIsRechecking] = useState(false);

View File

@@ -956,13 +956,13 @@
"enterprise_features": "Enterprise Features",
"get_an_enterprise_license_to_get_access_to_all_features": "Get an Enterprise license to get access to all features.",
"keep_full_control_over_your_data_privacy_and_security": "Keep full control over your data privacy and security.",
"license_invalid_description": "The license key in your ENTERPRISE_LICENSE_KEY environment variable is not valid. Please check for typos or request a new key.",
"license_status": "License Status",
"license_status_active": "Active",
"license_status_description": "Status of your enterprise license.",
"license_status_expired": "Expired",
"license_status_invalid": "Invalid License",
"license_status_unreachable": "Unreachable",
"license_invalid_description": "The license key in your ENTERPRISE_LICENSE_KEY environment variable is not valid. Please check for typos or request a new key.",
"license_unreachable_grace_period": "License server cannot be reached. Your enterprise features remain active during a 3-day grace period ending {gracePeriodEnd}.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "No call needed, no strings attached: Request a free 30-day trial license to test all features by filling out this form:",
"no_credit_card_no_sales_call_just_test_it": "No credit card. No sales call. Just test it :)",

View File

@@ -956,13 +956,13 @@
"enterprise_features": "Vállalati funkciók",
"get_an_enterprise_license_to_get_access_to_all_features": "Vállalati licenc megszerzése az összes funkcióhoz való hozzáféréshez.",
"keep_full_control_over_your_data_privacy_and_security": "Az adatvédelem és biztonság fölötti rendelkezés teljes kézben tartása.",
"license_invalid_description": "Az ENTERPRISE_LICENSE_KEY környezeti változóban lévő licenckulcs nem érvényes. Ellenőrizze, hogy nem gépelte-e el, vagy kérjen új kulcsot.",
"license_status": "Licencállapot",
"license_status_active": "Aktív",
"license_status_description": "A vállalati licenc állapota.",
"license_status_expired": "Lejárt",
"license_status_invalid": "Érvénytelen licenc",
"license_status_unreachable": "Nem érhető el",
"license_invalid_description": "Az ENTERPRISE_LICENSE_KEY környezeti változóban lévő licenckulcs nem érvényes. Ellenőrizze, hogy nem gépelte-e el, vagy kérjen új kulcsot.",
"license_unreachable_grace_period": "A licenckiszolgálót nem lehet elérni. A vállalati funkciók egy 3 napos türelmi időszak alatt aktívak maradnak, egészen eddig: {gracePeriodEnd}.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Nincs szükség telefonálásra, nincs feltételekhez kötöttség: kérjen 30 napos ingyenes próbalicencet az összes funkció kipróbálásához az alábbi űrlap kitöltésével:",
"no_credit_card_no_sales_call_just_test_it": "Nem kell hitelkártya. Nincsenek értékesítési hívások. Egyszerűen csak próbálja ki :)",

View File

@@ -30,4 +30,9 @@ export const ZEnterpriseLicenseDetails = z.object({
export type TEnterpriseLicenseDetails = z.infer<typeof ZEnterpriseLicenseDetails>;
export type TEnterpriseLicenseStatusReturn = "active" | "expired" | "unreachable" | "invalid_license" | "no-license";
export type TEnterpriseLicenseStatusReturn =
| "active"
| "expired"
| "unreachable"
| "invalid_license"
| "no-license";

View File

@@ -21,7 +21,7 @@ export function LanguageSelect({ language, onLanguageChange, disabled, locale }:
const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const [selectedOption, setSelectedOption] = useState(
const [selectedOption, setSelectedOption] = useState<TIso639Language | undefined>(
iso639Languages.find((isoLang) => isoLang.code === language.code)
);
const items = iso639Languages;
@@ -72,7 +72,7 @@ export function LanguageSelect({ language, onLanguageChange, disabled, locale }:
<ChevronDown className="h-4 w-4 shrink-0" />
</Button>
<div
className={`ring-opacity-5 absolute right-0 z-30 mt-2 space-y-1 rounded-md bg-white p-1 shadow-lg ring-1 ring-black ${isOpen ? "" : "hidden"}`}>
className={`absolute right-0 z-30 mt-2 space-y-1 rounded-md bg-white p-1 shadow-lg ring-1 ring-black ring-opacity-5 ${isOpen ? "" : "hidden"}`}>
<Input
autoComplete="off"
onChange={(e) => {

View File

@@ -18,7 +18,7 @@ export interface TIso639Language {
};
}
export const iso639Languages: TIso639Language[] = [
export const iso639Languages = [
{
code: "aa",
label: {
@@ -4072,7 +4072,14 @@ export const iso639Languages: TIso639Language[] = [
"hu-HU": "Zulu",
},
},
];
] as const satisfies readonly TIso639Language[];
/**
* Union of every ISO 639 language code Formbricks supports.
* Derived automatically from the `iso639Languages` array — stays in sync
* without any manual maintenance.
*/
export type Iso639Code = (typeof iso639Languages)[number]["code"];
export const getLanguageLabel = (languageCode: string, locale: string): string | undefined => {
const language = iso639Languages.find((lang) => lang.code === languageCode);

View File

@@ -1,12 +1,12 @@
import { useRef, useState } from "preact/hooks";
import { useTranslation } from "react-i18next";
import { getLanguageLabel } from "@formbricks/i18n-utils/src";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { type TSurveyLanguage } from "@formbricks/types/surveys/types";
import { LanguageIcon } from "@/components/icons/language-icon";
import { mixColor } from "@/lib/color";
import { getI18nLanguage } from "@/lib/i18n-utils";
import i18n from "@/lib/i18n.config";
import { getLanguageDisplayName } from "@/lib/language-display-name";
import { useClickOutside } from "@/lib/use-click-outside-hook";
import { cn, isRTLLanguage } from "@/lib/utils";
@@ -80,7 +80,7 @@ export function LanguageSwitch({
title={t("common.language_switch")}
type="button"
className={cn(
"text-heading relative flex h-8 w-8 items-center justify-center rounded-md focus:ring-2 focus:ring-offset-2 focus:outline-hidden"
"text-heading focus:outline-hidden relative flex h-8 w-8 items-center justify-center rounded-md focus:ring-2 focus:ring-offset-2"
)}
style={{
backgroundColor: isHovered ? hoverColorWithOpacity : "transparent",
@@ -113,7 +113,7 @@ export function LanguageSwitch({
onClick={() => {
changeLanguage(surveyLanguage.language.code);
}}>
{getLanguageLabel(surveyLanguage.language.code, "en-US")}
{getLanguageDisplayName(surveyLanguage.language.code)}
</button>
);
})}

View File

@@ -0,0 +1,40 @@
import { describe, expect, test } from "vitest";
import { getLanguageDisplayName } from "./language-display-name";
describe("getLanguageDisplayName", () => {
test("returns native name for common language codes", () => {
expect(getLanguageDisplayName("de")).toBe("Deutsch");
expect(getLanguageDisplayName("fr")).toBe("français");
expect(getLanguageDisplayName("es")).toBe("español");
expect(getLanguageDisplayName("ja")).toBe("日本語");
expect(getLanguageDisplayName("ko")).toBe("한국어");
expect(getLanguageDisplayName("ar")).toBe("العربية");
expect(getLanguageDisplayName("en")).toBe("English");
});
test("returns native name for regional variants", () => {
expect(getLanguageDisplayName("pt-BR")).toBe("português (Brasil)");
expect(getLanguageDisplayName("de-AT")).toBe("Österreichisches Deutsch");
expect(getLanguageDisplayName("fr-CA")).toBe("français canadien");
expect(getLanguageDisplayName("zh-Hans")).toBe("简体中文");
expect(getLanguageDisplayName("zh-Hant")).toBe("繁體中文");
});
test("returns native name for less common codes", () => {
expect(getLanguageDisplayName("aa")).toBe("Afar");
expect(getLanguageDisplayName("is")).toBe("íslenska");
expect(getLanguageDisplayName("cy")).toBe("Cymraeg");
expect(getLanguageDisplayName("eu")).toBe("euskara");
expect(getLanguageDisplayName("vo")).toBe("Volapük");
expect(getLanguageDisplayName("bo")).toBe("བོད་སྐད་");
});
test("returns raw code for unknown codes", () => {
expect(getLanguageDisplayName("xx")).toBe("xx");
expect(getLanguageDisplayName("unknown")).toBe("unknown");
});
test("returns empty string for empty input", () => {
expect(getLanguageDisplayName("")).toBe("");
});
});

View File

@@ -0,0 +1,238 @@
import type { Iso639Code } from "@formbricks/i18n-utils";
/**
* Native-script display names for every ISO 639 code Formbricks supports.
*
* Each language is shown in its own script (e.g. "Deutsch", "français", "日本語")
* so users can recognise their language regardless of the current UI locale.
*
* The `Iso639Code` key type is derived from the `iso639Languages` array in
* @formbricks/i18n-utils via `as const satisfies`, so adding or removing a
* language there will cause a compile-time error here until this map is updated.
*
* ~5 KB — still ~71 KB smaller than the old 14-locale static list from
* @formbricks/i18n-utils that was previously bundled.
*/
const NATIVE_NAMES: Record<Iso639Code, string> = {
aa: "Afar",
ab: "Abkhazian",
ae: "Avestan",
af: "Afrikaans",
ak: "Akan",
am: "አማርኛ",
an: "Aragonese",
ar: "العربية",
"ar-SA": "العربية (المملكة العربية السعودية)",
"ar-EG": "العربية (مصر)",
"ar-AE": "العربية (الإمارات العربية المتحدة)",
"ar-MA": "العربية (المغرب)",
as: "অসমীয়া",
av: "Avaric",
ay: "Aymara",
az: "azərbaycan",
ba: "Bashkir",
be: "беларуская",
bg: "български",
bh: "Bhojpuri",
bi: "Bislama",
bm: "bamanakan",
bn: "বাংলা",
bo: "བོད་སྐད་",
br: "brezhoneg",
bs: "bosanski",
ca: "català",
ce: "нохчийн",
ch: "Chamorro",
co: "Corsican",
cr: "Cree",
cs: "čeština",
cu: "Church Slavic",
cv: "чӑваш",
cy: "Cymraeg",
da: "dansk",
de: "Deutsch",
"de-DE": "Deutsch (Deutschland)",
"de-AT": "Österreichisches Deutsch",
"de-CH": "Schweizer Hochdeutsch",
dv: "Divehi",
dz: "རྫོང་ཁ",
ee: "eʋegbe",
el: "Ελληνικά",
en: "English",
"en-US": "American English",
"en-GB": "British English",
"en-AU": "Australian English",
"en-CA": "Canadian English",
"en-IE": "English (Ireland)",
eo: "Esperanto",
es: "español",
"es-ES": "español de España",
"es-MX": "español de México",
"es-AR": "español (Argentina)",
"es-CO": "español (Colombia)",
"es-CL": "español (Chile)",
"es-PE": "español (Perú)",
"es-VE": "español (Venezuela)",
et: "eesti",
eu: "euskara",
fa: "فارسی",
ff: "Pulaar",
fi: "suomi",
fj: "Fijian",
fo: "føroyskt",
fr: "français",
"fr-FR": "français (France)",
"fr-CA": "français canadien",
"fr-BE": "français (Belgique)",
"fr-CH": "français suisse",
fy: "Frysk",
ga: "Gaeilge",
gd: "Gàidhlig",
gl: "galego",
gn: "Guarani",
gu: "ગુજરાતી",
gv: "Gaelg",
ha: "Hausa",
he: "עברית",
hi: "हिन्दी",
ho: "Hiri Motu",
hr: "hrvatski",
ht: "Haitian Creole",
hu: "magyar",
hy: "հայերեն",
hz: "Herero",
ia: "interlingua",
id: "Indonesia",
ie: "Interlingue",
ig: "Igbo",
ii: "ꆈꌠꉙ",
ik: "Inupiaq",
io: "Ido",
is: "íslenska",
it: "italiano",
iu: "Inuktitut",
ja: "日本語",
jv: "Jawa",
ka: "ქართული",
kg: "Kongo",
ki: "Gikuyu",
kj: "Kuanyama",
kk: "қазақ тілі",
kl: "kalaallisut",
km: "ខ្មែរ",
kn: "ಕನ್ನಡ",
ko: "한국어",
kr: "Kanuri",
ks: "کٲشُر",
ku: "kurdî (kurmancî)",
kv: "Komi",
kw: "kernewek",
ky: "кыргызча",
la: "Latin",
lb: "Lëtzebuergesch",
lg: "Luganda",
li: "Limburgish",
ln: "lingála",
lo: "ລາວ",
lt: "lietuvių",
lu: "Tshiluba",
lv: "latviešu",
mg: "Malagasy",
mh: "Marshallese",
mi: "Māori",
mk: "македонски",
ml: "മലയാളം",
mn: "монгол",
mr: "मराठी",
ms: "Melayu",
mt: "Malti",
my: "မြန်မာ",
na: "Nauru",
nb: "norsk bokmål",
nd: "isiNdebele",
ne: "नेपाली",
ng: "Ndonga",
nl: "Nederlands",
nn: "norsk nynorsk",
no: "norsk",
nr: "South Ndebele",
nv: "Navajo",
ny: "Nyanja",
oc: "occitan",
oj: "Ojibwa",
om: "Oromoo",
or: "ଓଡ଼ିଆ",
os: "ирон",
pa: "ਪੰਜਾਬੀ",
pi: "Pali",
pl: "polski",
ps: "پښتو",
pt: "português",
"pt-BR": "português (Brasil)",
"pt-PT": "português europeu",
qu: "Runasimi",
rm: "rumantsch",
rn: "Ikirundi",
ro: "română",
ru: "русский",
rw: "Ikinyarwanda",
sa: "संस्कृत भाषा",
sc: "sardu",
sd: "سنڌي",
se: "davvisámegiella",
sg: "Sängö",
si: "සිංහල",
sk: "slovenčina",
sl: "slovenščina",
sm: "Samoan",
sn: "chiShona",
so: "Soomaali",
sq: "shqip",
sr: "српски",
ss: "Swati",
st: "Sesotho",
su: "Basa Sunda",
sv: "svenska",
sw: "Kiswahili",
ta: "தமிழ்",
te: "తెలుగు",
tg: "тоҷикӣ",
th: "ไทย",
ti: "ትግርኛ",
tk: "türkmen dili",
tl: "Filipino",
tn: "Setswana",
to: "lea fakatonga",
tr: "Türkçe",
ts: "Tsonga",
tt: "татар",
tw: "Akan",
ty: "Tahitian",
ug: "ئۇيغۇرچە",
uk: "українська",
ur: "اردو",
uz: "o'zbek",
ve: "Venda",
vi: "Tiếng Việt",
vo: "Volapük",
wa: "Walloon",
wo: "Wolof",
xh: "IsiXhosa",
yi: "ייִדיש",
yo: "Èdè Yorùbá",
za: "Vahcuengh",
"zh-Hans": "简体中文",
"zh-Hant": "繁體中文",
"zh-CN": "中文(中国)",
"zh-TW": "中文(台灣)",
"zh-HK": "中文(中國香港特別行政區)",
zu: "isiZulu",
};
/**
* Returns the native display name for a language code (e.g. "de" → "Deutsch").
* Falls back to the raw code if the code is not in the map.
*/
export function getLanguageDisplayName(code: string): string {
return NATIVE_NAMES[code as Iso639Code] ?? code;
}