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,
canPaste,
}: FileItemProps) => {
const { t } = useTranslation();
const { date } = useFormat();
const [renaming, setRenaming] = useState(false);
const clickTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -720,7 +721,7 @@ export const FileItem = ({
)}
</div>
<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 className="hidden md:inline text-xs text-duck-dark/50 shrink-0 w-20 text-right">
{isDir ? '--' : formatSize(entry.size)}
@@ -774,7 +775,7 @@ export const FileItem = ({
)}
<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' })}
</span>
@@ -23,7 +23,7 @@ export const SelectionActions = ({ fileBrowserManager }: SelectionActionsProps)
<>
<span className="text-sm font-medium text-duck-dark/70 mr-1">
{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>
<button
onClick={handleCut}
@@ -512,12 +512,16 @@
"gitClone": "Git-Klon",
"searchFiles": "Dateien durchsuchen...",
"clearSelection": "Auswahl aufheben",
"selected": "ausgewählt",
"selected": {
"one": "ausgewählt",
"other": "ausgewählt"
},
"refresh": "Aktualisieren"
},
"grid": {
"empty": "Dieser Ordner ist leer",
"sort": "Sortieren:",
"folderType": "Ordner",
"name": "Name",
"type": "Typ",
"size": "Größe",
@@ -512,12 +512,16 @@
"gitClone": "Git clone",
"searchFiles": "Search files...",
"clearSelection": "Clear selection",
"selected": "selected",
"selected": {
"one": "selected",
"other": "selected"
},
"refresh": "Refresh"
},
"grid": {
"empty": "This folder is empty",
"sort": "Sort:",
"folderType": "Folder",
"name": "Name",
"type": "Type",
"size": "Size",
@@ -512,12 +512,16 @@
"gitClone": "Clonar con Git",
"searchFiles": "Buscar archivos...",
"clearSelection": "Limpiar selección",
"selected": "seleccionados",
"selected": {
"one": "seleccionado",
"other": "seleccionados"
},
"refresh": "Actualizar"
},
"grid": {
"empty": "Esta carpeta está vacía",
"sort": "Ordenar:",
"folderType": "Carpeta",
"name": "Nombre",
"type": "Tipo",
"size": "Tamaño",
@@ -512,12 +512,16 @@
"gitClone": "Cloner via Git",
"searchFiles": "Rechercher des fichiers...",
"clearSelection": "Effacer la sélection",
"selected": "sélectionnés",
"selected": {
"one": "sélectionné",
"other": "sélectionnés"
},
"refresh": "Actualiser"
},
"grid": {
"empty": "Ce dossier est vide",
"sort": "Trier :",
"folderType": "Dossier",
"name": "Nom",
"type": "Type",
"size": "Taille",
@@ -512,12 +512,16 @@
"gitClone": "Clonar via Git",
"searchFiles": "Pesquisar ficheiros...",
"clearSelection": "Limpar seleção",
"selected": "selecionados",
"selected": {
"one": "selecionado",
"other": "selecionados"
},
"refresh": "Atualizar"
},
"grid": {
"empty": "Esta pasta está vazia",
"sort": "Ordenar:",
"folderType": "Pasta",
"name": "Nome",
"type": "Tipo",
"size": "Tamanho",