i18n(users): translate the terminated-sessions toast
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Failing after 2m12s
Deploy to Production / Trigger production update (push) Failing after 11s

Both toasts printed the endpoint's own message field verbatim, so a Russian UI
answered "Terminated 0 sessions". That string is assembled in Python and can
never be translated where it is built, so the endpoints now also return the raw
count and the phrase is composed on the client.

message is kept: it goes to the audit log and may have other consumers, and
adding a field breaks no one.

Russian needs three forms, and zero does not land in the obvious one — 0 is the
"many" category ("сессий", not "сессии") — hence four keys against English's
two.

Fixes both call sites: the per-user button in the table row and Terminate all
sessions on the Sessions tab, which shared the same untranslated string.
This commit is contained in:
2026-08-12 15:00:07 +05:00
parent 9e4120bd8d
commit 8337539e9e
5 changed files with 25 additions and 8 deletions

View File

@@ -978,7 +978,9 @@ async def terminate_user_sessions(
resource_name=user.username
)
return {"message": f"Terminated {count} sessions"}
# count отдаётся отдельным полем: фронтенд склоняет фразу сам, показывать
# message пользователю нельзя — он не переводится.
return {"message": f"Terminated {count} sessions", "count": count}
# ==================== Security Settings API ====================
@@ -1167,7 +1169,7 @@ async def terminate_all_sessions(
ip_address=get_client_ip(request)
)
return {"message": f"Terminated {deleted} sessions"}
return {"message": f"Terminated {deleted} sessions", "count": deleted}
# ==================== Security Events API ====================

View File

@@ -262,7 +262,7 @@ function UsersTab() {
variant="ghost" size="icon" className="h-7 w-7"
title={t('users.terminate_sessions')}
onClick={() => terminateSessions.mutate(u.id, {
onSuccess: r => toast.success(r.message),
onSuccess: r => toast.success(t('users.sessions_terminated', { count: r.count })),
onError: e => toast.error(getErrMsg(e)),
})}
>
@@ -1074,7 +1074,7 @@ function SessionsTab() {
variant="destructive" size="sm"
disabled={sessions.length === 0 || terminateAll.isPending}
onClick={() => terminateAll.mutate(undefined, {
onSuccess: r => toast.success(r.message),
onSuccess: r => toast.success(t('users.sessions_terminated', { count: r.count })),
onError: e => toast.error(getErrMsg(e)),
})}
>

View File

@@ -173,11 +173,20 @@ export function useUnlockUser() {
});
}
/**
* `message` is the backend's own English wording and is not shown to the user;
* the toast is built from `count` so it can be translated and declined.
*/
export interface TerminateSessionsResult {
message: string;
count: number;
}
export function useTerminateUserSessions() {
const qc = useQueryClient();
return useMutation({
mutationFn: (userId: number) =>
apiClient.post<{ message: string }>(`/admin/api/users/${userId}/terminate-sessions`),
apiClient.post<TerminateSessionsResult>(`/admin/api/users/${userId}/terminate-sessions`),
onSuccess: () => qc.invalidateQueries({ queryKey: userKeys.sessions }),
});
}
@@ -337,7 +346,7 @@ export function useTerminateSession() {
export function useTerminateAllSessions() {
const qc = useQueryClient();
return useMutation({
mutationFn: () => apiClient.post<{ message: string }>('/admin/api/sessions/terminate-all'),
mutationFn: () => apiClient.post<TerminateSessionsResult>('/admin/api/sessions/terminate-all'),
onSuccess: () => qc.invalidateQueries({ queryKey: userKeys.sessions }),
});
}

View File

@@ -1509,7 +1509,9 @@
"used": "Used",
"unlimited": "unlimited",
"saved": "Quota saved"
}
},
"sessions_terminated_one": "Terminated {{count}} session",
"sessions_terminated_other": "Terminated {{count}} sessions"
},
"workspaces": {
"create": "Create Workspace",

View File

@@ -1509,7 +1509,11 @@
"used": "Использовано",
"unlimited": "без лимита",
"saved": "Квоты сохранены"
}
},
"sessions_terminated_one": "Завершена {{count}} сессия",
"sessions_terminated_few": "Завершено {{count}} сессии",
"sessions_terminated_many": "Завершено {{count}} сессий",
"sessions_terminated_other": "Завершено {{count}} сессий"
},
"workspaces": {
"create": "Создать рабочую область",