fix(i18n): translate submit button, density/view toggles; rename watchlist guide to Merkliste; add guide help buttons
Build & Push Docker Image / build (push) Successful in 2m31s

This commit is contained in:
opencode
2026-08-04 20:43:36 +02:00
parent 63aa14914f
commit 70e72dac71
23 changed files with 106 additions and 47 deletions
@@ -15,6 +15,7 @@ import {
import { useQueryClient } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
import { Button } from "@/components/ui/button";
import { GuideHelp } from "@/components/guide-help";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
@@ -105,7 +106,10 @@ export function AdminToolsTab() {
<Card>
<CardHeader className="flex flex-row items-center justify-between gap-4 flex-wrap">
<div className="space-y-1.5">
<CardTitle>{t("adminTools.allTools")}</CardTitle>
<CardTitle className="flex items-center gap-2">
{t("adminTools.allTools")}
<GuideHelp guide="administration" label={t("adminTools.allTools")} />
</CardTitle>
<CardDescription>
{selectedIds.length > 0 ? t("adminTools.selected", { count: selectedIds.length }) : t("adminTools.toolCount", { count: allTools.length })}
</CardDescription>
@@ -1,5 +1,6 @@
import { Slider } from "@/components/ui/slider";
import { Maximize2, Minimize2 } from "lucide-react";
import { useTranslation } from "react-i18next";
export type Density = "cozy" | "compact";
@@ -10,8 +11,13 @@ export function DensityToggle({
value: Density;
onValueChange: (d: Density) => void;
}) {
const { t } = useTranslation();
const densityLabel = value === "compact" ? t("common.densityCompact") : t("common.densityComfortable");
return (
<div className="flex items-center gap-2" title={`Density: ${value === "compact" ? "Compact" : "Comfortable"}`}>
<div
className="flex items-center gap-2"
title={t("common.densityTitle", { density: densityLabel })}
>
<Maximize2 className="w-4 h-4 text-muted-foreground" aria-hidden />
<Slider
className="w-16"
@@ -20,7 +26,7 @@ export function DensityToggle({
step={1}
value={[value === "compact" ? 1 : 0]}
onValueChange={([v]) => onValueChange(v === 1 ? "compact" : "cozy")}
aria-label="List density"
aria-label={t("common.listDensity")}
/>
<Minimize2 className="w-4 h-4 text-muted-foreground" aria-hidden />
</div>
@@ -105,7 +105,7 @@ export function FilterPopover({
step={0.5}
value={[minRating ?? 0]}
onValueChange={([v]) => onChange({ minRating: v })}
aria-label="Minimum rating"
aria-label={t("filter.minRating")}
/>
<span className="text-sm tabular-nums w-8 text-right text-muted-foreground">
{minRating != null ? `${minRating.toFixed(1)}+` : t("filter.any")}
@@ -17,6 +17,7 @@ import {
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/password-input";
import { GuideHelp } from "@/components/guide-help";
import { useToast } from "@/hooks/use-toast";
import { useChangeMyPassword, useGetPasswordRedirect, getGetPasswordRedirectQueryKey } from "@workspace/api-client-react";
import { useQueryClient } from "@tanstack/react-query";
@@ -164,7 +165,10 @@ export function UserMenu() {
<Dialog open={pwOpen} onOpenChange={setPwOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{t("auth.changePassword")}</DialogTitle>
<DialogTitle className="flex items-center gap-2">
{t("auth.changePassword")}
<GuideHelp guide="konto" label={t("auth.changePassword")} />
</DialogTitle>
</DialogHeader>
{!user?.isLocal ? (
<div className="space-y-4 py-2">
@@ -193,7 +197,7 @@ export function UserMenu() {
<PasswordInput
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
placeholder="min. 6 characters"
placeholder={t("auth.passwordMinLength")}
data-testid="input-new-password"
/>
</div>
@@ -1,12 +1,13 @@
import { LayoutGrid, List, Rows3 } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { useTranslation } from "react-i18next";
export type ViewMode = "grid" | "table" | "rows";
const OPTIONS: { value: ViewMode; label: string; Icon: typeof LayoutGrid }[] = [
{ value: "grid", label: "Grid", Icon: LayoutGrid },
{ value: "table", label: "Table", Icon: List },
{ value: "rows", label: "Rows", Icon: Rows3 },
const OPTIONS: { value: ViewMode; labelKey: string; Icon: typeof LayoutGrid }[] = [
{ value: "grid", labelKey: "common.viewGrid", Icon: LayoutGrid },
{ value: "table", labelKey: "common.viewTable", Icon: List },
{ value: "rows", labelKey: "common.viewRows", Icon: Rows3 },
];
export function ViewToggle({
@@ -16,6 +17,7 @@ export function ViewToggle({
value: ViewMode;
onValueChange: (v: ViewMode) => void;
}) {
const { t } = useTranslation();
return (
<ToggleGroup
type="single"
@@ -25,10 +27,16 @@ export function ViewToggle({
}}
variant="outline"
size="sm"
aria-label="View mode"
aria-label={t("common.viewMode")}
>
{OPTIONS.map(({ value: v, label, Icon }) => (
<ToggleGroupItem key={v} value={v} aria-label={label} title={label} data-testid={`view-${v}`}>
{OPTIONS.map(({ value: v, labelKey, Icon }) => (
<ToggleGroupItem
key={v}
value={v}
aria-label={t(labelKey)}
title={t(labelKey)}
data-testid={`view-${v}`}
>
<Icon className="w-4 h-4" />
</ToggleGroupItem>
))}
+11 -2
View File
@@ -30,6 +30,7 @@
"currentPassword": "Aktuelles Passwort",
"newPassword": "Neues Passwort",
"confirmPassword": "Passwort bestätigen",
"passwordMinLength": "mind. 6 Zeichen",
"pwMismatch": "Passwörter stimmen nicht überein",
"pwTooShort": "Passwort muss mindestens 8 Zeichen haben",
"pwChanged": "Passwort geändert",
@@ -54,8 +55,16 @@
"all": "Alle",
"none": "Keine",
"language": "Sprache",
"viewDetails": "Details ansehen",
"guideTooltip": "Anleitung in der Dokumentation"
"viewDetails": "Details ansehen",
"guideTooltip": "Anleitung in der Dokumentation",
"viewMode": "Ansichtsmodus",
"viewGrid": "Raster",
"viewTable": "Tabelle",
"viewRows": "Zeilen",
"listDensity": "Listendichte",
"densityCompact": "Kompakt",
"densityComfortable": "Komfortabel",
"densityTitle": "Dichte: {{density}}"
},
"home": {
"welcome": "Willkommen bei toolr",
+11 -2
View File
@@ -30,6 +30,7 @@
"currentPassword": "Current password",
"newPassword": "New password",
"confirmPassword": "Confirm password",
"passwordMinLength": "min. 6 characters",
"pwMismatch": "Passwords do not match",
"pwTooShort": "Password must be at least 8 characters",
"pwChanged": "Password changed",
@@ -54,8 +55,16 @@
"all": "All",
"none": "None",
"language": "Language",
"viewDetails": "View details",
"guideTooltip": "Guide in the documentation"
"viewDetails": "View details",
"guideTooltip": "Guide in the documentation",
"viewMode": "View mode",
"viewGrid": "Grid",
"viewTable": "Table",
"viewRows": "Rows",
"listDensity": "List density",
"densityCompact": "Compact",
"densityComfortable": "Comfortable",
"densityTitle": "Density: {{density}}"
},
"home": {
"welcome": "Welcome to toolr",
+5 -1
View File
@@ -7,6 +7,7 @@ import {
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { Layout } from "@/components/layout";
import { GuideHelp } from "@/components/guide-help";
import { ToolCard } from "@/components/tool-card";
import { Star, Wrench, MessageSquare, ArrowRight, Plus } from "lucide-react";
import { Link } from "wouter";
@@ -24,7 +25,10 @@ export default function Home() {
<div className="space-y-8 pb-8">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-end gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2">{t("home.welcome")}</h1>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-2">
{t("home.welcome")}
<GuideHelp guide="getting-started" label={t("home.welcome")} />
</h1>
<p className="text-muted-foreground">{t("home.tagline")}</p>
</div>
<Button asChild>
+5 -1
View File
@@ -1,6 +1,7 @@
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Layout } from "@/components/layout";
import { GuideHelp } from "@/components/guide-help";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -46,7 +47,10 @@ export default function RedundancyPage() {
<div className="space-y-6">
<div className="flex items-center gap-3">
<AlertTriangle className="w-6 h-6 text-amber-500" />
<h1 className="text-3xl font-bold">{t("redundancy.title")}</h1>
<h1 className="text-3xl font-bold flex items-center gap-2">
{t("redundancy.title")}
<GuideHelp guide="redundanz" label={t("redundancy.title")} />
</h1>
</div>
<p className="text-muted-foreground">
{t("redundancy.subtitle")}
+1 -1
View File
@@ -369,7 +369,7 @@ export default function ToolNew() {
className="w-full sm:w-auto"
data-testid="button-submit-tool"
>
{createTool.isPending ? "Adding Tool..." : "Submit Tool"}
{createTool.isPending ? t("toolForm.addingTool") : t("toolForm.submitTool")}
</Button>
</div>
</form>
@@ -6,6 +6,7 @@ import {
} from "@workspace/api-client-react";
import { useVirtualizer } from "@tanstack/react-virtual";
import { Layout } from "@/components/layout";
import { GuideHelp } from "@/components/guide-help";
import { ToolCard } from "@/components/tool-card";
import { ToolCardWide } from "@/components/tool-card-wide";
import { ToolRow, TABLE_GRID } from "@/components/tool-row";
@@ -230,7 +231,10 @@ export default function ToolsBrowse() {
<div className="space-y-6 pb-8">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-end gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2">{t("nav.browseTools")}</h1>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-2">
{t("nav.browseTools")}
<GuideHelp guide="tools-finden" label={t("browse.subtitle")} />
</h1>
<p className="text-muted-foreground">{t("browse.subtitle")}</p>
</div>
<Button asChild>
@@ -521,7 +525,10 @@ export default function ToolsBrowse() {
<AlertDialog open={compareUpsellOpen} onOpenChange={setCompareUpsellOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t("browse.comparePremiumTitle")}</AlertDialogTitle>
<AlertDialogTitle className="flex items-center gap-2">
{t("browse.comparePremiumTitle")}
<GuideHelp guide="plaene" label={t("browse.comparePremiumTitle")} />
</AlertDialogTitle>
<AlertDialogDescription>
{t("browse.comparePremiumSub")}
</AlertDialogDescription>
+5 -1
View File
@@ -15,6 +15,7 @@ import {
import { useQueryClient } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
import { Layout } from "@/components/layout";
import { GuideHelp } from "@/components/guide-help";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
@@ -156,7 +157,10 @@ export default function Trash() {
<Layout>
<div className="space-y-6 pb-10">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2">{t("trash.title")}</h1>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-2">
{t("trash.title")}
<GuideHelp guide="papierkorb" label={t("trash.title")} />
</h1>
<p className="text-muted-foreground">{t("trash.subtitle")}</p>
</div>
+2 -2
View File
@@ -10,7 +10,7 @@ Besuch bis zum Anlegen und Bewerten eines Tools.
## 1. Anmelden
Die meisten Aktionen (Tool anlegen, bewerten, Watchlist, Vergleichen) erfordern
Die meisten Aktionen (Tool anlegen, bewerten, Merkliste, Vergleichen) erfordern
ein Konto. Klicke unten links auf **Anmelden**. Je nach Konfiguration der
Instanz hast du zwei Möglichkeiten:
@@ -51,7 +51,7 @@ Siehe [Bewerten](/docs/handbook/bewerten).
## 5. Weiterführend
- [Tools vergleichen](/docs/handbook/vergleichen)
- [Watchlist](/docs/handbook/watchlist)
- [Merkliste](/docs/handbook/watchlist)
- [Analytics](/docs/handbook/analytics)
- [Pläne & Berechtigungen](/docs/handbook/plaene)
- [Administration](/docs/handbook/administration)
+1 -1
View File
@@ -18,7 +18,7 @@ die richtige Wahl zu treffen.
| **Tool anlegen** | Neues Tool mit Beschreibung, Kategorie, Features & Tags eintragen | Angemeldet |
| **Tool bearbeiten/löschen** | Eigene Tools pflegen (Ersteller:in oder Admin) | Angemeldet |
| **Bewerten** | Nützlichkeit & Bedienbarkeit (15) plus Kommentar vergeben | Angemeldet |
| **Watchlist** | Tools als Favoriten speichern | Premium |
| **Merkliste** | Tools als Favoriten speichern | Premium |
| **Vergleichen** | Tools nebeneinander gegenüberstellen | Premium |
| **Kosten erfassen** | Lizenz- und Kostenmodelle je Tool eintragen | Premium |
| **Analytics** | Statistiken, Top-Tools, Verteilungen | Alle |
+2 -2
View File
@@ -26,7 +26,7 @@ Der aktive Modus steht im Endpunkt
Dein Profil (Avatar, Name, E-Mail, Tarif) siehst du unten links im
Benutzermenü. Dort stehen dir folgende Aktionen zur Verfügung:
- **Watchlist** — deine gespeicherten Tools (nur mit dem entsprechenden Tarif).
- **Merkliste** — deine gespeicherten Tools (nur mit dem entsprechenden Tarif).
- **Papierkorb** — wiederherstellbare, gelöschte Tools (Premium/Enterprise).
- **Passwort ändern** — für lokale Konten direkt in toolr; für OIDC-Konten wird
die Passwortverwaltung im Identitätsanbieter angeboten.
@@ -51,7 +51,7 @@ API-Referenz: [`POST /auth/me/password`](/docs/reference/endpoints/auth#changeMy
- die **Listenansicht** und **Dichte** im Bereich Tools durchsuchen anpassen
(siehe [Tools finden & durchsuchen](/docs/handbook/tools-finden)).
Deine Präferenzen (inkl. Watchlist) werden im Endpunkt
Deine Präferenzen (inkl. Merkliste) werden im Endpunkt
[`GET /auth/me/preferences`](/docs/reference/endpoints/auth#getMePreferences)
gespeichert und über [`PUT /auth/me/preferences`](/docs/reference/endpoints/auth#updateMePreferences)
aktualisiert.
+2 -2
View File
@@ -13,7 +13,7 @@ Feature-Beschränkungen.
| Tarif | Beschreibung |
| --- | --- |
| **Free** | Grundfunktionen: suchen, filtern, ansehen, Analytics |
| **Premium** | Zusätzlich Watchlist, Vergleichen, Papierkorb, Kosten |
| **Premium** | Zusätzlich Merkliste, Vergleichen, Papierkorb, Kosten |
| **Enterprise** | Alle Premium-Features + erweiterter Support |
### Feature-Berechtigungen
@@ -23,7 +23,7 @@ Premium/Enterprise schalten folgende Features frei:
| Feature | Funktion | Mehr erfahren |
| --- | --- | --- |
| `compare` | Tools vergleichen | [Vergleichen](/docs/handbook/vergleichen) |
| `watchlist` | Favoritenliste | [Watchlist](/docs/handbook/watchlist) |
| `watchlist` | Favoritenliste | [Merkliste](/docs/handbook/watchlist) |
| `trash` | Papierkorb (soft gelöschte Tools) | [Papierkorb](/docs/handbook/papierkorb) |
| `costs` | Kosten-/Lizenzmodelle erfassen | [Kosten erfassen](/docs/handbook/kosten) |
+1 -1
View File
@@ -19,7 +19,7 @@ Ohne Eingabe zeigt die Palette:
- **Zuletzt angesehen** — die letzten 5 Tools, die du besucht hast.
- **Navigation** — Tools durchsuchen, Tool hinzufügen, Analytics sowie
(abhängig von Berechtigungen) Watchlist, Papierkorb und Admin.
(abhängig von Berechtigungen) Merkliste, Papierkorb und Admin.
### Suche
+2 -2
View File
@@ -58,12 +58,12 @@ In der Tabellenansicht sind die Spalten **Tool**, **Bewertung** und **Anzahl
Bewertungen** sortierbar. Beim Überfahren einer Zeile erscheint eine Vorschau
mit Bewertungsdetails, Tags und Mini-Balken.
## Auswählen für Vergleich & Watchlist
## Auswählen für Vergleich & Merkliste
- Auf jeder Karte/Zeile findest du ein **Vergleichs-Icon**, mit dem du Tools zur
[Vergleichsleiste](/docs/handbook/vergleichen) hinzufügst.
- Das **Lesezeichen-Icon** speichert Tools in deiner
[Watchlist](/docs/handbook/watchlist) (nur mit dem entsprechenden Tarif).
[Merkliste](/docs/handbook/watchlist) (nur mit dem entsprechenden Tarif).
## API
+10 -10
View File
@@ -1,14 +1,14 @@
---
title: Watchlist
title: Merkliste
order: 8
---
# Watchlist
# Merkliste
Die **Watchlist** ist eine persönliche Favoritenliste. Tools darin kannst du
Die **Merkliste** ist eine persönliche Favoritenliste. Tools darin kannst du
jederzeit per Klick wieder aufrufen und vergleichen.
> Die Watchlist ist ein **Premium-Feature** (Premium/Enterprise) und steht
> Die Merkliste ist ein **Premium-Feature** (Premium/Enterprise) und steht
> Admins immer zur Verfügung.
## Voraussetzung
@@ -21,18 +21,18 @@ beim Lesezeichen ein Hinweis auf den Tarifwechsel
- Auf jeder Karte/Zeile im Bereich **Tools durchsuchen** findest du das
**Lesezeichen-Icon**.
- Ein Klick speichert das Tool in deiner Watchlist — das Icon wird gefüllt.
- Ein Klick speichert das Tool in deiner Merkliste — das Icon wird gefüllt.
- Ein erneuter Klick entfernt es wieder.
## Watchlist ansehen
## Merkliste ansehen
Öffne die Watchlist über das Benutzermenü oder die Seitenleiste. Sie zeigt alle
Öffne die Merkliste über das Benutzermenü oder die Seitenleiste. Sie zeigt alle
gespeicherten Tools als Karten. Das gefüllte Lesezeichen auf einer Karte
entfernt das Tool aus der Liste.
## Wo wird die Watchlist gespeichert?
## Wo wird die Merkliste gespeichert?
Die Watchlist ist eine Liste von Tool-IDs in deinen **Benutzerpräferenzen**.
Die Merkliste ist eine Liste von Tool-IDs in deinen **Benutzerpräferenzen**.
Damit ist sie geräteübergreifend mit deinem Konto verbunden.
API-Referenz: [`GET /auth/me/watchlist`](/docs/reference/endpoints/auth#getMeWatchlist).
API-Referenz: [`GET /auth/me/watchlist`](/docs/reference/endpoints/auth#getMeMerkliste).
+1 -1
View File
@@ -6,7 +6,7 @@
- **Vollständige Dokumentations-Site** in mkdocs-Optik unter `/docs`:
- **Handbuch** mit verständlichen Erklärungen zu allen Features
(Erste Schritte, Tool anlegen, Bewertungen, Vergleichen, Watchlist,
(Erste Schritte, Tool anlegen, Bewertungen, Vergleichen, Merkliste,
Analytics, Administration, Datenmodell).
- **Automatisch generierte Referenz** aus `lib/api-spec/openapi.yaml`:
alle Endpunkte und Datenfelder (Typ, Pflichtstatus, Constraints) —
+1 -1
View File
@@ -12,7 +12,7 @@
und führt zur Standalone-Doku.
- **User Guide komplett überarbeitet**: 17 Handbuch-Seiten mit
Schritt-für-Schritt-Anleitungen für alle Funktionen (Tools finden, Tool
anlegen, Bewerten, Watchlist, Vergleichen, Kosten, Analytics, Pläne,
anlegen, Bewerten, Merkliste, Vergleichen, Kosten, Analytics, Pläne,
Administration, Redundanz, Papierkorb, Tastenkürzel, Datenmodell).
## Fixes & Verbesserungen
+1 -1
View File
@@ -10,7 +10,7 @@
(`/docs/vX.Y.Z/…`) — inklusive Handbuch und Referenz, wie sie zum
Release-Zeitpunkt galten. Die letzten 7 Versionen bleiben verfügbar.
- **Hilfe-Buttons in Formularen (NetBox-Stil)**: Bei Bewertung, Tool
anlegen/bearbeiten, Kosten, Vergleichen, Watchlist und Analytics gibt es
anlegen/bearbeiten, Kosten, Vergleichen, Merkliste und Analytics gibt es
jetzt ein Hilfe-Icon, das direkt zur passenden Anleitung im Handbuch führt.
## Fixes & Verbesserungen
+1 -1
View File
@@ -6,7 +6,7 @@
- **Komplette Lokalisierung der App (Deutsch/Englisch)**: Alle Seiten und
Komponenten sind jetzt übersetzt — Tool-Detailseite, Verwaltung, Papierkorb,
Redundanz-Dashboard, Vergleichen, Watchlist, Analytics, Tool anlegen und
Redundanz-Dashboard, Vergleichen, Merkliste, Analytics, Tool anlegen und
bearbeiten, Admin-Bereich inkl. Tools-Tab, Kategorie-Combobox und
Theme-Umschalter. Die Sprachauswahl oben rechts wechselt die gesamte
Oberfläche.