From 32eaba8532d1a33f67d99340f760dff375e482f9 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 1 Jul 2026 11:08:03 +0700 Subject: [PATCH 1/4] Add project dashboard with entry progress tracking. Wire the dev-only dashboard route and show dictionary progress toward 10k entries, including per-writing-system field completion with compact circular progress indicators. Co-authored-by: Cursor --- frontend/viewer/src/ShadcnProjectView.svelte | 4 + .../circular-progress.svelte | 61 +++++++ .../components/ui/circular-progress/index.ts | 7 + frontend/viewer/src/locales/en.po | 61 +++++++ frontend/viewer/src/locales/es.po | 61 +++++++ frontend/viewer/src/locales/fr.po | 61 +++++++ frontend/viewer/src/locales/id.po | 61 +++++++ frontend/viewer/src/locales/ko.po | 61 +++++++ frontend/viewer/src/locales/ms.po | 61 +++++++ frontend/viewer/src/locales/sw.po | 61 +++++++ frontend/viewer/src/locales/vi.po | 61 +++++++ .../project/dashboard/DashboardView.svelte | 169 ++++++++++++++++++ .../src/project/dashboard/dashboard-stats.ts | 148 +++++++++++++++ 13 files changed, 877 insertions(+) create mode 100644 frontend/viewer/src/lib/components/ui/circular-progress/circular-progress.svelte create mode 100644 frontend/viewer/src/lib/components/ui/circular-progress/index.ts create mode 100644 frontend/viewer/src/project/dashboard/DashboardView.svelte create mode 100644 frontend/viewer/src/project/dashboard/dashboard-stats.ts diff --git a/frontend/viewer/src/ShadcnProjectView.svelte b/frontend/viewer/src/ShadcnProjectView.svelte index 761fbb8e35..eb2b167e33 100644 --- a/frontend/viewer/src/ShadcnProjectView.svelte +++ b/frontend/viewer/src/ShadcnProjectView.svelte @@ -17,6 +17,7 @@ import ProjectSidebar from './project/ProjectSidebar.svelte'; import BrowseView from './project/browse/BrowseView.svelte'; import TasksView from './project/tasks/TasksView.svelte'; + import DashboardView from './project/dashboard/DashboardView.svelte'; import {initViewService} from '$lib/views/view-service.svelte'; import DialogsProvider from '$lib/DialogsProvider.svelte'; import {navigate, Route, useRouter} from 'svelte-routing'; @@ -70,6 +71,9 @@ + + + diff --git a/frontend/viewer/src/lib/components/ui/circular-progress/circular-progress.svelte b/frontend/viewer/src/lib/components/ui/circular-progress/circular-progress.svelte new file mode 100644 index 0000000000..e4a37313ae --- /dev/null +++ b/frontend/viewer/src/lib/components/ui/circular-progress/circular-progress.svelte @@ -0,0 +1,61 @@ + + +
+ + + + +
diff --git a/frontend/viewer/src/lib/components/ui/circular-progress/index.ts b/frontend/viewer/src/lib/components/ui/circular-progress/index.ts new file mode 100644 index 0000000000..6c1080af94 --- /dev/null +++ b/frontend/viewer/src/lib/components/ui/circular-progress/index.ts @@ -0,0 +1,7 @@ +import Root from './circular-progress.svelte'; + +export { + Root, + // + Root as CircularProgress, +}; diff --git a/frontend/viewer/src/locales/en.po b/frontend/viewer/src/locales/en.po index c31ad2ee21..87c3bead86 100644 --- a/frontend/viewer/src/locales/en.po +++ b/frontend/viewer/src/locales/en.po @@ -51,11 +51,23 @@ msgstr "{0} ago" msgid "{0} authors" msgstr "{0} authors" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} complete" +msgstr "{0} complete" + #. File size display with unit #: src/lib/components/audio/audio-editor.svelte msgid "{0} MB" msgstr "{0} MB" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} of {1} entries" +msgstr "{0} of {1} entries" + +#: src/project/dashboard/DashboardView.svelte +msgid "{0} remaining" +msgstr "{0} remaining" + #. Display name for server #: src/home/Server.svelte msgid "{0} Server" @@ -92,6 +104,10 @@ msgstr "**Fewer morpheme types** (only *Root*, *Bound Root*, *Stem*, *Bound Stem msgid "**Simpler terminology** (e.g. *Word* instead of *Lexeme form*, *Meaning* instead of *Sense*)" msgstr "**Simpler terminology** (e.g. *Word* instead of *Lexeme form*, *Meaning* instead of *Sense*)" +#: src/project/dashboard/DashboardView.svelte +msgid "10,000 entry goal" +msgstr "10,000 entry goal" + #. Update notification #: src/lib/notifications/NotificationOutlet.svelte msgid "A new version of FieldWorks Lite is available." @@ -220,6 +236,10 @@ msgstr "an entry" msgid "Analysis" msgstr "Analysis" +#: src/project/dashboard/DashboardView.svelte +msgid "Analysis writing systems" +msgstr "Analysis writing systems" + #. Relevant view: Classic #. Lite view equivalent: "Any part of speech" #. Filter option: clear grammatical info filter @@ -364,6 +384,7 @@ msgstr "Choose theme" #. Lite view equivalent: "Display as" #. Field label: form for citing in dictionary #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Citation form" msgstr "Citation form" @@ -532,9 +553,14 @@ msgstr "Dark" #. Sidebar menu item #: src/project/ProjectSidebar.svelte +#: src/project/dashboard/DashboardView.svelte msgid "Dashboard" msgstr "Dashboard" +#: src/project/dashboard/DashboardView.svelte +msgid "Dashboard failed to load" +msgstr "Dashboard failed to load" + #. Field label #: src/lib/troubleshoot/TroubleshootDialog.svelte msgid "Data Directory" @@ -544,6 +570,7 @@ msgstr "Data Directory" #. Definition for a sense/meaning (e.g., "bank" = "financial institution") #. Appears under each Sense/Meaning in entry editor #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Definition" msgstr "Definition" @@ -700,6 +727,14 @@ msgstr "Ends with" msgid "Entries missing..." msgstr "Entries missing..." +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with examples" +msgstr "Entries with examples" + +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with senses" +msgstr "Entries with senses" + #. EntryView label for the current dictionary object when FieldWorks Classic is shown #: src/project/browse/EntryView.svelte msgid "entry" @@ -768,6 +803,10 @@ msgstr "Example" msgid "Example sentence" msgstr "Example sentence" +#: src/project/dashboard/DashboardView.svelte +msgid "Example sentences" +msgstr "Example sentences" + #. Error message when copy fails #: src/lib/components/ui/button/copy-button.svelte msgid "Failed to copy to clipboard" @@ -974,6 +1013,7 @@ msgstr "Get support" #. Different from "Definition" (longer explanation) #. Appears under each sense/meaning; concise translation #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Gloss" msgstr "Gloss" @@ -1099,6 +1139,7 @@ msgstr "Lexbox logo" #. Lite view equivalent: "Word" #. Field label: the main headword/base form #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Lexeme form" msgstr "Lexeme form" @@ -1131,6 +1172,10 @@ msgstr "Literal meaning" msgid "Loading activity" msgstr "Loading activity" +#: src/project/dashboard/DashboardView.svelte +msgid "Loading dashboard..." +msgstr "Loading dashboard..." + #. Loading state text shown in the project/dictionary dropdown while the list of dictionaries is being fetched. #: src/project/ProjectDropdown.svelte msgid "Loading Dictionaries..." @@ -1324,6 +1369,10 @@ msgstr "No activity matches these filters" msgid "No activity types" msgstr "No activity types" +#: src/project/dashboard/DashboardView.svelte +msgid "No analysis writing systems configured." +msgstr "No analysis writing systems configured." + #. Placeholder when no audio file #. Shows in audio field when empty (no file uploaded yet) #: src/lib/components/field-editors/audio-input.svelte @@ -1384,6 +1433,10 @@ msgstr "No server configured" msgid "No subject, unable to create a new {0}" msgstr "No subject, unable to create a new {0}" +#: src/project/dashboard/DashboardView.svelte +msgid "No vernacular writing systems configured." +msgstr "No vernacular writing systems configured." + #. Relevant view: Lite #. Classic view equivalent: "No entries found" #: src/lib/entry-editor/EntryOrSensePicker.svelte @@ -2010,6 +2063,10 @@ msgstr "Toggle filters" msgid "Toggle pinned" msgstr "Toggle pinned" +#: src/project/dashboard/DashboardView.svelte +msgid "Total entries" +msgstr "Total entries" + #. Field label #: src/lib/entry-editor/object-editors/ExampleEditorPrimitive.svelte #: src/lib/views/entity-config.ts @@ -2164,6 +2221,10 @@ msgstr "Uses components as" msgid "Vernacular" msgstr "Vernacular" +#: src/project/dashboard/DashboardView.svelte +msgid "Vernacular writing systems" +msgstr "Vernacular writing systems" + #. Button label #: src/project/browse/ViewPicker.svelte msgid "View" diff --git a/frontend/viewer/src/locales/es.po b/frontend/viewer/src/locales/es.po index 20bb46208c..3854b75096 100644 --- a/frontend/viewer/src/locales/es.po +++ b/frontend/viewer/src/locales/es.po @@ -56,11 +56,23 @@ msgstr "{0} hace" msgid "{0} authors" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} complete" +msgstr "" + #. File size display with unit #: src/lib/components/audio/audio-editor.svelte msgid "{0} MB" msgstr "{0} MB" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} of {1} entries" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "{0} remaining" +msgstr "" + #. Display name for server #: src/home/Server.svelte msgid "{0} Server" @@ -97,6 +109,10 @@ msgstr "**Menos tipos de morfema** (sólo *Raíz*, *Raíz vinculado*, *Tallo*, * msgid "**Simpler terminology** (e.g. *Word* instead of *Lexeme form*, *Meaning* instead of *Sense*)" msgstr "**Terminología más simple** (p. ej. *Palabra* en lugar de *Forma del lexema*, *Significado* en lugar de *Acepción*)" +#: src/project/dashboard/DashboardView.svelte +msgid "10,000 entry goal" +msgstr "" + #. Update notification #: src/lib/notifications/NotificationOutlet.svelte msgid "A new version of FieldWorks Lite is available." @@ -225,6 +241,10 @@ msgstr "una entrada" msgid "Analysis" msgstr "Análisis" +#: src/project/dashboard/DashboardView.svelte +msgid "Analysis writing systems" +msgstr "" + #. Relevant view: Classic #. Lite view equivalent: "Any part of speech" #. Filter option: clear grammatical info filter @@ -369,6 +389,7 @@ msgstr "Elegir tema" #. Lite view equivalent: "Display as" #. Field label: form for citing in dictionary #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Citation form" msgstr "Formulario de cita" @@ -537,9 +558,14 @@ msgstr "Oscuro" #. Sidebar menu item #: src/project/ProjectSidebar.svelte +#: src/project/dashboard/DashboardView.svelte msgid "Dashboard" msgstr "Cuadro de mandos" +#: src/project/dashboard/DashboardView.svelte +msgid "Dashboard failed to load" +msgstr "" + #. Field label #: src/lib/troubleshoot/TroubleshootDialog.svelte msgid "Data Directory" @@ -549,6 +575,7 @@ msgstr "Directorio de datos" #. Definition for a sense/meaning (e.g., "bank" = "financial institution") #. Appears under each Sense/Meaning in entry editor #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Definition" msgstr "Definición" @@ -705,6 +732,14 @@ msgstr "Termina con" msgid "Entries missing..." msgstr "Entradas faltantes..." +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with examples" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with senses" +msgstr "" + #. EntryView label for the current dictionary object when FieldWorks Classic is shown #: src/project/browse/EntryView.svelte msgid "entry" @@ -773,6 +808,10 @@ msgstr "Ejemplo" msgid "Example sentence" msgstr "Ejemplo de frase" +#: src/project/dashboard/DashboardView.svelte +msgid "Example sentences" +msgstr "" + #. Error message when copy fails #: src/lib/components/ui/button/copy-button.svelte msgid "Failed to copy to clipboard" @@ -979,6 +1018,7 @@ msgstr "Obtener soporte" #. Different from "Definition" (longer explanation) #. Appears under each sense/meaning; concise translation #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Gloss" msgstr "Glosa" @@ -1104,6 +1144,7 @@ msgstr "Logotipo de Lexbox" #. Lite view equivalent: "Word" #. Field label: the main headword/base form #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Lexeme form" msgstr "Forma del lexema" @@ -1136,6 +1177,10 @@ msgstr "Significado literal" msgid "Loading activity" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "Loading dashboard..." +msgstr "" + #. Loading state text shown in the project/dictionary dropdown while the list of dictionaries is being fetched. #: src/project/ProjectDropdown.svelte msgid "Loading Dictionaries..." @@ -1329,6 +1374,10 @@ msgstr "" msgid "No activity types" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "No analysis writing systems configured." +msgstr "" + #. Placeholder when no audio file #. Shows in audio field when empty (no file uploaded yet) #: src/lib/components/field-editors/audio-input.svelte @@ -1389,6 +1438,10 @@ msgstr "Ningún servidor configurado" msgid "No subject, unable to create a new {0}" msgstr "No hay tema, no se puede crear un nuevo {0}" +#: src/project/dashboard/DashboardView.svelte +msgid "No vernacular writing systems configured." +msgstr "" + #. Relevant view: Lite #. Classic view equivalent: "No entries found" #: src/lib/entry-editor/EntryOrSensePicker.svelte @@ -2015,6 +2068,10 @@ msgstr "Alternar filtros" msgid "Toggle pinned" msgstr "Alternar fijado" +#: src/project/dashboard/DashboardView.svelte +msgid "Total entries" +msgstr "" + #. Field label #: src/lib/entry-editor/object-editors/ExampleEditorPrimitive.svelte #: src/lib/views/entity-config.ts @@ -2169,6 +2226,10 @@ msgstr "Utiliza componentes como" msgid "Vernacular" msgstr "Vernáculo" +#: src/project/dashboard/DashboardView.svelte +msgid "Vernacular writing systems" +msgstr "" + #. Button label #: src/project/browse/ViewPicker.svelte msgid "View" diff --git a/frontend/viewer/src/locales/fr.po b/frontend/viewer/src/locales/fr.po index 205ebf064e..62a4614305 100644 --- a/frontend/viewer/src/locales/fr.po +++ b/frontend/viewer/src/locales/fr.po @@ -56,11 +56,23 @@ msgstr "il y a {0}" msgid "{0} authors" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} complete" +msgstr "" + #. File size display with unit #: src/lib/components/audio/audio-editor.svelte msgid "{0} MB" msgstr "{0} MB" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} of {1} entries" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "{0} remaining" +msgstr "" + #. Display name for server #: src/home/Server.svelte msgid "{0} Server" @@ -97,6 +109,10 @@ msgstr "**Moins de types de morphèmes** (seulement *Racine*, *Racines liées*, msgid "**Simpler terminology** (e.g. *Word* instead of *Lexeme form*, *Meaning* instead of *Sense*)" msgstr "**terminologie plus simple** (par exemple *Mot* au lieu de *forme de lexème*, *Signification* au lieu de *Sense*)" +#: src/project/dashboard/DashboardView.svelte +msgid "10,000 entry goal" +msgstr "" + #. Update notification #: src/lib/notifications/NotificationOutlet.svelte msgid "A new version of FieldWorks Lite is available." @@ -225,6 +241,10 @@ msgstr "une entrée" msgid "Analysis" msgstr "Analyse" +#: src/project/dashboard/DashboardView.svelte +msgid "Analysis writing systems" +msgstr "" + #. Relevant view: Classic #. Lite view equivalent: "Any part of speech" #. Filter option: clear grammatical info filter @@ -369,6 +389,7 @@ msgstr "Choisir un thème" #. Lite view equivalent: "Display as" #. Field label: form for citing in dictionary #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Citation form" msgstr "Autonyme" @@ -537,9 +558,14 @@ msgstr "Sombre" #. Sidebar menu item #: src/project/ProjectSidebar.svelte +#: src/project/dashboard/DashboardView.svelte msgid "Dashboard" msgstr "Tableau de bord" +#: src/project/dashboard/DashboardView.svelte +msgid "Dashboard failed to load" +msgstr "" + #. Field label #: src/lib/troubleshoot/TroubleshootDialog.svelte msgid "Data Directory" @@ -549,6 +575,7 @@ msgstr "Répertoire de données" #. Definition for a sense/meaning (e.g., "bank" = "financial institution") #. Appears under each Sense/Meaning in entry editor #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Definition" msgstr "Définition" @@ -705,6 +732,14 @@ msgstr "Se termine par" msgid "Entries missing..." msgstr "Entrées manquantes..." +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with examples" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with senses" +msgstr "" + #. EntryView label for the current dictionary object when FieldWorks Classic is shown #: src/project/browse/EntryView.svelte msgid "entry" @@ -773,6 +808,10 @@ msgstr "Exemple" msgid "Example sentence" msgstr "Phrase exemplaire" +#: src/project/dashboard/DashboardView.svelte +msgid "Example sentences" +msgstr "" + #. Error message when copy fails #: src/lib/components/ui/button/copy-button.svelte msgid "Failed to copy to clipboard" @@ -979,6 +1018,7 @@ msgstr "Obtenir de l'aide" #. Different from "Definition" (longer explanation) #. Appears under each sense/meaning; concise translation #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Gloss" msgstr "Glose" @@ -1104,6 +1144,7 @@ msgstr "Logo Lexbox" #. Lite view equivalent: "Word" #. Field label: the main headword/base form #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Lexeme form" msgstr "Forme de lexème" @@ -1136,6 +1177,10 @@ msgstr "Sens littéral" msgid "Loading activity" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "Loading dashboard..." +msgstr "" + #. Loading state text shown in the project/dictionary dropdown while the list of dictionaries is being fetched. #: src/project/ProjectDropdown.svelte msgid "Loading Dictionaries..." @@ -1329,6 +1374,10 @@ msgstr "" msgid "No activity types" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "No analysis writing systems configured." +msgstr "" + #. Placeholder when no audio file #. Shows in audio field when empty (no file uploaded yet) #: src/lib/components/field-editors/audio-input.svelte @@ -1389,6 +1438,10 @@ msgstr "Pas de serveur configuré" msgid "No subject, unable to create a new {0}" msgstr "Aucun sujet, impossible de créer un nouveau {0}" +#: src/project/dashboard/DashboardView.svelte +msgid "No vernacular writing systems configured." +msgstr "" + #. Relevant view: Lite #. Classic view equivalent: "No entries found" #: src/lib/entry-editor/EntryOrSensePicker.svelte @@ -2015,6 +2068,10 @@ msgstr "Basculer les filtres" msgid "Toggle pinned" msgstr "Activer/désactiver épinglé" +#: src/project/dashboard/DashboardView.svelte +msgid "Total entries" +msgstr "" + #. Field label #: src/lib/entry-editor/object-editors/ExampleEditorPrimitive.svelte #: src/lib/views/entity-config.ts @@ -2169,6 +2226,10 @@ msgstr "Utilise des composants comme" msgid "Vernacular" msgstr "Vernaculaire" +#: src/project/dashboard/DashboardView.svelte +msgid "Vernacular writing systems" +msgstr "" + #. Button label #: src/project/browse/ViewPicker.svelte msgid "View" diff --git a/frontend/viewer/src/locales/id.po b/frontend/viewer/src/locales/id.po index 7e3085eb37..675b240472 100644 --- a/frontend/viewer/src/locales/id.po +++ b/frontend/viewer/src/locales/id.po @@ -56,11 +56,23 @@ msgstr "{0} yang lalu" msgid "{0} authors" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} complete" +msgstr "" + #. File size display with unit #: src/lib/components/audio/audio-editor.svelte msgid "{0} MB" msgstr "{0} MB" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} of {1} entries" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "{0} remaining" +msgstr "" + #. Display name for server #: src/home/Server.svelte msgid "{0} Server" @@ -97,6 +109,10 @@ msgstr "**Jenis morfem yang lebih sedikit** (hanya *Root*, *Bound Root*, *Stem*, msgid "**Simpler terminology** (e.g. *Word* instead of *Lexeme form*, *Meaning* instead of *Sense*)" msgstr "**Terminologi yang lebih sederhana** (mis. *Kata* alih-alih *Bentuk leksem*, *Makna* alih-alih *Rasa*)" +#: src/project/dashboard/DashboardView.svelte +msgid "10,000 entry goal" +msgstr "" + #. Update notification #: src/lib/notifications/NotificationOutlet.svelte msgid "A new version of FieldWorks Lite is available." @@ -225,6 +241,10 @@ msgstr "sebuah entri" msgid "Analysis" msgstr "Analisis" +#: src/project/dashboard/DashboardView.svelte +msgid "Analysis writing systems" +msgstr "" + #. Relevant view: Classic #. Lite view equivalent: "Any part of speech" #. Filter option: clear grammatical info filter @@ -369,6 +389,7 @@ msgstr "Pilih tema" #. Lite view equivalent: "Display as" #. Field label: form for citing in dictionary #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Citation form" msgstr "Formulir kutipan" @@ -537,9 +558,14 @@ msgstr "Gelap" #. Sidebar menu item #: src/project/ProjectSidebar.svelte +#: src/project/dashboard/DashboardView.svelte msgid "Dashboard" msgstr "Dasbor" +#: src/project/dashboard/DashboardView.svelte +msgid "Dashboard failed to load" +msgstr "" + #. Field label #: src/lib/troubleshoot/TroubleshootDialog.svelte msgid "Data Directory" @@ -549,6 +575,7 @@ msgstr "Direktori Data" #. Definition for a sense/meaning (e.g., "bank" = "financial institution") #. Appears under each Sense/Meaning in entry editor #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Definition" msgstr "Definisi" @@ -705,6 +732,14 @@ msgstr "Diakhiri dengan" msgid "Entries missing..." msgstr "Entri yang hilang..." +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with examples" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with senses" +msgstr "" + #. EntryView label for the current dictionary object when FieldWorks Classic is shown #: src/project/browse/EntryView.svelte msgid "entry" @@ -773,6 +808,10 @@ msgstr "Contoh" msgid "Example sentence" msgstr "Contoh kalimat" +#: src/project/dashboard/DashboardView.svelte +msgid "Example sentences" +msgstr "" + #. Error message when copy fails #: src/lib/components/ui/button/copy-button.svelte msgid "Failed to copy to clipboard" @@ -979,6 +1018,7 @@ msgstr "Dapatkan dukungan" #. Different from "Definition" (longer explanation) #. Appears under each sense/meaning; concise translation #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Gloss" msgstr "Arti Singkat" @@ -1104,6 +1144,7 @@ msgstr "Logo Lexbox" #. Lite view equivalent: "Word" #. Field label: the main headword/base form #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Lexeme form" msgstr "Bentuk leksem" @@ -1136,6 +1177,10 @@ msgstr "Arti harfiah" msgid "Loading activity" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "Loading dashboard..." +msgstr "" + #. Loading state text shown in the project/dictionary dropdown while the list of dictionaries is being fetched. #: src/project/ProjectDropdown.svelte msgid "Loading Dictionaries..." @@ -1329,6 +1374,10 @@ msgstr "" msgid "No activity types" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "No analysis writing systems configured." +msgstr "" + #. Placeholder when no audio file #. Shows in audio field when empty (no file uploaded yet) #: src/lib/components/field-editors/audio-input.svelte @@ -1389,6 +1438,10 @@ msgstr "Tidak ada server yang dikonfigurasi" msgid "No subject, unable to create a new {0}" msgstr "Tidak ada subjek, tidak dapat membuat {0}baru" +#: src/project/dashboard/DashboardView.svelte +msgid "No vernacular writing systems configured." +msgstr "" + #. Relevant view: Lite #. Classic view equivalent: "No entries found" #: src/lib/entry-editor/EntryOrSensePicker.svelte @@ -2015,6 +2068,10 @@ msgstr "Beralih filter" msgid "Toggle pinned" msgstr "Beralih disematkan" +#: src/project/dashboard/DashboardView.svelte +msgid "Total entries" +msgstr "" + #. Field label #: src/lib/entry-editor/object-editors/ExampleEditorPrimitive.svelte #: src/lib/views/entity-config.ts @@ -2169,6 +2226,10 @@ msgstr "Menggunakan komponen sebagai" msgid "Vernacular" msgstr "Vernakular" +#: src/project/dashboard/DashboardView.svelte +msgid "Vernacular writing systems" +msgstr "" + #. Button label #: src/project/browse/ViewPicker.svelte msgid "View" diff --git a/frontend/viewer/src/locales/ko.po b/frontend/viewer/src/locales/ko.po index 6b92e1006a..90364d1d98 100644 --- a/frontend/viewer/src/locales/ko.po +++ b/frontend/viewer/src/locales/ko.po @@ -56,11 +56,23 @@ msgstr "{0} 전" msgid "{0} authors" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} complete" +msgstr "" + #. File size display with unit #: src/lib/components/audio/audio-editor.svelte msgid "{0} MB" msgstr "{0} MB" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} of {1} entries" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "{0} remaining" +msgstr "" + #. Display name for server #: src/home/Server.svelte msgid "{0} Server" @@ -97,6 +109,10 @@ msgstr "**형태소 유형 감소** (*뿌리*, *결합된 뿌리*, *줄기*, * msgid "**Simpler terminology** (e.g. *Word* instead of *Lexeme form*, *Meaning* instead of *Sense*)" msgstr "**간단한 용어 사용** (예: *어휘 형태* 대신 *단어*, *의미* 대신 *의미*)" +#: src/project/dashboard/DashboardView.svelte +msgid "10,000 entry goal" +msgstr "" + #. Update notification #: src/lib/notifications/NotificationOutlet.svelte msgid "A new version of FieldWorks Lite is available." @@ -225,6 +241,10 @@ msgstr "항목" msgid "Analysis" msgstr "분석" +#: src/project/dashboard/DashboardView.svelte +msgid "Analysis writing systems" +msgstr "" + #. Relevant view: Classic #. Lite view equivalent: "Any part of speech" #. Filter option: clear grammatical info filter @@ -369,6 +389,7 @@ msgstr "테마 선택" #. Lite view equivalent: "Display as" #. Field label: form for citing in dictionary #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Citation form" msgstr "인용 양식" @@ -537,9 +558,14 @@ msgstr "Dark" #. Sidebar menu item #: src/project/ProjectSidebar.svelte +#: src/project/dashboard/DashboardView.svelte msgid "Dashboard" msgstr "대시보드" +#: src/project/dashboard/DashboardView.svelte +msgid "Dashboard failed to load" +msgstr "" + #. Field label #: src/lib/troubleshoot/TroubleshootDialog.svelte msgid "Data Directory" @@ -549,6 +575,7 @@ msgstr "데이터 디렉토리" #. Definition for a sense/meaning (e.g., "bank" = "financial institution") #. Appears under each Sense/Meaning in entry editor #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Definition" msgstr "정의" @@ -705,6 +732,14 @@ msgstr "로 끝납니다." msgid "Entries missing..." msgstr "항목이 누락되었습니다..." +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with examples" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with senses" +msgstr "" + #. EntryView label for the current dictionary object when FieldWorks Classic is shown #: src/project/browse/EntryView.svelte msgid "entry" @@ -773,6 +808,10 @@ msgstr "예" msgid "Example sentence" msgstr "문장 예시" +#: src/project/dashboard/DashboardView.svelte +msgid "Example sentences" +msgstr "" + #. Error message when copy fails #: src/lib/components/ui/button/copy-button.svelte msgid "Failed to copy to clipboard" @@ -979,6 +1018,7 @@ msgstr "지원 받기" #. Different from "Definition" (longer explanation) #. Appears under each sense/meaning; concise translation #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Gloss" msgstr "광택" @@ -1104,6 +1144,7 @@ msgstr "렉스박스 로고" #. Lite view equivalent: "Word" #. Field label: the main headword/base form #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Lexeme form" msgstr "Lexeme 양식" @@ -1136,6 +1177,10 @@ msgstr "문자 그대로의 의미" msgid "Loading activity" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "Loading dashboard..." +msgstr "" + #. Loading state text shown in the project/dictionary dropdown while the list of dictionaries is being fetched. #: src/project/ProjectDropdown.svelte msgid "Loading Dictionaries..." @@ -1329,6 +1374,10 @@ msgstr "" msgid "No activity types" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "No analysis writing systems configured." +msgstr "" + #. Placeholder when no audio file #. Shows in audio field when empty (no file uploaded yet) #: src/lib/components/field-editors/audio-input.svelte @@ -1389,6 +1438,10 @@ msgstr "구성된 서버 없음" msgid "No subject, unable to create a new {0}" msgstr "제목이 없습니다, 새로 만들 수 없습니다 {0}" +#: src/project/dashboard/DashboardView.svelte +msgid "No vernacular writing systems configured." +msgstr "" + #. Relevant view: Lite #. Classic view equivalent: "No entries found" #: src/lib/entry-editor/EntryOrSensePicker.svelte @@ -2015,6 +2068,10 @@ msgstr "필터 토글" msgid "Toggle pinned" msgstr "고정 토글" +#: src/project/dashboard/DashboardView.svelte +msgid "Total entries" +msgstr "" + #. Field label #: src/lib/entry-editor/object-editors/ExampleEditorPrimitive.svelte #: src/lib/views/entity-config.ts @@ -2169,6 +2226,10 @@ msgstr "구성 요소를 다음과 같이 사용합니다." msgid "Vernacular" msgstr "토착어" +#: src/project/dashboard/DashboardView.svelte +msgid "Vernacular writing systems" +msgstr "" + #. Button label #: src/project/browse/ViewPicker.svelte msgid "View" diff --git a/frontend/viewer/src/locales/ms.po b/frontend/viewer/src/locales/ms.po index 41e2547ffd..99ce1bd2b6 100644 --- a/frontend/viewer/src/locales/ms.po +++ b/frontend/viewer/src/locales/ms.po @@ -56,11 +56,23 @@ msgstr "{0} yang lalu" msgid "{0} authors" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} complete" +msgstr "" + #. File size display with unit #: src/lib/components/audio/audio-editor.svelte msgid "{0} MB" msgstr "{0} MB" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} of {1} entries" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "{0} remaining" +msgstr "" + #. Display name for server #: src/home/Server.svelte msgid "{0} Server" @@ -97,6 +109,10 @@ msgstr "**Lebih sedikit jenis morfem** (hanya *Akar*, *Akar Terikat*, *Batang*, msgid "**Simpler terminology** (e.g. *Word* instead of *Lexeme form*, *Meaning* instead of *Sense*)" msgstr "**Terminologi lebih mudah** (cth. *Perkataan* bukan *bentuk leksem*, *Makna* bukan *Sense*)" +#: src/project/dashboard/DashboardView.svelte +msgid "10,000 entry goal" +msgstr "" + #. Update notification #: src/lib/notifications/NotificationOutlet.svelte msgid "A new version of FieldWorks Lite is available." @@ -225,6 +241,10 @@ msgstr "satu entri" msgid "Analysis" msgstr "Analisis" +#: src/project/dashboard/DashboardView.svelte +msgid "Analysis writing systems" +msgstr "" + #. Relevant view: Classic #. Lite view equivalent: "Any part of speech" #. Filter option: clear grammatical info filter @@ -369,6 +389,7 @@ msgstr "Pilih tema" #. Lite view equivalent: "Display as" #. Field label: form for citing in dictionary #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Citation form" msgstr "Bentuk petikan" @@ -537,9 +558,14 @@ msgstr "Gelap" #. Sidebar menu item #: src/project/ProjectSidebar.svelte +#: src/project/dashboard/DashboardView.svelte msgid "Dashboard" msgstr "Papan Pemuka" +#: src/project/dashboard/DashboardView.svelte +msgid "Dashboard failed to load" +msgstr "" + #. Field label #: src/lib/troubleshoot/TroubleshootDialog.svelte msgid "Data Directory" @@ -549,6 +575,7 @@ msgstr "Direktori Data" #. Definition for a sense/meaning (e.g., "bank" = "financial institution") #. Appears under each Sense/Meaning in entry editor #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Definition" msgstr "Definisi" @@ -705,6 +732,14 @@ msgstr "Berakhir dengan" msgid "Entries missing..." msgstr "Entri yang hilang..." +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with examples" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with senses" +msgstr "" + #. EntryView label for the current dictionary object when FieldWorks Classic is shown #: src/project/browse/EntryView.svelte msgid "entry" @@ -773,6 +808,10 @@ msgstr "Contoh" msgid "Example sentence" msgstr "Ayat contoh" +#: src/project/dashboard/DashboardView.svelte +msgid "Example sentences" +msgstr "" + #. Error message when copy fails #: src/lib/components/ui/button/copy-button.svelte msgid "Failed to copy to clipboard" @@ -979,6 +1018,7 @@ msgstr "Dapatkan sokongan" #. Different from "Definition" (longer explanation) #. Appears under each sense/meaning; concise translation #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Gloss" msgstr "Glos" @@ -1104,6 +1144,7 @@ msgstr "Logo Lexbox" #. Lite view equivalent: "Word" #. Field label: the main headword/base form #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Lexeme form" msgstr "Bentuk leksem" @@ -1136,6 +1177,10 @@ msgstr "Makna harfiah" msgid "Loading activity" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "Loading dashboard..." +msgstr "" + #. Loading state text shown in the project/dictionary dropdown while the list of dictionaries is being fetched. #: src/project/ProjectDropdown.svelte msgid "Loading Dictionaries..." @@ -1329,6 +1374,10 @@ msgstr "" msgid "No activity types" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "No analysis writing systems configured." +msgstr "" + #. Placeholder when no audio file #. Shows in audio field when empty (no file uploaded yet) #: src/lib/components/field-editors/audio-input.svelte @@ -1389,6 +1438,10 @@ msgstr "Tiada pelayan dikonfigurasikan" msgid "No subject, unable to create a new {0}" msgstr "Tiada subjek, tidak dapat membuat {0} baru" +#: src/project/dashboard/DashboardView.svelte +msgid "No vernacular writing systems configured." +msgstr "" + #. Relevant view: Lite #. Classic view equivalent: "No entries found" #: src/lib/entry-editor/EntryOrSensePicker.svelte @@ -2015,6 +2068,10 @@ msgstr "Togol penapis" msgid "Toggle pinned" msgstr "Togol disemat" +#: src/project/dashboard/DashboardView.svelte +msgid "Total entries" +msgstr "" + #. Field label #: src/lib/entry-editor/object-editors/ExampleEditorPrimitive.svelte #: src/lib/views/entity-config.ts @@ -2169,6 +2226,10 @@ msgstr "Menggunakan komponen sebagai" msgid "Vernacular" msgstr "Bahasaan" +#: src/project/dashboard/DashboardView.svelte +msgid "Vernacular writing systems" +msgstr "" + #. Button label #: src/project/browse/ViewPicker.svelte msgid "View" diff --git a/frontend/viewer/src/locales/sw.po b/frontend/viewer/src/locales/sw.po index 2bf6258491..c6aec6f43e 100644 --- a/frontend/viewer/src/locales/sw.po +++ b/frontend/viewer/src/locales/sw.po @@ -56,11 +56,23 @@ msgstr "{0} iliyopita" msgid "{0} authors" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} complete" +msgstr "" + #. File size display with unit #: src/lib/components/audio/audio-editor.svelte msgid "{0} MB" msgstr "{0} MB" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} of {1} entries" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "{0} remaining" +msgstr "" + #. Display name for server #: src/home/Server.svelte msgid "{0} Server" @@ -97,6 +109,10 @@ msgstr "**Aina chache za morifemi** (tu *Root*, *Bound Root*, *Stem*, *Bound Ste msgid "**Simpler terminology** (e.g. *Word* instead of *Lexeme form*, *Meaning* instead of *Sense*)" msgstr "**Istilahi rahisi** (kwa mfano *Neno* badala ya *Lexeme form*, *Maana* badala ya *Sense*)" +#: src/project/dashboard/DashboardView.svelte +msgid "10,000 entry goal" +msgstr "" + #. Update notification #: src/lib/notifications/NotificationOutlet.svelte msgid "A new version of FieldWorks Lite is available." @@ -225,6 +241,10 @@ msgstr "ingizo" msgid "Analysis" msgstr "Uchambuzi" +#: src/project/dashboard/DashboardView.svelte +msgid "Analysis writing systems" +msgstr "" + #. Relevant view: Classic #. Lite view equivalent: "Any part of speech" #. Filter option: clear grammatical info filter @@ -369,6 +389,7 @@ msgstr "Chagua mandhari" #. Lite view equivalent: "Display as" #. Field label: form for citing in dictionary #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Citation form" msgstr "Fomu ya Manukuu" @@ -537,9 +558,14 @@ msgstr "Giza" #. Sidebar menu item #: src/project/ProjectSidebar.svelte +#: src/project/dashboard/DashboardView.svelte msgid "Dashboard" msgstr "Dashibodi" +#: src/project/dashboard/DashboardView.svelte +msgid "Dashboard failed to load" +msgstr "" + #. Field label #: src/lib/troubleshoot/TroubleshootDialog.svelte msgid "Data Directory" @@ -549,6 +575,7 @@ msgstr "Faharasa ya Data" #. Definition for a sense/meaning (e.g., "bank" = "financial institution") #. Appears under each Sense/Meaning in entry editor #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Definition" msgstr "Maana" @@ -705,6 +732,14 @@ msgstr "Inaishia na" msgid "Entries missing..." msgstr "Ingizo linalokosa..." +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with examples" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with senses" +msgstr "" + #. EntryView label for the current dictionary object when FieldWorks Classic is shown #: src/project/browse/EntryView.svelte msgid "entry" @@ -773,6 +808,10 @@ msgstr "Mfano" msgid "Example sentence" msgstr "Sentensi ya mfano" +#: src/project/dashboard/DashboardView.svelte +msgid "Example sentences" +msgstr "" + #. Error message when copy fails #: src/lib/components/ui/button/copy-button.svelte msgid "Failed to copy to clipboard" @@ -979,6 +1018,7 @@ msgstr "Pata msaada" #. Different from "Definition" (longer explanation) #. Appears under each sense/meaning; concise translation #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Gloss" msgstr "Glosi" @@ -1104,6 +1144,7 @@ msgstr "Nembo ya Lexbox" #. Lite view equivalent: "Word" #. Field label: the main headword/base form #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Lexeme form" msgstr "Fomu ya Neno" @@ -1136,6 +1177,10 @@ msgstr "Maana halisi" msgid "Loading activity" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "Loading dashboard..." +msgstr "" + #. Loading state text shown in the project/dictionary dropdown while the list of dictionaries is being fetched. #: src/project/ProjectDropdown.svelte msgid "Loading Dictionaries..." @@ -1329,6 +1374,10 @@ msgstr "" msgid "No activity types" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "No analysis writing systems configured." +msgstr "" + #. Placeholder when no audio file #. Shows in audio field when empty (no file uploaded yet) #: src/lib/components/field-editors/audio-input.svelte @@ -1389,6 +1438,10 @@ msgstr "Hakuna seva iliyotengwa" msgid "No subject, unable to create a new {0}" msgstr "Hakuna kigezo, haiwezi kutengeneza {0} mpya" +#: src/project/dashboard/DashboardView.svelte +msgid "No vernacular writing systems configured." +msgstr "" + #. Relevant view: Lite #. Classic view equivalent: "No entries found" #: src/lib/entry-editor/EntryOrSensePicker.svelte @@ -2015,6 +2068,10 @@ msgstr "Geuza kichujio" msgid "Toggle pinned" msgstr "Geuza iliyobandikwa" +#: src/project/dashboard/DashboardView.svelte +msgid "Total entries" +msgstr "" + #. Field label #: src/lib/entry-editor/object-editors/ExampleEditorPrimitive.svelte #: src/lib/views/entity-config.ts @@ -2169,6 +2226,10 @@ msgstr "Inatumia sehemu kama" msgid "Vernacular" msgstr "Lugha ya kienyeji" +#: src/project/dashboard/DashboardView.svelte +msgid "Vernacular writing systems" +msgstr "" + #. Button label #: src/project/browse/ViewPicker.svelte msgid "View" diff --git a/frontend/viewer/src/locales/vi.po b/frontend/viewer/src/locales/vi.po index fe3b137341..223334a677 100644 --- a/frontend/viewer/src/locales/vi.po +++ b/frontend/viewer/src/locales/vi.po @@ -56,11 +56,23 @@ msgstr "{0} trước" msgid "{0} authors" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} complete" +msgstr "" + #. File size display with unit #: src/lib/components/audio/audio-editor.svelte msgid "{0} MB" msgstr "{0} MB" +#: src/project/dashboard/DashboardView.svelte +msgid "{0} of {1} entries" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "{0} remaining" +msgstr "" + #. Display name for server #: src/home/Server.svelte msgid "{0} Server" @@ -97,6 +109,10 @@ msgstr "**Ít loại môrphêm hơn** (chỉ *Gốc*, *Gốc liên kết*, *Thâ msgid "**Simpler terminology** (e.g. *Word* instead of *Lexeme form*, *Meaning* instead of *Sense*)" msgstr "**Thuật ngữ đơn giản hơn** (ví dụ *Từ* thay vì *Hình thức lexeme*, *Ý nghĩa* thay vì *Sense*)" +#: src/project/dashboard/DashboardView.svelte +msgid "10,000 entry goal" +msgstr "" + #. Update notification #: src/lib/notifications/NotificationOutlet.svelte msgid "A new version of FieldWorks Lite is available." @@ -225,6 +241,10 @@ msgstr "một mục" msgid "Analysis" msgstr "Phân tích" +#: src/project/dashboard/DashboardView.svelte +msgid "Analysis writing systems" +msgstr "" + #. Relevant view: Classic #. Lite view equivalent: "Any part of speech" #. Filter option: clear grammatical info filter @@ -369,6 +389,7 @@ msgstr "Chọn chủ đề" #. Lite view equivalent: "Display as" #. Field label: form for citing in dictionary #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Citation form" msgstr "Hình thức trích dẫn" @@ -537,9 +558,14 @@ msgstr "Tối" #. Sidebar menu item #: src/project/ProjectSidebar.svelte +#: src/project/dashboard/DashboardView.svelte msgid "Dashboard" msgstr "Bảng điều khiển" +#: src/project/dashboard/DashboardView.svelte +msgid "Dashboard failed to load" +msgstr "" + #. Field label #: src/lib/troubleshoot/TroubleshootDialog.svelte msgid "Data Directory" @@ -549,6 +575,7 @@ msgstr "Thư mục dữ liệu" #. Definition for a sense/meaning (e.g., "bank" = "financial institution") #. Appears under each Sense/Meaning in entry editor #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Definition" msgstr "Định nghĩa" @@ -705,6 +732,14 @@ msgstr "Kết thúc bằng" msgid "Entries missing..." msgstr "Các mục còn thiếu..." +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with examples" +msgstr "" + +#: src/project/dashboard/DashboardView.svelte +msgid "Entries with senses" +msgstr "" + #. EntryView label for the current dictionary object when FieldWorks Classic is shown #: src/project/browse/EntryView.svelte msgid "entry" @@ -773,6 +808,10 @@ msgstr "Ví dụ" msgid "Example sentence" msgstr "Câu ví dụ" +#: src/project/dashboard/DashboardView.svelte +msgid "Example sentences" +msgstr "" + #. Error message when copy fails #: src/lib/components/ui/button/copy-button.svelte msgid "Failed to copy to clipboard" @@ -979,6 +1018,7 @@ msgstr "Nhận hỗ trợ" #. Different from "Definition" (longer explanation) #. Appears under each sense/meaning; concise translation #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Gloss" msgstr "Ghi chú" @@ -1104,6 +1144,7 @@ msgstr "Logo Lexbox" #. Lite view equivalent: "Word" #. Field label: the main headword/base form #: src/lib/views/entity-config.ts +#: src/project/dashboard/DashboardView.svelte msgid "Lexeme form" msgstr "Hình thức lexeme" @@ -1136,6 +1177,10 @@ msgstr "Ý nghĩa đen" msgid "Loading activity" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "Loading dashboard..." +msgstr "" + #. Loading state text shown in the project/dictionary dropdown while the list of dictionaries is being fetched. #: src/project/ProjectDropdown.svelte msgid "Loading Dictionaries..." @@ -1329,6 +1374,10 @@ msgstr "" msgid "No activity types" msgstr "" +#: src/project/dashboard/DashboardView.svelte +msgid "No analysis writing systems configured." +msgstr "" + #. Placeholder when no audio file #. Shows in audio field when empty (no file uploaded yet) #: src/lib/components/field-editors/audio-input.svelte @@ -1389,6 +1438,10 @@ msgstr "Chưa cấu hình máy chủ" msgid "No subject, unable to create a new {0}" msgstr "Không có chủ đề, không thể tạo mới {0}" +#: src/project/dashboard/DashboardView.svelte +msgid "No vernacular writing systems configured." +msgstr "" + #. Relevant view: Lite #. Classic view equivalent: "No entries found" #: src/lib/entry-editor/EntryOrSensePicker.svelte @@ -2015,6 +2068,10 @@ msgstr "Chuyển đổi bộ lọc" msgid "Toggle pinned" msgstr "Chuyển trạng thái ghim" +#: src/project/dashboard/DashboardView.svelte +msgid "Total entries" +msgstr "" + #. Field label #: src/lib/entry-editor/object-editors/ExampleEditorPrimitive.svelte #: src/lib/views/entity-config.ts @@ -2169,6 +2226,10 @@ msgstr "Sử dụng các thành phần như" msgid "Vernacular" msgstr "Tiếng địa phương" +#: src/project/dashboard/DashboardView.svelte +msgid "Vernacular writing systems" +msgstr "" + #. Button label #: src/project/browse/ViewPicker.svelte msgid "View" diff --git a/frontend/viewer/src/project/dashboard/DashboardView.svelte b/frontend/viewer/src/project/dashboard/DashboardView.svelte new file mode 100644 index 0000000000..f84b67565b --- /dev/null +++ b/frontend/viewer/src/project/dashboard/DashboardView.svelte @@ -0,0 +1,169 @@ + + +{#snippet fieldStatRow( + label: string, + field: FieldCompletion, + total: number, + wsColorClass: string, +)} +
+ {label} + {formatCount(field, total)} +
+ +
+
+{/snippet} + +
+
+ +

{$t`Dashboard`}

+
+ + + {#if dashboard} + {@const totalFormatted = formatNumber(dashboard.totalEntries)} + {@const goalFormatted = formatNumber(dashboard.goalTarget)} + {@const percentComplete = formatPercent(dashboard.goalPercent)} + {@const remainingFormatted = formatNumber(dashboard.goalRemaining)} +
+ + + {$t`10,000 entry goal`} + + {$t`${totalFormatted} of ${goalFormatted} entries`} + + + + +
+ {$t`${percentComplete} complete`} + {$t`${remainingFormatted} remaining`} +
+
+
+ +
+ + + {$t`Total entries`} + + +

{formatNumber(dashboard.totalEntries)}

+
+
+ + + {$t`Entries with senses`} + + +

{formatNumber(dashboard.entriesWithSenses)}

+

+ {formatCompletion( + {filled: dashboard.entriesWithSenses, percent: dashboard.totalEntries === 0 ? 0 : Math.round((dashboard.entriesWithSenses / dashboard.totalEntries) * 100)}, + dashboard.totalEntries, + )} +

+
+
+ + + {$t`Entries with examples`} + + +

{formatNumber(dashboard.entriesWithExamples)}

+

+ {formatCompletion( + {filled: dashboard.entriesWithExamples, percent: dashboard.totalEntries === 0 ? 0 : Math.round((dashboard.entriesWithExamples / dashboard.totalEntries) * 100)}, + dashboard.totalEntries, + )} +

+
+
+
+ +
+

{$t`Vernacular writing systems`}

+ {#if dashboard.vernacular.length === 0} +

{$t`No vernacular writing systems configured.`}

+ {:else} +
+ {#each dashboard.vernacular as ws (ws.wsId)} + {@const wsColorClass = writingSystemService.wsColor(ws.wsId, 'vernacular')} + + + {ws.abbreviation} + + + {@render fieldStatRow($t`Lexeme form`, ws.lexemeForm, dashboard.totalEntries, wsColorClass)} + {@render fieldStatRow($t`Citation form`, ws.citationForm, dashboard.totalEntries, wsColorClass)} + {@render fieldStatRow($t`Example sentences`, ws.exampleSentence, dashboard.totalEntries, wsColorClass)} + + + {/each} +
+ {/if} +
+ +
+

{$t`Analysis writing systems`}

+ {#if dashboard.analysis.length === 0} +

{$t`No analysis writing systems configured.`}

+ {:else} +
+ {#each dashboard.analysis as ws (ws.wsId)} + {@const wsColorClass = writingSystemService.wsColor(ws.wsId, 'analysis')} + + + {ws.abbreviation} + + + {@render fieldStatRow($t`Gloss`, ws.gloss, dashboard.totalEntries, wsColorClass)} + {@render fieldStatRow($t`Definition`, ws.definition, dashboard.totalEntries, wsColorClass)} + + + {/each} +
+ {/if} +
+
+ {:else} +

{$t`Loading dashboard...`}

+ {/if} +
+
diff --git a/frontend/viewer/src/project/dashboard/dashboard-stats.ts b/frontend/viewer/src/project/dashboard/dashboard-stats.ts new file mode 100644 index 0000000000..080d18d50d --- /dev/null +++ b/frontend/viewer/src/project/dashboard/dashboard-stats.ts @@ -0,0 +1,148 @@ +import {useDebounce} from 'runed'; +import type {IMiniLcmJsInvokable, IWritingSystem} from '$lib/dotnet-types'; +import {useProjectEventBus} from '$lib/services/event-bus'; +import {useProjectContext} from '$project/project-context.svelte'; +import {useWritingSystemService} from '$project/data'; + +export const ENTRY_GOAL = 10_000; + +export type FieldCompletion = { + filled: number; + percent: number; +}; + +export type VernacularWritingSystemStats = { + wsId: string; + abbreviation: string; + lexemeForm: FieldCompletion; + citationForm: FieldCompletion; + exampleSentence: FieldCompletion; +}; + +export type AnalysisWritingSystemStats = { + wsId: string; + abbreviation: string; + gloss: FieldCompletion; + definition: FieldCompletion; +}; + +export type DashboardStats = { + totalEntries: number; + entriesWithSenses: number; + entriesWithExamples: number; + goalTarget: number; + goalPercent: number; + goalRemaining: number; + vernacular: VernacularWritingSystemStats[]; + analysis: AnalysisWritingSystemStats[]; +}; + +const dashboardStatsSymbol = Symbol.for('fw-lite-dashboard-stats'); + +async function countMissing(api: IMiniLcmJsInvokable, gridifyFilter: string): Promise { + return api.countEntries(undefined, {filter: {gridifyFilter}}); +} + +function completion(total: number, filled: number): FieldCompletion { + return { + filled, + percent: total === 0 ? 0 : Math.round((filled / total) * 100), + }; +} + +async function filledCount(api: IMiniLcmJsInvokable, total: number, gridifyFilter: string): Promise { + const missing = await countMissing(api, gridifyFilter); + return total - missing; +} + +function mapVernacularStats( + totalEntries: number, + writingSystems: IWritingSystem[], + filledCounts: number[], +): VernacularWritingSystemStats[] { + let index = 0; + return writingSystems.map(ws => ({ + wsId: ws.wsId, + abbreviation: ws.abbreviation, + lexemeForm: completion(totalEntries, filledCounts[index++]), + citationForm: completion(totalEntries, filledCounts[index++]), + exampleSentence: completion(totalEntries, filledCounts[index++]), + })); +} + +function mapAnalysisStats( + totalEntries: number, + writingSystems: IWritingSystem[], + filledCounts: number[], +): AnalysisWritingSystemStats[] { + let index = 0; + return writingSystems.map(ws => ({ + wsId: ws.wsId, + abbreviation: ws.abbreviation, + gloss: completion(totalEntries, filledCounts[index++]), + definition: completion(totalEntries, filledCounts[index++]), + })); +} + +async function fetchDashboardStats( + api: IMiniLcmJsInvokable, + vernacular: IWritingSystem[], + analysis: IWritingSystem[], +): Promise { + const totalEntries = await api.countEntries(undefined, undefined); + + const [entriesWithSenses, entriesWithExamples, vernacularFilled, analysisFilled] = await Promise.all([ + filledCount(api, totalEntries, 'Senses=null'), + filledCount(api, totalEntries, 'Senses.ExampleSentences=null'), + Promise.all(vernacular.flatMap(ws => [ + filledCount(api, totalEntries, `LexemeForm[${ws.wsId}]=`), + filledCount(api, totalEntries, `CitationForm[${ws.wsId}]=`), + filledCount(api, totalEntries, `Senses.ExampleSentences=null|Senses.ExampleSentences.Sentence[${ws.wsId}]=`), + ])), + Promise.all(analysis.flatMap(ws => [ + filledCount(api, totalEntries, `Senses=null|Senses.Gloss[${ws.wsId}]=`), + filledCount(api, totalEntries, `Senses=null|Senses.Definition[${ws.wsId}]=`), + ])), + ]); + + return { + totalEntries, + entriesWithSenses, + entriesWithExamples, + goalTarget: ENTRY_GOAL, + goalPercent: totalEntries === 0 ? 0 : Math.min(100, Math.round((totalEntries / ENTRY_GOAL) * 100)), + goalRemaining: Math.max(0, ENTRY_GOAL - totalEntries), + vernacular: mapVernacularStats(totalEntries, vernacular, vernacularFilled), + analysis: mapAnalysisStats(totalEntries, analysis, analysisFilled), + }; +} + +export function useDashboardStats() { + const projectContext = useProjectContext(); + const projectEventBus = useProjectEventBus(); + const writingSystemService = useWritingSystemService(); + + const statsResource = projectContext.getOrAddAsync( + dashboardStatsSymbol, + undefined, + async (api) => { + let vernacular = writingSystemService.vernacular; + let analysis = writingSystemService.analysis; + if (vernacular.length === 0 && analysis.length === 0) { + const writingSystems = await api.getWritingSystems(); + vernacular = writingSystems.vernacular; + analysis = writingSystems.analysis; + } + return fetchDashboardStats(api, vernacular, analysis); + }, + { + onAdd: (resource) => { + const debouncedRefetch = useDebounce(() => void resource.refetch(), 500); + projectEventBus.onEntryDeleted(() => void debouncedRefetch()); + projectEventBus.onEntryUpdated(() => void debouncedRefetch()); + }, + }, + ); + + return statsResource; +} From d37475688fdefa1e413c647e9854b4371151379b Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Thu, 2 Jul 2026 16:27:18 +0200 Subject: [PATCH 2/4] Refine project dashboard: milestone hero, feedback card, copy polish - Replace fixed 10,000-entry goal with a next-milestone progress hero; drop the redundant Total entries card (the count is now the hero number) - Add a "What would you like to see here?" feedback card that deep-links to a prefilled forum suggestion; share the community URLs via community-links.ts - Encouraging, non-servile, translation-friendly copy; scrollbar padding fix Co-Authored-By: Claude Opus 4.8 --- .../src/lib/about/FeedbackDialog.svelte | 5 +- .../viewer/src/lib/about/community-links.ts | 13 +++ frontend/viewer/src/locales/en.po | 41 +++++---- frontend/viewer/src/locales/es.po | 41 +++++---- frontend/viewer/src/locales/fr.po | 41 +++++---- frontend/viewer/src/locales/id.po | 41 +++++---- frontend/viewer/src/locales/ko.po | 41 +++++---- frontend/viewer/src/locales/ms.po | 41 +++++---- frontend/viewer/src/locales/sw.po | 41 +++++---- frontend/viewer/src/locales/vi.po | 41 +++++---- .../project/dashboard/DashboardView.svelte | 90 +++++++++++++------ .../src/project/dashboard/dashboard-stats.ts | 26 ++++-- 12 files changed, 297 insertions(+), 165 deletions(-) create mode 100644 frontend/viewer/src/lib/about/community-links.ts diff --git a/frontend/viewer/src/lib/about/FeedbackDialog.svelte b/frontend/viewer/src/lib/about/FeedbackDialog.svelte index 28b2e40a58..67ef874f93 100644 --- a/frontend/viewer/src/lib/about/FeedbackDialog.svelte +++ b/frontend/viewer/src/lib/about/FeedbackDialog.svelte @@ -4,6 +4,7 @@ import {useFwLiteConfig} from '$lib/services/service-provider'; import {Button} from '$lib/components/ui/button'; import ResponsiveDialog from '$lib/components/responsive-dialog/responsive-dialog.svelte'; + import {featureRequestsUrl, supportForumUrl} from './community-links'; let {open = $bindable()}: { open: boolean } = $props(); const config = useFwLiteConfig(); @@ -14,7 +15,7 @@
-
- {:else}

{$t`Loading dashboard...`}

diff --git a/frontend/viewer/src/project/dashboard/dashboard-stats.ts b/frontend/viewer/src/project/dashboard/dashboard-stats.ts index 080d18d50d..5b4e8aee4b 100644 --- a/frontend/viewer/src/project/dashboard/dashboard-stats.ts +++ b/frontend/viewer/src/project/dashboard/dashboard-stats.ts @@ -4,13 +4,29 @@ import {useProjectEventBus} from '$lib/services/event-bus'; import {useProjectContext} from '$project/project-context.svelte'; import {useWritingSystemService} from '$project/data'; -export const ENTRY_GOAL = 10_000; +export const MILESTONES = [50, 100, 250, 500, 1_000, 2_500, 5_000, 10_000, 25_000, 50_000, 100_000]; export type FieldCompletion = { filled: number; percent: number; }; +export type MilestoneProgress = { + target: number; + remaining: number; + percent: number; +}; + +function nextMilestone(totalEntries: number): MilestoneProgress | undefined { + const target = MILESTONES.find(m => m > totalEntries); + if (target === undefined) return undefined; + return { + target, + remaining: target - totalEntries, + percent: Math.min(100, Math.round((totalEntries / target) * 100)), + }; +} + export type VernacularWritingSystemStats = { wsId: string; abbreviation: string; @@ -30,9 +46,7 @@ export type DashboardStats = { totalEntries: number; entriesWithSenses: number; entriesWithExamples: number; - goalTarget: number; - goalPercent: number; - goalRemaining: number; + milestone: MilestoneProgress | undefined; vernacular: VernacularWritingSystemStats[]; analysis: AnalysisWritingSystemStats[]; }; @@ -109,9 +123,7 @@ async function fetchDashboardStats( totalEntries, entriesWithSenses, entriesWithExamples, - goalTarget: ENTRY_GOAL, - goalPercent: totalEntries === 0 ? 0 : Math.min(100, Math.round((totalEntries / ENTRY_GOAL) * 100)), - goalRemaining: Math.max(0, ENTRY_GOAL - totalEntries), + milestone: nextMilestone(totalEntries), vernacular: mapVernacularStats(totalEntries, vernacular, vernacularFilled), analysis: mapAnalysisStats(totalEntries, analysis, analysisFilled), }; From 12c58e7973579640ab62142a0bb89ba4fb6926d1 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 3 Jul 2026 16:03:35 +0700 Subject: [PATCH 3/4] Address CodeRabbit dashboard stats review feedback. Use named per-field counts instead of positional arrays, and compute senses/examples as FieldCompletion in one place. Co-authored-by: Cursor --- .../project/dashboard/DashboardView.svelte | 14 +--- .../src/project/dashboard/dashboard-stats.ts | 77 +++++++++++-------- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/frontend/viewer/src/project/dashboard/DashboardView.svelte b/frontend/viewer/src/project/dashboard/DashboardView.svelte index 3fce4ac5f9..683ba2bd7b 100644 --- a/frontend/viewer/src/project/dashboard/DashboardView.svelte +++ b/frontend/viewer/src/project/dashboard/DashboardView.svelte @@ -108,12 +108,9 @@ {$t`Entries with senses`} -

{formatNumber(dashboard.entriesWithSenses)}

+

{formatNumber(dashboard.entriesWithSenses.filled)}

- {formatCompletion( - {filled: dashboard.entriesWithSenses, percent: dashboard.totalEntries === 0 ? 0 : Math.round((dashboard.entriesWithSenses / dashboard.totalEntries) * 100)}, - dashboard.totalEntries, - )} + {formatCompletion(dashboard.entriesWithSenses, dashboard.totalEntries)}

@@ -122,12 +119,9 @@ {$t`Entries with examples`} -

{formatNumber(dashboard.entriesWithExamples)}

+

{formatNumber(dashboard.entriesWithExamples.filled)}

- {formatCompletion( - {filled: dashboard.entriesWithExamples, percent: dashboard.totalEntries === 0 ? 0 : Math.round((dashboard.entriesWithExamples / dashboard.totalEntries) * 100)}, - dashboard.totalEntries, - )} + {formatCompletion(dashboard.entriesWithExamples, dashboard.totalEntries)}

diff --git a/frontend/viewer/src/project/dashboard/dashboard-stats.ts b/frontend/viewer/src/project/dashboard/dashboard-stats.ts index 5b4e8aee4b..a42736bd1f 100644 --- a/frontend/viewer/src/project/dashboard/dashboard-stats.ts +++ b/frontend/viewer/src/project/dashboard/dashboard-stats.ts @@ -44,13 +44,24 @@ export type AnalysisWritingSystemStats = { export type DashboardStats = { totalEntries: number; - entriesWithSenses: number; - entriesWithExamples: number; + entriesWithSenses: FieldCompletion; + entriesWithExamples: FieldCompletion; milestone: MilestoneProgress | undefined; vernacular: VernacularWritingSystemStats[]; analysis: AnalysisWritingSystemStats[]; }; +type VernacularFilledCounts = { + lexemeForm: number; + citationForm: number; + exampleSentence: number; +}; + +type AnalysisFilledCounts = { + gloss: number; + definition: number; +}; + const dashboardStatsSymbol = Symbol.for('fw-lite-dashboard-stats'); async function countMissing(api: IMiniLcmJsInvokable, gridifyFilter: string): Promise { @@ -72,30 +83,34 @@ async function filledCount(api: IMiniLcmJsInvokable, total: number, gridifyFilte function mapVernacularStats( totalEntries: number, writingSystems: IWritingSystem[], - filledCounts: number[], + filledCounts: VernacularFilledCounts[], ): VernacularWritingSystemStats[] { - let index = 0; - return writingSystems.map(ws => ({ - wsId: ws.wsId, - abbreviation: ws.abbreviation, - lexemeForm: completion(totalEntries, filledCounts[index++]), - citationForm: completion(totalEntries, filledCounts[index++]), - exampleSentence: completion(totalEntries, filledCounts[index++]), - })); + return writingSystems.map((ws, i) => { + const counts = filledCounts[i]; + return { + wsId: ws.wsId, + abbreviation: ws.abbreviation, + lexemeForm: completion(totalEntries, counts.lexemeForm), + citationForm: completion(totalEntries, counts.citationForm), + exampleSentence: completion(totalEntries, counts.exampleSentence), + }; + }); } function mapAnalysisStats( totalEntries: number, writingSystems: IWritingSystem[], - filledCounts: number[], + filledCounts: AnalysisFilledCounts[], ): AnalysisWritingSystemStats[] { - let index = 0; - return writingSystems.map(ws => ({ - wsId: ws.wsId, - abbreviation: ws.abbreviation, - gloss: completion(totalEntries, filledCounts[index++]), - definition: completion(totalEntries, filledCounts[index++]), - })); + return writingSystems.map((ws, i) => { + const counts = filledCounts[i]; + return { + wsId: ws.wsId, + abbreviation: ws.abbreviation, + gloss: completion(totalEntries, counts.gloss), + definition: completion(totalEntries, counts.definition), + }; + }); } async function fetchDashboardStats( @@ -105,24 +120,24 @@ async function fetchDashboardStats( ): Promise { const totalEntries = await api.countEntries(undefined, undefined); - const [entriesWithSenses, entriesWithExamples, vernacularFilled, analysisFilled] = await Promise.all([ + const [entriesWithSensesCount, entriesWithExamplesCount, vernacularFilled, analysisFilled] = await Promise.all([ filledCount(api, totalEntries, 'Senses=null'), filledCount(api, totalEntries, 'Senses.ExampleSentences=null'), - Promise.all(vernacular.flatMap(ws => [ - filledCount(api, totalEntries, `LexemeForm[${ws.wsId}]=`), - filledCount(api, totalEntries, `CitationForm[${ws.wsId}]=`), - filledCount(api, totalEntries, `Senses.ExampleSentences=null|Senses.ExampleSentences.Sentence[${ws.wsId}]=`), - ])), - Promise.all(analysis.flatMap(ws => [ - filledCount(api, totalEntries, `Senses=null|Senses.Gloss[${ws.wsId}]=`), - filledCount(api, totalEntries, `Senses=null|Senses.Definition[${ws.wsId}]=`), - ])), + Promise.all(vernacular.map(async ws => ({ + lexemeForm: await filledCount(api, totalEntries, `LexemeForm[${ws.wsId}]=`), + citationForm: await filledCount(api, totalEntries, `CitationForm[${ws.wsId}]=`), + exampleSentence: await filledCount(api, totalEntries, `Senses.ExampleSentences=null|Senses.ExampleSentences.Sentence[${ws.wsId}]=`), + }))), + Promise.all(analysis.map(async ws => ({ + gloss: await filledCount(api, totalEntries, `Senses=null|Senses.Gloss[${ws.wsId}]=`), + definition: await filledCount(api, totalEntries, `Senses=null|Senses.Definition[${ws.wsId}]=`), + }))), ]); return { totalEntries, - entriesWithSenses, - entriesWithExamples, + entriesWithSenses: completion(totalEntries, entriesWithSensesCount), + entriesWithExamples: completion(totalEntries, entriesWithExamplesCount), milestone: nextMilestone(totalEntries), vernacular: mapVernacularStats(totalEntries, vernacular, vernacularFilled), analysis: mapAnalysisStats(totalEntries, analysis, analysisFilled), From e3526e7b48bf8099fd487016aa5c8429b0479d10 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 3 Jul 2026 16:12:44 +0700 Subject: [PATCH 4/4] Show dashboard in sidebar for all users. Remove the dev-mode gate so the Dashboard nav item is visible without enabling developer mode. Co-authored-by: Cursor --- frontend/viewer/src/project/ProjectSidebar.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/viewer/src/project/ProjectSidebar.svelte b/frontend/viewer/src/project/ProjectSidebar.svelte index a5121c098e..9a2bd745f7 100644 --- a/frontend/viewer/src/project/ProjectSidebar.svelte +++ b/frontend/viewer/src/project/ProjectSidebar.svelte @@ -88,9 +88,7 @@ {$t`Dictionary`} - - {@render ViewButton('dashboard', 'i-mdi-view-dashboard', $t`Dashboard`)} - + {@render ViewButton('dashboard', 'i-mdi-view-dashboard', $t`Dashboard`)} {@render ViewButton('browse', 'i-mdi-book-alphabet', $t`Browse`, formatNumber(stats.current?.totalEntryCount))} {@render ViewButton('tasks', 'i-mdi-checkbox-marked', $t`Tasks`)}