mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-11-02 08:35:24 +00:00
fix: proper plural forms for count strings and full translations
This commit is contained in:
1 parent
874ad9f4fd
commit
83968ede80
47 files changed
+70068
-38949
No files matched your search
@@ -14,6 +14,7 @@
|
||||
### Fixed
|
||||
|
||||
- Regional browser languages like `es-MX`, `pt` or `zh-HK` showed English instead of the closest translation
|
||||
- Counts like "1 keys" or "Moved 1 host(s)" now use the singular
|
||||
|
||||
## 26.10.0
|
||||
|
||||
|
||||
@@ -20,9 +20,30 @@ const OUT = path.join(
|
||||
"settings-search-index.ts",
|
||||
);
|
||||
|
||||
function flatKeys(obj, prefix = "") {
|
||||
return Object.entries(obj).flatMap(([key, value]) =>
|
||||
typeof value === "object"
|
||||
? flatKeys(value, `${prefix}${key}.`)
|
||||
: [`${prefix}${key}`],
|
||||
);
|
||||
}
|
||||
|
||||
const EN_KEYS = new Set(
|
||||
flatKeys(require(path.join(ROOT, "src", "ui", "locales", "en.json"))),
|
||||
);
|
||||
|
||||
// t("x", { count }) reads x_one or x_other, so index the plural key.
|
||||
function realKey(key) {
|
||||
return EN_KEYS.has(key) || !EN_KEYS.has(`${key}_other`)
|
||||
? key
|
||||
: `${key}_other`;
|
||||
}
|
||||
|
||||
function keysIn(text) {
|
||||
return [
|
||||
...new Set([...text.matchAll(/\bt\(\s*"([\w.-]+)"/g)].map((m) => m[1])),
|
||||
...new Set(
|
||||
[...text.matchAll(/\bt\(\s*"([\w.-]+)"/g)].map((m) => realKey(m[1])),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+34
-17
@@ -344,7 +344,8 @@
|
||||
"unsavedChangesDescription": "Your host changes have not been saved. Discard them?",
|
||||
"keepEditing": "Keep editing",
|
||||
"discardChanges": "Discard changes",
|
||||
"movedToFolder": "Moved {{count}} host(s) to \"{{folder}}\"",
|
||||
"movedToFolder_one": "Moved {{count}} host to \"{{folder}}\"",
|
||||
"movedToFolder_other": "Moved {{count}} hosts to \"{{folder}}\"",
|
||||
"editHostTooltip": "Edit host",
|
||||
"behavior": "Behavior",
|
||||
"scrollbackBuffer": "Scrollback Buffer",
|
||||
@@ -545,7 +546,8 @@
|
||||
"collapseSubHosts": "Collapse sub-hosts",
|
||||
"expandSubHosts": "Expand sub-hosts",
|
||||
"cannotNestUnderDescendant": "Can't nest a host under its own sub-host",
|
||||
"movedToParent": "Moved {{count}} host(s) under \"{{parent}}\"",
|
||||
"movedToParent_one": "Moved {{count}} host under \"{{parent}}\"",
|
||||
"movedToParent_other": "Moved {{count}} hosts under \"{{parent}}\"",
|
||||
"newFolder": "New folder",
|
||||
"createFolderTitle": "Create folder",
|
||||
"editFolderTitle": "Edit folder",
|
||||
@@ -573,7 +575,8 @@
|
||||
"folderSaved": "Folder saved",
|
||||
"failedToSaveFolder": "Failed to save folder",
|
||||
"folderDeleted": "Deleted folder \"{{name}}\"",
|
||||
"deleteFolderConfirm": "Delete \"{{name}}\" and its {{count}} host(s)? This cannot be undone.",
|
||||
"deleteFolderConfirm_one": "Delete \"{{name}}\" and its {{count}} host? This cannot be undone.",
|
||||
"deleteFolderConfirm_other": "Delete \"{{name}}\" and its {{count}} hosts? This cannot be undone.",
|
||||
"failedToMoveHosts": "Failed to move hosts",
|
||||
"failedToReorder": "Failed to reorder",
|
||||
"expandAll": "Expand all folders",
|
||||
@@ -641,7 +644,8 @@
|
||||
"copyLink": "Copy Link",
|
||||
"deleteCredentialConfirm": "Delete credential \"{{name}}\"?",
|
||||
"deletedCredential": "Deleted {{name}}",
|
||||
"deleteHostsConfirm": "Delete {{count}} host{{plural}}? This cannot be undone.",
|
||||
"deleteHostsConfirm_one": "Delete {{count}} host? This cannot be undone.",
|
||||
"deleteHostsConfirm_other": "Delete {{count}} hosts? This cannot be undone.",
|
||||
"movedToRoot": "Moved to root",
|
||||
"addTagsPlaceholder": "Add tags...",
|
||||
"authDetails": "Authentication Details",
|
||||
@@ -700,7 +704,8 @@
|
||||
"groupFeatureFlags": "Plugin settings",
|
||||
"groupAdvanced": "Advanced config",
|
||||
"preview": "Preview",
|
||||
"moreHosts": "... {{count}} more hosts",
|
||||
"moreHosts_one": "... {{count}} more host",
|
||||
"moreHosts_other": "... {{count}} more hosts",
|
||||
"summary": "{{selected}} of {{total}} hosts",
|
||||
"credentialsIncluded": "credentials included",
|
||||
"credentialsExcluded": "credentials excluded",
|
||||
@@ -718,11 +723,14 @@
|
||||
"moveMenu": "Move",
|
||||
"connectSelected": "Connect",
|
||||
"cancelSelection": "Cancel",
|
||||
"deletedCount": "Deleted {{count}} hosts",
|
||||
"failedToDeleteCount": "Failed to delete {{count}} hosts",
|
||||
"deletedCount_one": "Deleted {{count}} host",
|
||||
"deletedCount_other": "Deleted {{count}} hosts",
|
||||
"failedToDeleteCount_one": "Failed to delete {{count}} host",
|
||||
"failedToDeleteCount_other": "Failed to delete {{count}} hosts",
|
||||
"duplicatedHost": "Duplicated \"{{name}}\"",
|
||||
"failedToDuplicateHost": "Failed to duplicate host",
|
||||
"updatedCount": "Updated {{count}} hosts",
|
||||
"updatedCount_one": "Updated {{count}} host",
|
||||
"updatedCount_other": "Updated {{count}} hosts",
|
||||
"friendlyNameLabel": "Friendly Name",
|
||||
"descriptionLabel": "Description",
|
||||
"loadingHosts": "Loading hosts...",
|
||||
@@ -762,7 +770,8 @@
|
||||
"shareHostTitle": "Share: {{name}}",
|
||||
"shareFolder": "Share Folder",
|
||||
"shareFolderTitle": "Share folder: {{name}}",
|
||||
"folderSharedSuccessfully": "Shared {{count}} host(s) in folder",
|
||||
"folderSharedSuccessfully_one": "Shared {{count}} host in folder",
|
||||
"folderSharedSuccessfully_other": "Shared {{count}} hosts in folder",
|
||||
"failedToShareFolder": "Failed to share folder",
|
||||
"sharing": {
|
||||
"loadError": "Failed to load sharing data. Please try again.",
|
||||
@@ -921,7 +930,8 @@
|
||||
"unsplit": "Unsplit",
|
||||
"layouts": "Layouts",
|
||||
"applyLayout": "Change layout",
|
||||
"maxPanes": "A split can hold up to {{count}} panes",
|
||||
"maxPanes_one": "A split can hold up to {{count}} pane",
|
||||
"maxPanes_other": "A split can hold up to {{count}} panes",
|
||||
"dropSplit": "Drop to split",
|
||||
"dropHere": "Drop to show here",
|
||||
"paneActions": "Pane actions",
|
||||
@@ -1237,7 +1247,8 @@
|
||||
"statusCheck": "Status Check",
|
||||
"sec": "sec",
|
||||
"logLevel": "Log Level",
|
||||
"usersCount": "{{count}} users",
|
||||
"usersCount_one": "{{count}} user",
|
||||
"usersCount_other": "{{count}} users",
|
||||
"searchUsers": "Search users...",
|
||||
"noUsers": "No users",
|
||||
"noUsersMatch": "No users match your search",
|
||||
@@ -1247,7 +1258,8 @@
|
||||
"roleName": "Name",
|
||||
"roleDisplayName": "Display Name",
|
||||
"roleDescription": "Description",
|
||||
"rolesCount": "{{count}} roles",
|
||||
"rolesCount_one": "{{count}} role",
|
||||
"rolesCount_other": "{{count}} roles",
|
||||
"createRole": "Create",
|
||||
"creating": "Creating...",
|
||||
"exportDatabase": "Export Database",
|
||||
@@ -1261,7 +1273,8 @@
|
||||
"changeFile": "Change",
|
||||
"import": "Import",
|
||||
"importing": "Importing...",
|
||||
"apiKeysCount": "{{count}} keys",
|
||||
"apiKeysCount_one": "{{count}} key",
|
||||
"apiKeysCount_other": "{{count}} keys",
|
||||
"apiKeysDocsLink": "View docs",
|
||||
"apiKeysCliHint": "Use an API key with the Termix CLI to manage servers from your terminal.",
|
||||
"apiKeysCliLink": "View CLI docs",
|
||||
@@ -1392,14 +1405,16 @@
|
||||
"exportUserDataDesc": "Download this user's hosts, credentials and file manager data as JSON. Secrets are decrypted.",
|
||||
"exportUserDataSuccess": "User data exported",
|
||||
"exportUserDataFailed": "Failed to export user data",
|
||||
"hostsCount": "{{count}} hosts",
|
||||
"hostsCount_one": "{{count}} host",
|
||||
"hostsCount_other": "{{count}} hosts",
|
||||
"addHostForUser": "Add Host",
|
||||
"noHostsForUser": "This user has no hosts",
|
||||
"connectToHost": "Connect",
|
||||
"deleteHostConfirm": "Delete host \"{{name}}\" belonging to {{username}}?",
|
||||
"hostDeletedSuccess": "Host deleted",
|
||||
"hostDeleteFailed": "Failed to delete host",
|
||||
"credentialsCount": "{{count}} credentials",
|
||||
"credentialsCount_one": "{{count}} credential",
|
||||
"credentialsCount_other": "{{count}} credentials",
|
||||
"addCredentialForUser": "Add Credential",
|
||||
"noCredentialsForUser": "This user has no credentials",
|
||||
"deleteCredentialConfirm": "Delete credential \"{{name}}\" belonging to {{username}}?",
|
||||
@@ -1428,7 +1443,8 @@
|
||||
"importError": "Database import failed",
|
||||
"sectionHostDefaults": "Host Defaults",
|
||||
"rolePermissions": {
|
||||
"count": "{{count}} permissions",
|
||||
"count_one": "{{count}} permission",
|
||||
"count_other": "{{count}} permissions",
|
||||
"editAction": "Edit permissions",
|
||||
"loadError": "Failed to load the permissions catalog",
|
||||
"saved": "Role permissions saved",
|
||||
@@ -1647,7 +1663,8 @@
|
||||
"apiKeyCreatedWarning": "Key created - copy it now, it won't be shown again.",
|
||||
"copyApiKey": "Copy API key",
|
||||
"copiedToClipboard": "Copied to clipboard",
|
||||
"apiKeyCount": "{{count}} keys",
|
||||
"apiKeyCount_one": "{{count}} key",
|
||||
"apiKeyCount_other": "{{count}} keys",
|
||||
"newKey": "New Key",
|
||||
"noApiKeys": "No API keys yet.",
|
||||
"apiKeyActive": "Active",
|
||||
|
||||
+1507
-1186
File diff suppressed because it is too large.
Load diff
+1634
-1101
File diff suppressed because it is too large.
Load diff
+1597
-1276
File diff suppressed because it is too large.
Load diff
+1684
-1363
File diff suppressed because it is too large.
Load diff
+1547
-1173
File diff suppressed because it is too large.
Load diff
+1603
-1176
File diff suppressed because it is too large.
Load diff
+1374
-1053
File diff suppressed because it is too large.
Load diff
+1327
-1006
File diff suppressed because it is too large.
Load diff
+1726
-1405
File diff suppressed because it is too large.
Load diff
+1323
-949
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1549
-1228
File diff suppressed because it is too large.
Load diff
+1290
-916
File diff suppressed because it is too large.
Load diff
+1787
-1413
File diff suppressed because it is too large.
Load diff
+1514
-1193
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1623
-1302
File diff suppressed because it is too large.
Load diff
+1418
-1151
File diff suppressed because it is too large.
Load diff
+1355
-981
File diff suppressed because it is too large.
Load diff
+1368
-1100
File diff suppressed because it is too large.
Load diff
+1358
-1090
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1545
-1224
File diff suppressed because it is too large.
Load diff
+1396
-1075
File diff suppressed because it is too large.
Load diff
+1399
-972
File diff suppressed because it is too large.
Load diff
+1546
-1172
File diff suppressed because it is too large.
Load diff
+1439
-1065
File diff suppressed because it is too large.
Load diff
+1788
-1414
File diff suppressed because it is too large.
Load diff
+1531
-1104
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1587
-1213
File diff suppressed because it is too large.
Load diff
+1385
-1064
File diff suppressed because it is too large.
Load diff
+1452
-1184
File diff suppressed because it is too large.
Load diff
+1411
-1090
File diff suppressed because it is too large.
Load diff
+1584
-1157
File diff suppressed because it is too large.
Load diff
+1309
-1043
File diff suppressed because it is too large.
Load diff
+1224
-956
File diff suppressed because it is too large.
Load diff
+1398
-1130
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -39,7 +39,7 @@ export const SETTINGS_SEARCH_INDEX: Record<string, string[]> = {
|
||||
"admin.apiKeyUser",
|
||||
"admin.apiKeysCliHint",
|
||||
"admin.apiKeysCliLink",
|
||||
"admin.apiKeysCount",
|
||||
"admin.apiKeysCount_other",
|
||||
"admin.apiKeysDocsLink",
|
||||
"admin.copiedToClipboard",
|
||||
"admin.createKey",
|
||||
@@ -162,7 +162,7 @@ export const SETTINGS_SEARCH_INDEX: Record<string, string[]> = {
|
||||
"admin.roleDescription",
|
||||
"admin.roleDisplayName",
|
||||
"admin.roleName",
|
||||
"admin.rolePermissions.count",
|
||||
"admin.rolePermissions.count_other",
|
||||
"admin.rolePermissions.editAction",
|
||||
"admin.rolePermissions.loadError",
|
||||
"admin.rolePermissions.pluginDisabled",
|
||||
@@ -170,7 +170,7 @@ export const SETTINGS_SEARCH_INDEX: Record<string, string[]> = {
|
||||
"admin.rolePermissions.saveError",
|
||||
"admin.rolePermissions.saved",
|
||||
"admin.rolePermissions.saving",
|
||||
"admin.rolesCount",
|
||||
"admin.rolesCount_other",
|
||||
"admin.sectionRoles",
|
||||
"admin.systemBadge",
|
||||
"common.cancel",
|
||||
@@ -282,7 +282,7 @@ export const SETTINGS_SEARCH_INDEX: Record<string, string[]> = {
|
||||
"admin.userAdminAccess",
|
||||
"admin.userAdminAccessDesc",
|
||||
"admin.userRoles",
|
||||
"admin.usersCount",
|
||||
"admin.usersCount_other",
|
||||
"common.cancel",
|
||||
"common.loading",
|
||||
"common.next",
|
||||
@@ -294,7 +294,7 @@ export const SETTINGS_SEARCH_INDEX: Record<string, string[]> = {
|
||||
"admin.revokeKey",
|
||||
"admin.revokeKeyConfirm",
|
||||
"newUi.sidebar.userProfile.apiKeyActive",
|
||||
"newUi.sidebar.userProfile.apiKeyCount",
|
||||
"newUi.sidebar.userProfile.apiKeyCount_other",
|
||||
"newUi.sidebar.userProfile.apiKeyCreateFailed",
|
||||
"newUi.sidebar.userProfile.apiKeyCreated",
|
||||
"newUi.sidebar.userProfile.apiKeyCreatedWarning",
|
||||
|
||||
@@ -1413,7 +1413,6 @@ export function SidebarTree({
|
||||
setConfirmDialog({
|
||||
message: t("hosts.deleteHostsConfirm", {
|
||||
count: selectedHostIds.size,
|
||||
plural: selectedHostIds.size !== 1 ? "s" : "",
|
||||
}),
|
||||
onConfirm: async () => {
|
||||
const ids = Array.from(selectedHostIds);
|
||||
|
||||
Reference in new issue
Block a user