localization: fix two defects found by actually rendering the shipped locales

Neither was visible from the source or from any test — both only showed up
switching the live UI to es-ES/de-DE/fr-FR in a browser, which is what
turned up the catalogue-wiring bug this builds on top of (see prior commit).

1. FileItem.tsx had two `isDir ? 'Folder' : ...` ternaries the scanner
   cannot see (values computed inside a JSX expression, not JSX text) —
   the Type column and the grid-view size line both showed the English
   word "Folder" regardless of locale. New key files.grid.folderType,
   translated in all five catalogs.

2. files.menu.selected was a single hardcoded string ("selected" /
   "seleccionados" / "sélectionnés" / ...), not a plural form. English
   doesn't inflect "selected" for number so this was invisible in the
   source locale, but Spanish and French do: "1 seleccionados" and
   "1 sélectionnés" are both wrong, should be singular. Converted to a
   {one, other} pair in all five locales (pt-PT included — same bug,
   pre-existing) and updated SelectionActions.tsx to pass count. German
   was fine either way ("ausgewählt" doesn't inflect here).

Verified by minting a token for the owner's own account, driving a
headless browser through es-ES/de-DE/fr-FR at /settings/profile, /files,
and the file context menu, and reading the screenshots — not just running
the test suite. Owner's real settings were read before and restored after.
This commit is contained in:
2026-08-23 17:50:28 +00:00
parent fbdfd3fb89
commit 1ff271bb5e
7 changed files with 29 additions and 8 deletions
@@ -588,6 +588,7 @@ export const FileItem = ({
isPinned, isPinned,
canPaste, canPaste,
}: FileItemProps) => { }: FileItemProps) => {
const { t } = useTranslation();
const { date } = useFormat(); const { date } = useFormat();
const [renaming, setRenaming] = useState(false); const [renaming, setRenaming] = useState(false);
const clickTimer = useRef<ReturnType<typeof setTimeout> | null>(null); const clickTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -720,7 +721,7 @@ export const FileItem = ({
)} )}
</div> </div>
<span className="hidden md:inline text-xs text-duck-dark/50 shrink-0 w-20 text-right"> <span className="hidden md:inline text-xs text-duck-dark/50 shrink-0 w-20 text-right">
{isDir ? 'Folder' : getFileType(entry.name).replace(/^\w/, (c) => c.toUpperCase())} {isDir ? t('files.grid.folderType') : getFileType(entry.name).replace(/^\w/, (c) => c.toUpperCase())}
</span> </span>
<span className="hidden md:inline text-xs text-duck-dark/50 shrink-0 w-20 text-right"> <span className="hidden md:inline text-xs text-duck-dark/50 shrink-0 w-20 text-right">
{isDir ? '--' : formatSize(entry.size)} {isDir ? '--' : formatSize(entry.size)}
@@ -774,7 +775,7 @@ export const FileItem = ({
)} )}
<span className="text-xs text-duck-dark/50"> <span className="text-xs text-duck-dark/50">
{isDir ? 'Folder' : formatSize(entry.size)} {isDir ? t('files.grid.folderType') : formatSize(entry.size)}
{' · '} {' · '}
{date(entry.modifiedAt, { month: 'short', day: 'numeric', year: 'numeric' })} {date(entry.modifiedAt, { month: 'short', day: 'numeric', year: 'numeric' })}
</span> </span>
@@ -23,7 +23,7 @@ export const SelectionActions = ({ fileBrowserManager }: SelectionActionsProps)
<> <>
<span className="text-sm font-medium text-duck-dark/70 mr-1"> <span className="text-sm font-medium text-duck-dark/70 mr-1">
{selected.size} {selected.size}
<span className="hidden md:inline"> {t('files.menu.selected')}</span> <span className="hidden md:inline"> {t('files.menu.selected', { count: selected.size })}</span>
</span> </span>
<button <button
onClick={handleCut} onClick={handleCut}
@@ -512,12 +512,16 @@
"gitClone": "Git-Klon", "gitClone": "Git-Klon",
"searchFiles": "Dateien durchsuchen...", "searchFiles": "Dateien durchsuchen...",
"clearSelection": "Auswahl aufheben", "clearSelection": "Auswahl aufheben",
"selected": "ausgewählt", "selected": {
"one": "ausgewählt",
"other": "ausgewählt"
},
"refresh": "Aktualisieren" "refresh": "Aktualisieren"
}, },
"grid": { "grid": {
"empty": "Dieser Ordner ist leer", "empty": "Dieser Ordner ist leer",
"sort": "Sortieren:", "sort": "Sortieren:",
"folderType": "Ordner",
"name": "Name", "name": "Name",
"type": "Typ", "type": "Typ",
"size": "Größe", "size": "Größe",
@@ -512,12 +512,16 @@
"gitClone": "Git clone", "gitClone": "Git clone",
"searchFiles": "Search files...", "searchFiles": "Search files...",
"clearSelection": "Clear selection", "clearSelection": "Clear selection",
"selected": "selected", "selected": {
"one": "selected",
"other": "selected"
},
"refresh": "Refresh" "refresh": "Refresh"
}, },
"grid": { "grid": {
"empty": "This folder is empty", "empty": "This folder is empty",
"sort": "Sort:", "sort": "Sort:",
"folderType": "Folder",
"name": "Name", "name": "Name",
"type": "Type", "type": "Type",
"size": "Size", "size": "Size",
@@ -512,12 +512,16 @@
"gitClone": "Clonar con Git", "gitClone": "Clonar con Git",
"searchFiles": "Buscar archivos...", "searchFiles": "Buscar archivos...",
"clearSelection": "Limpiar selección", "clearSelection": "Limpiar selección",
"selected": "seleccionados", "selected": {
"one": "seleccionado",
"other": "seleccionados"
},
"refresh": "Actualizar" "refresh": "Actualizar"
}, },
"grid": { "grid": {
"empty": "Esta carpeta está vacía", "empty": "Esta carpeta está vacía",
"sort": "Ordenar:", "sort": "Ordenar:",
"folderType": "Carpeta",
"name": "Nombre", "name": "Nombre",
"type": "Tipo", "type": "Tipo",
"size": "Tamaño", "size": "Tamaño",
@@ -512,12 +512,16 @@
"gitClone": "Cloner via Git", "gitClone": "Cloner via Git",
"searchFiles": "Rechercher des fichiers...", "searchFiles": "Rechercher des fichiers...",
"clearSelection": "Effacer la sélection", "clearSelection": "Effacer la sélection",
"selected": "sélectionnés", "selected": {
"one": "sélectionné",
"other": "sélectionnés"
},
"refresh": "Actualiser" "refresh": "Actualiser"
}, },
"grid": { "grid": {
"empty": "Ce dossier est vide", "empty": "Ce dossier est vide",
"sort": "Trier :", "sort": "Trier :",
"folderType": "Dossier",
"name": "Nom", "name": "Nom",
"type": "Type", "type": "Type",
"size": "Taille", "size": "Taille",
@@ -512,12 +512,16 @@
"gitClone": "Clonar via Git", "gitClone": "Clonar via Git",
"searchFiles": "Pesquisar ficheiros...", "searchFiles": "Pesquisar ficheiros...",
"clearSelection": "Limpar seleção", "clearSelection": "Limpar seleção",
"selected": "selecionados", "selected": {
"one": "selecionado",
"other": "selecionados"
},
"refresh": "Atualizar" "refresh": "Atualizar"
}, },
"grid": { "grid": {
"empty": "Esta pasta está vazia", "empty": "Esta pasta está vazia",
"sort": "Ordenar:", "sort": "Ordenar:",
"folderType": "Pasta",
"name": "Nome", "name": "Nome",
"type": "Tipo", "type": "Tipo",
"size": "Tamanho", "size": "Tamanho",