feat: add vocabulary browser with filters, audio playback, and example sentences
Release Build / build-docker (push) Successful in 2m44s
Release Build / build-android-and-release (push) Failing after 1m27s

- Add Vocabularies page with searchable, level-grouped grid
- Implement multi-faceted filter system (word type, verb class, transitivity, reading ending, character count)
- Add male/female voice audio playback via WaniKani pronunciation data
- Display context sentences from WaniKani API
- Collapsible mnemonics section in detail modal
- Component kanji chips navigate to Collection with pre-filled search
- Vocabulary sync uses upsert to keep data fresh (audio, sentences, normalized POS)
- Add Vocabulary model, controller, service, and API route
- Add seed data with 28 vocabulary entries including transitive/intransitive verb pairs
- Full i18n support (EN, DE, JA) for all new UI elements
This commit is contained in:
2026-04-28 21:57:34 +02:00
parent c1651550d1
commit b1d4f9f71b
21 changed files with 2403 additions and 222 deletions
+87
View File
@@ -10,6 +10,7 @@ const messages = {
dashboard: 'Dashboard',
review: 'Review',
collection: 'Collection',
vocabularies: 'Vocabularies',
settings: 'Settings',
sync: 'Sync',
logout: 'Logout',
@@ -99,6 +100,8 @@ const messages = {
items: 'Items',
language: 'Language',
drawingTolerance: 'Drawing Tolerance',
strokeStrictness: 'Stroke Strictness',
advanced: 'Advanced',
strict: 'Strict',
loose: 'Loose',
save: 'Save & Close',
@@ -142,6 +145,32 @@ const messages = {
startLesson: 'Start Lesson',
redoLesson: 'Redo Lesson',
},
vocabulary: {
searchLabel: 'Search Vocabulary...',
placeholder: "e.g. '大人', 'otona', 'adult'",
loading: 'Loading Vocabularies...',
noMatches: 'No matches found',
tryDifferent: 'Try searching for a different word or reading.',
levelHeader: 'LEVEL',
levelLabel: 'Level',
primary: 'Primary',
mnemonics: 'Mnemonics',
meaningMnemonic: 'Meaning Mnemonic',
readingMnemonic: 'Reading Mnemonic',
components: 'Component Kanji',
exampleSentences: 'Example Sentences',
filters: 'Filters',
filterType: 'Word Type',
filterVerbClass: 'Verb Class',
filterTransitivity: 'Transitivity',
filterEnding: 'Reading Ending',
filterLength: 'Character Count',
clearAll: 'Clear All',
matchCount: '{n} results',
listenMale: 'Male',
listenFemale: 'Female',
close: 'Close',
},
},
de: {
common: {
@@ -152,6 +181,7 @@ const messages = {
dashboard: 'Übersicht',
review: 'Lernen',
collection: 'Sammlung',
vocabularies: 'Vokabeln',
settings: 'Einstellungen',
sync: 'Sync',
logout: 'Abmelden',
@@ -241,6 +271,8 @@ const messages = {
items: 'Einträge',
language: 'Sprache',
drawingTolerance: 'Zeichentoleranz',
strokeStrictness: 'Strichgenauigkeit',
advanced: 'Erweitert',
strict: 'Strikt',
loose: 'Locker',
save: 'Speichern & Schließen',
@@ -284,6 +316,32 @@ const messages = {
startLesson: 'Lektion starten',
redoLesson: 'Lektion wiederholen',
},
vocabulary: {
searchLabel: 'Vokabel suchen...',
placeholder: "z.B. '大人', 'otona', 'Erwachsener'",
loading: 'Lade Vokabeln...',
noMatches: 'Keine Treffer',
tryDifferent: 'Versuche einen anderen Suchbegriff.',
levelHeader: 'STUFE',
levelLabel: 'Stufe',
primary: 'Primär',
mnemonics: 'Eselsbrücken',
meaningMnemonic: 'Bedeutungs-Eselsbrücke',
readingMnemonic: 'Lesungs-Eselsbrücke',
components: 'Kanji-Bestandteile',
exampleSentences: 'Beispielsätze',
filters: 'Filter',
filterType: 'Wortart',
filterVerbClass: 'Verbklasse',
filterTransitivity: 'Transitivität',
filterEnding: 'Lesung-Endung',
filterLength: 'Zeichenanzahl',
clearAll: 'Zurücksetzen',
matchCount: '{n} Ergebnisse',
listenMale: 'Männlich',
listenFemale: 'Weiblich',
close: 'Schließen',
},
},
ja: {
common: {
@@ -294,6 +352,7 @@ const messages = {
dashboard: 'ダッシュボード',
review: '復習',
collection: 'コレクション',
vocabularies: '単語',
settings: '設定',
sync: '同期',
logout: 'ログアウト',
@@ -383,6 +442,8 @@ const messages = {
items: '個',
language: '言語 (Language)',
drawingTolerance: '描画許容範囲',
strokeStrictness: '筆画の厳密さ',
advanced: '詳細設定',
strict: '厳しい',
loose: '甘い',
save: '保存して閉じる',
@@ -426,6 +487,32 @@ const messages = {
startLesson: 'レッスン開始',
redoLesson: 'レッスンをやり直す',
},
vocabulary: {
searchLabel: '単語を検索...',
placeholder: "例: '大人', 'おとな', 'adult'",
loading: '単語を読み込み中...',
noMatches: '見つかりませんでした',
tryDifferent: '別のキーワードで検索してください。',
levelHeader: 'レベル',
levelLabel: 'レベル',
primary: '主要',
mnemonics: '覚え方',
meaningMnemonic: '意味の覚え方',
readingMnemonic: '読みの覚え方',
components: '構成漢字',
exampleSentences: '例文',
filters: 'フィルター',
filterType: '品詞',
filterVerbClass: '動詞の種類',
filterTransitivity: '自他',
filterEnding: '読みの語尾',
filterLength: '文字数',
clearAll: 'クリア',
matchCount: '{n}件',
listenMale: '男性',
listenFemale: '女性',
close: '閉じる',
},
},
};