feat(i18n): localize portal account settings

This commit is contained in:
poyrazavsever
2026-07-21 13:37:23 +03:00
parent 48f9d7bc87
commit 86a9c79511
11 changed files with 397 additions and 40 deletions
+38 -4
View File
@@ -12,6 +12,7 @@ import {
appProfiles,
authAuditEvents,
clients,
instanceI18nSettings,
instanceLocales,
portalInvitations,
session,
@@ -298,6 +299,7 @@ export async function acceptPortalInvitation(input: {
.run();
throw new PortalInvitationError("INVITATION_EXPIRED", "Davet bağlantısının süresi dolmuş.");
}
const preferenceLocale = resolveActivePortalLocale(tx, invitation.locale);
const [existingUser] = tx
.select({ id: user.id })
@@ -365,7 +367,7 @@ export async function acceptPortalInvitation(input: {
const linkedClient = tx
.update(clients)
.set({ authUserId, portalLocale: invitation.locale, updatedAt: now })
.set({ authUserId, portalLocale: preferenceLocale, updatedAt: now })
.where(
and(
eq(clients.id, invitation.clientId),
@@ -384,12 +386,12 @@ export async function acceptPortalInvitation(input: {
tx.insert(userPreferences)
.values({
ownerUserId: authUserId,
language: invitation.locale,
language: preferenceLocale,
})
.onConflictDoUpdate({
target: userPreferences.ownerUserId,
set: {
language: invitation.locale,
language: preferenceLocale,
updatedAt: now.toISOString(),
},
})
@@ -418,7 +420,12 @@ export async function acceptPortalInvitation(input: {
type: "invitation_accepted",
authUserId,
email: invitation.email,
metadata: { invitationId: invitation.id, clientId: invitation.clientId, locale: invitation.locale },
metadata: {
invitationId: invitation.id,
clientId: invitation.clientId,
locale: invitation.locale,
preferenceLocale,
},
})
.run();
@@ -617,3 +624,30 @@ function assertPortalReadyLocale(
return row.code;
}
function resolveActivePortalLocale(
tx: Pick<ReturnType<typeof getSqliteConnection>["db"], "select">,
localeInput: string,
): string {
const defaultLocale = tx
.select({ defaultLocale: instanceI18nSettings.defaultLocale })
.from(instanceI18nSettings)
.where(eq(instanceI18nSettings.key, "default"))
.get()?.defaultLocale ?? "tr";
const candidates = [localeInput, defaultLocale, "tr", "en"];
for (const candidate of candidates) {
const locale = candidate.trim();
const row = tx
.select({ code: instanceLocales.code, status: instanceLocales.status })
.from(instanceLocales)
.where(eq(instanceLocales.code, locale))
.get();
if (row?.status === "active") {
return row.code;
}
}
return "tr";
}
@@ -0,0 +1 @@
ALTER TABLE `project_revisions` ADD `source_locale` text;
+8 -1
View File
@@ -85,6 +85,13 @@
"when": 1784626279393,
"tag": "0011_tricky_mordo",
"breakpoints": true
},
{
"idx": 12,
"version": "6",
"when": 1784627370000,
"tag": "0012_portal_revision_locale",
"breakpoints": true
}
]
}
}
+1
View File
@@ -326,6 +326,7 @@ export const projectRevisions = sqliteTable(
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
description: text("description").notNull(),
sourceLocale: text("source_locale"),
status: text("status").$type<RevisionStatus>().default("pending").notNull(),
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })