84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { getSqliteConnection } from "@/server/db/client";
|
|
import { DomainError } from "@/server/domain/errors";
|
|
import { I18nService } from "@/server/i18n/service";
|
|
import { requireFreelancerBackend } from "@/server/web/freelancer";
|
|
import { cleanText } from "@/server/web/form-data";
|
|
|
|
const SUPPORTED_BCP47_PATTERN = /^[a-z]{2}(?:-[A-Z]{2}[0-9]?)?$/;
|
|
|
|
export async function createLanguageAction(formData: FormData) {
|
|
const rawCode = cleanText(formData.get("code")) ?? "";
|
|
const name = cleanText(formData.get("name")) ?? "";
|
|
const nativeName = cleanText(formData.get("nativeName")) ?? "";
|
|
const fallbackLocale = cleanText(formData.get("fallbackLocale")) ?? "";
|
|
const textDirection = cleanText(formData.get("textDirection")) ?? "";
|
|
const code = canonicalizeSupportedLocale(rawCode);
|
|
|
|
if (!code) return { errorKey: "settings.languageNew.errors.code" };
|
|
if (!name || name.length > 80) {
|
|
return { errorKey: "settings.languageNew.errors.name" };
|
|
}
|
|
if (!nativeName || nativeName.length > 80) {
|
|
return { errorKey: "settings.languageNew.errors.nativeName" };
|
|
}
|
|
if (textDirection !== "ltr" && textDirection !== "rtl") {
|
|
return { errorKey: "settings.languageNew.errors.direction" };
|
|
}
|
|
if (fallbackLocale === code) {
|
|
return { errorKey: "settings.languageNew.errors.selfFallback" };
|
|
}
|
|
|
|
try {
|
|
const { actor } = await requireFreelancerBackend();
|
|
const service = new I18nService(getSqliteConnection().db);
|
|
const locales = service.listLocales(actor);
|
|
if (locales.some((locale) => locale.code === code)) {
|
|
return { errorKey: "settings.languageNew.errors.duplicate" };
|
|
}
|
|
if (
|
|
!fallbackLocale
|
|
|| !locales.some(
|
|
(locale) => locale.code === fallbackLocale && locale.status !== "archived",
|
|
)
|
|
) {
|
|
return { errorKey: "settings.languageNew.errors.fallback" };
|
|
}
|
|
|
|
const locale = service.createLocale(actor, {
|
|
code,
|
|
name,
|
|
nativeName,
|
|
fallbackLocale,
|
|
textDirection,
|
|
});
|
|
revalidatePath("/settings/languages");
|
|
return { success: true, locale: locale.code };
|
|
} catch (error) {
|
|
console.error("Language creation failed", error);
|
|
if (error instanceof DomainError) {
|
|
if (error.details?.reason === "fallback_loop") {
|
|
return { errorKey: "settings.languageNew.errors.fallbackLoop" };
|
|
}
|
|
if (error.details?.reason === "self_fallback") {
|
|
return { errorKey: "settings.languageNew.errors.selfFallback" };
|
|
}
|
|
if (error.code === "CONFLICT") {
|
|
return { errorKey: "settings.languageNew.errors.duplicate" };
|
|
}
|
|
}
|
|
return { errorKey: "settings.languageNew.errors.createFailed" };
|
|
}
|
|
}
|
|
|
|
function canonicalizeSupportedLocale(value: string): string | null {
|
|
try {
|
|
const [canonical] = Intl.getCanonicalLocales(value.replaceAll("_", "-"));
|
|
return canonical && SUPPORTED_BCP47_PATTERN.test(canonical) ? canonical : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|