feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the device's language, each named in its own language. app_en.arb, the template, is now British English (colour, centre) and the fallback for any language the app doesn't speak; app_en_US.arb overrides the five messages American English spells differently, and app_en_GB.arb is empty, there so that Material's texts and dates are British too. The German translation says "du" and keeps one set of terms throughout (Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in the Anki import). arb_files_test checks that German has every message of the template and that every translation keeps its placeholders. resolveAppLocale picks the first language the device prefers that the app speaks, in its country's variant where there is one; other English is British, and a country alone never picks a language (Spanish in the United States isn't American English), which Flutter's own resolution would do. Both MaterialApps take material_ui's GlobalMaterialLocalizations delegates: gen-l10n's list brings flutter_localizations', which serve only package:flutter/material.dart, so material_ui's widgets found no Material texts in German and threw. The editor's own text blocks show our placeholders while empty (list item, to-do, quote, heading, and the desktop's / hint): the editor's own follow a locale of their own, process-wide, in few languages. The last texts written into widgets (the person's data failing to open, the cards failing to read, the About dialog) moved into the arb files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
55a0a7fdf0
commit
3ee9bce0c5
28 changed files with 4239 additions and 60 deletions
|
|
@ -99,7 +99,7 @@ A spaced-repetition learning app: studysets of flashcards, reviewed on an FSRS s
|
|||
- **Routes carry ids, never objects.** A screen finds its item in the repository's live list with `FoundById` (`core/widget/`): a spinner until the list arrives, then the item, or a screen saying it no longer exists (deleted on another device, or an old link). A screen that deletes its item closes, and keeps showing the item while it goes. Why: every location can be built from its path alone, and no screen goes on showing something that has gone.
|
||||
- **The look is one theme** (`core/theme/`): `appTheme(brightness)` with hand-written light and dark colour schemes around the brand's cobalt and copper; `Theme.of(context).extras` (`ThemeExtras`) for the colours Material has no role for (states, the four FSRS ratings, a starred star); `PaletteColor` for the 20 colours a person can give content. `theme_test.dart` holds text to WCAG AA contrast (4.5:1) on each palette colour and on the dark theme's primary colour and surface. Layouts follow Material 3's window-size classes (`context.windowSize`, `core/layout/`), and running text stays within `readableContentMaxWidth`. Icons are Lucide (`lucide_icons_flutter`), except a starred star, which needs the fill Lucide lacks; the Material icons left in older screens go when those are restyled. Shared widgets are in `core/widget/` (`SectionCard`, `AppLoadingIndicator`, `confirmDestructiveAction`, `FoundById`, `TitleWithKicker`, `ContentCountsLine`, `showUndoSnackBar`, `ScrollHidingFab`, `PaletteColorPicker`, and the sheets below).
|
||||
- **Forms and pickers open as sheets**: `showAdaptiveSheet` with a `SheetScaffold` (title and close button on top, the body scrolling, the action pinned below) shows a bottom sheet below expanded windows and a dialog from there on. A sheet that asks before discarding changes (a `PopScope`) passes `swipeable: false`: a bottom sheet swiped away closes without asking its `PopScope` (Flutter issue 152120, open with v3.47). A sheet offering an Undo passes `hasSnackBars: true`, so its snack bars show on it rather than behind its barrier. A picker that changes something outside it reports every change as it happens (`onChanged`) rather than as its result, since a sheet swiped away has none.
|
||||
- **Text comes from `AppLocalizations`** (Flutter's gen-l10n, `lib/l10n/app_en.arb`; English only for now): no string a person reads is written into a widget, except in older screens until they are restyled. The generated `lib/l10n/app_localizations*.dart` are committed and never edited, analysed or formatted by hand. `flutter pub get` writes them anew, so CI fails when they don't match the `.arb` file.
|
||||
- **Text comes from `AppLocalizations`** (Flutter's gen-l10n, `lib/l10n/`): no string a person reads is written into a widget. The app speaks British English, American English and German (informal "du"; the terms of `app_de.arb`, such as Lernset and Karteikarte, stay consistent). `app_en.arb` is the template and holds British English, also the fallback for any language the app doesn't speak; `app_en_US.arb` overrides only what American English spells differently, and `app_en_GB.arb` is empty, there so that Material and dates format the British way. A new message goes into `app_en.arb` and `app_de.arb` together (`test/l10n/arb_files_test.dart` fails otherwise, and checks the placeholders), and into `app_en_US.arb` if its spelling differs. `resolveAppLocale` (`core/l10n/`) picks the language, never by country alone, and both `MaterialApp`s take `appLocalizationsDelegates`, not gen-l10n's list: that brings flutter_localizations' Material texts, which serve only the SDK's Material, so material_ui's widgets would stay English. The editor's own placeholders are replaced by ours (`buildersWithUnknown`), since the editor's texts follow a process-wide locale of their own. The generated `lib/l10n/app_localizations*.dart` are committed and never edited, analysed or formatted by hand. `flutter pub get` writes them anew, so CI fails when they don't match the `.arb` files.
|
||||
|
||||
## Testing
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# Flutter's gen-l10n: the strings in lib/l10n/app_en.arb become the AppLocalizations
|
||||
# class, generated into lib/l10n/app_localizations*.dart (committed, never edited or
|
||||
# formatted by hand). English only for now; another language is one more .arb file.
|
||||
# Flutter's gen-l10n: the strings in lib/l10n/*.arb become the AppLocalizations class,
|
||||
# generated into lib/l10n/app_localizations*.dart (committed, never edited or formatted
|
||||
# by hand). app_en.arb, the template, is British English, also the fallback;
|
||||
# app_en_US.arb overrides what American English spells differently; app_en_GB.arb is
|
||||
# empty, there so that Material and dates format the British way; app_de.arb is German.
|
||||
arb-dir: lib/l10n
|
||||
template-arb-file: app_en.arb
|
||||
output-localization-file: app_localizations.dart
|
||||
|
|
|
|||
785
halkyon_learn_flutter/lib/l10n/app_de.arb
Normal file
785
halkyon_learn_flutter/lib/l10n/app_de.arb
Normal file
|
|
@ -0,0 +1,785 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
|
||||
"appTitle": "Halkyon Learn",
|
||||
|
||||
"shellTabHome": "Startseite",
|
||||
"shellTabLibrary": "Bibliothek",
|
||||
"shellTabExplore": "Entdecken",
|
||||
"shellTabProfile": "Profil",
|
||||
|
||||
"homeTitle": "Startseite",
|
||||
|
||||
"libraryTitle": "Bibliothek",
|
||||
"libraryCreateStudyset": "Lernset erstellen",
|
||||
"libraryEmpty": "Noch keine Lernsets",
|
||||
"libraryUnreadable": "Deine Lernsets konnten nicht gelesen werden.",
|
||||
"libraryArchivedSection": "Archiviert",
|
||||
"librarySearchTooltip": "Suchen",
|
||||
"librarySearchHint": "Lernsets durchsuchen",
|
||||
"librarySearchClearTooltip": "Suche löschen",
|
||||
"librarySearchCloseTooltip": "Suche schließen",
|
||||
"librarySearchNoMatches": "Keine Lernsets für deine Suche gefunden",
|
||||
"libraryStudysetArchived": "Lernset archiviert",
|
||||
"libraryStudysetUnarchived": "Lernset aus dem Archiv geholt",
|
||||
"libraryStudysetDeleted": "Lernset gelöscht",
|
||||
"libraryDeleteTitle": "Lernset löschen?",
|
||||
"libraryDeleteMessage": "Es wird samt allem, was darin ist, und allen zugehörigen Karten auf all deinen Geräten gelöscht. Du kannst dies für einige Sekunden rückgängig machen.",
|
||||
"libraryDeleteConfirm": "Löschen",
|
||||
|
||||
"studysetSheetCreateTitle": "Lernset erstellen",
|
||||
"studysetSheetEditTitle": "Lernset bearbeiten",
|
||||
"studysetSheetCreate": "Erstellen",
|
||||
"studysetSheetSave": "Speichern",
|
||||
"studysetSheetTitleLabel": "Titel",
|
||||
"studysetSheetSecondaryLabel": "Zweittitel",
|
||||
"studysetSheetSecondaryHelper": "Wird als kleines Label über dem Titel angezeigt.",
|
||||
"studysetSheetColor": "Farbe",
|
||||
"studysetSheetDiscardTitle": "Änderungen verwerfen?",
|
||||
"studysetSheetDiscardMessage": "Du hast ungespeicherte Änderungen an diesem Lernset. Verwerfen und das Formular schließen?",
|
||||
"studysetSheetDiscardConfirm": "Verwerfen",
|
||||
|
||||
"paletteColorName": "{name, select, carrot{Karotte} cerise{Kirschrot} cobalt{Kobalt} copper{Kupfer} enzian{Enzian} fern{Farn} forest{Wald} ocean{Ozean} olive{Olive} pacific{Pazifik} pickle{Essiggurke} pine{Kiefer} pink{Rosa} plum{Pflaume} purple{Lila} reef{Riff} sahara{Sahara} slateblue{Schieferblau} soil{Erde} strawberry{Erdbeere} other{{name}}}",
|
||||
"colorPickerLabel": "Farbe: {color}",
|
||||
|
||||
"studysetCountFlashcards": "{count, plural, =1{1 Karteikarte} other{{count} Karteikarten}}",
|
||||
"studysetCountNotes": "{count, plural, =1{1 Notiz} other{{count} Notizen}}",
|
||||
"studysetNoContent": "Noch kein Inhalt",
|
||||
"studysetActionReview": "Wiederholen",
|
||||
"studysetActionStudy": "Lernen",
|
||||
"studysetActionAdd": "Inhalt hinzufügen",
|
||||
"studysetArchived": "Archiviert",
|
||||
"studysetStar": "Mit Stern markieren",
|
||||
"studysetUnstar": "Markierung entfernen",
|
||||
"studysetOptions": "Optionen",
|
||||
"studysetMenuEdit": "Bearbeiten",
|
||||
"studysetMenuArchive": "Archivieren",
|
||||
"studysetMenuUnarchive": "Aus Archiv holen",
|
||||
"studysetMenuDelete": "Löschen",
|
||||
"studysetMenuMove": "Verschieben nach…",
|
||||
"studysetTabFlashcards": "Karteikarten",
|
||||
"studysetTabNotes": "Notizen",
|
||||
"studysetTabPaths": "Pfade",
|
||||
"studysetCreatePath": "Pfad erstellen",
|
||||
"pathImportMenu": "Pfad importieren (JSON)",
|
||||
"pathImportFileType": "Lernpfad (JSON)",
|
||||
"pathImported": "{count, plural, =1{„{title}“ mit 1 Schritt importiert.} other{„{title}“ mit {count} Schritten importiert.}}",
|
||||
"pathImportSkipped": "{count, plural, =1{1 Block wurde ausgelassen: Bilder und Animationen werden nicht importiert.} other{{count} Blöcke wurden ausgelassen: Bilder und Animationen werden nicht importiert.}}",
|
||||
"pathImportFailed": "Diese Datei konnte nicht importiert werden. {reason}",
|
||||
"pathImportUnreadable": "Der Inhalt ist kein Text in UTF-8.",
|
||||
"pathsEmpty": "Noch keine Pfade",
|
||||
"pathsEmptyHint": "Ein Pfad führt Schritt für Schritt durch eine Lektion.",
|
||||
"pathOptions": "Pfadoptionen",
|
||||
"pathEdit": "Bearbeiten",
|
||||
"pathResetProgress": "Fortschritt zurücksetzen",
|
||||
"pathProgressReset": "Fortschritt zurückgesetzt",
|
||||
"pathDelete": "Löschen",
|
||||
"pathDeleteTitle": "Pfad löschen?",
|
||||
"pathDeleteMessage": "Dieser Pfad und seine Schritte werden aus dem Lernset entfernt. Du kannst dies für einige Sekunden rückgängig machen.",
|
||||
"pathDeleteConfirm": "Löschen",
|
||||
"pathDeleted": "Pfad gelöscht",
|
||||
"pathCreateTitle": "Neuer Pfad",
|
||||
"pathCreate": "Erstellen",
|
||||
"pathTitleLabel": "Titel",
|
||||
"pathTitleRequired": "Ein Pfad braucht einen Titel.",
|
||||
"pathSecondaryTitleLabel": "Zweittitel",
|
||||
"pathSecondaryTitleHint": "Z. B. der Titel in der Sprache, die du lernst",
|
||||
"pathEditTitle": "Pfad bearbeiten",
|
||||
"pathDescription": "Beschreibung",
|
||||
"pathAddDescription": "Beschreibung hinzufügen",
|
||||
"pathDescriptionEditTitle": "Pfadbeschreibung",
|
||||
"pathAddTags": "Tags hinzufügen",
|
||||
"pathTags": "Tags",
|
||||
"pathSteps": "Schritte",
|
||||
"pathStepsEmpty": "Noch keine Schritte",
|
||||
"pathStepsEmptyHint": "Wähle einen Schritt-Typ, um mit dem Schreiben der Lektion zu beginnen.",
|
||||
"pathAddStep": "Schritt hinzufügen",
|
||||
"pathReorder": "Umsortieren",
|
||||
"pathReorderDone": "Fertig",
|
||||
"pathStepDelete": "Schritt löschen",
|
||||
"pathStepDeleted": "Schritt gelöscht",
|
||||
"stepUntitled": "Unbenannter Schritt",
|
||||
"stepKindInfo": "Info",
|
||||
"stepKindSingleChoice": "Einzelauswahl",
|
||||
"stepKindMultipleChoice": "Mehrfachauswahl",
|
||||
"stepKindDragAndDrop": "Ziehen und Ablegen",
|
||||
"stepKindFillInTheBlank": "Lückentext",
|
||||
"stepEditTitle": "Schritt bearbeiten",
|
||||
"stepTitleHint": "Titel",
|
||||
"stepOptions": "Optionen",
|
||||
"stepOptionHint": "Option {number}",
|
||||
"stepAddOption": "Option hinzufügen",
|
||||
"stepRemoveOption": "Option entfernen",
|
||||
"stepCorrectOption": "Richtige Option",
|
||||
"stepCorrectOptions": "Richtige Optionen",
|
||||
"stepCategories": "Kategorien",
|
||||
"stepCategoryHint": "Kategorie {number}",
|
||||
"stepAddCategory": "Kategorie hinzufügen",
|
||||
"stepRemoveCategory": "Kategorie entfernen",
|
||||
"stepItems": "Elemente",
|
||||
"stepItemHint": "Element {number}",
|
||||
"stepAddItem": "Element hinzufügen",
|
||||
"stepRemoveItem": "Element entfernen",
|
||||
"stepItemCategory": "Gehört zu",
|
||||
"stepPassage": "Textpassage",
|
||||
"stepPassageHint": "Gib ___ (drei Unterstriche) überall dort ein, wo ein Wort eingesetzt werden soll.",
|
||||
"stepBlankHint": "Wort für Lücke {number}",
|
||||
"stepIncomplete": "Noch nicht beantwortbar: Fülle jedes Feld aus und wähle, was richtig ist.",
|
||||
"stepFeedbackRight": "Feedback nach einer richtigen Antwort",
|
||||
"stepFeedbackWrong": "Feedback nach einer falschen Antwort",
|
||||
"stepFeedbackNone": "Keines",
|
||||
"stepSources": "Quellen",
|
||||
"stepAddSource": "Quelle hinzufügen",
|
||||
"stepRemoveSource": "Quelle entfernen",
|
||||
"stepSourceAuthors": "Autoren",
|
||||
"stepSourceYear": "Jahr",
|
||||
"stepSourceTitle": "Titel",
|
||||
"stepSourceUrl": "Link",
|
||||
"stepSourcesCount": "{count, plural, =1{1 Quelle} other{{count} Quellen}}",
|
||||
"stepConflict": "Dieser Schritt wurde auf einem anderen Gerät anders geändert. Behalte eine Version, um weiterzuschreiben.",
|
||||
"stepFieldTitle": "Titel",
|
||||
"stepFieldBody": "Text",
|
||||
"stepFieldExercise": "Aufgabe",
|
||||
"stepFieldRightFeedback": "Feedback nach einer richtigen Antwort",
|
||||
"stepFieldWrongFeedback": "Feedback nach einer falschen Antwort",
|
||||
"stepFieldSources": "Quellen",
|
||||
"runnerClose": "Schließen",
|
||||
"runnerEditStep": "Schritt bearbeiten",
|
||||
"runnerBack": "Zurück",
|
||||
"runnerCheck": "Antworten prüfen",
|
||||
"runnerContinue": "Weiter",
|
||||
"runnerFinish": "Abschließen",
|
||||
"runnerRight": "Richtig!",
|
||||
"runnerWrong": "Nicht ganz",
|
||||
"runnerAlreadyRight": "Du hast das schon richtig beantwortet.",
|
||||
"runnerSelectAll": "Wähle alle zutreffenden aus.",
|
||||
"runnerDragHint": "Ziehe jedes Element in die Kategorie, zu der es gehört, oder tippe es an und dann die Kategorie. Tippe ein platziertes Element an, um es zurückzunehmen.",
|
||||
"runnerBlankHint": "Ziehe ein Wort in jede Lücke oder tippe es an und dann die Lücke. Tippe ein platziertes Wort an, um es zurückzunehmen.",
|
||||
"runnerEmpty": "Dieser Pfad hat noch keine Schritte.",
|
||||
"runnerFinished": "Du hast diesen Pfad abgeschlossen",
|
||||
"runnerFinishedSteps": "{count, plural, =1{1 Schritt abgeschlossen.} other{{count} Schritte abgeschlossen.}}",
|
||||
"runnerNextPath": "Weiter zum nächsten Pfad",
|
||||
"runnerBackToStudyset": "Zurück zum Lernset",
|
||||
"studysetCountPaths": "{count, plural, =1{1 Pfad} other{{count} Pfade}}",
|
||||
"studysetCreateNote": "Notiz erstellen",
|
||||
"notesEmpty": "Noch keine Notizen",
|
||||
"notesEmptyHint": "Notizen erscheinen hier, sobald du welche hinzufügst.",
|
||||
"noteUntitled": "Unbenannte Notiz",
|
||||
"noteEdited": "Bearbeitet {when}",
|
||||
"timeJustNow": "gerade eben",
|
||||
"timeMinutesAgo": "{count, plural, =1{vor 1 Minute} other{vor {count} Minuten}}",
|
||||
"timeHoursAgo": "{count, plural, =1{vor 1 Stunde} other{vor {count} Stunden}}",
|
||||
"timeDaysAgo": "{count, plural, =1{gestern} other{vor {count} Tagen}}",
|
||||
"noteOptions": "Notizoptionen",
|
||||
"noteEdit": "Bearbeiten",
|
||||
"noteDelete": "Löschen",
|
||||
"noteDeleteTitle": "Notiz löschen?",
|
||||
"noteDeleteMessage": "Diese Notiz wird aus dem Lernset entfernt. Du kannst dies für einige Sekunden rückgängig machen.",
|
||||
"noteDeleteConfirm": "Löschen",
|
||||
"noteDeleted": "Notiz gelöscht",
|
||||
"noteNewTitleFor": "Neue Notiz in „{title}“",
|
||||
"noteTitleFor": "Notiz in „{title}“",
|
||||
"noteTitleHint": "Titel",
|
||||
"noteEditing": "Wird bearbeitet…",
|
||||
"noteSaving": "Wird gespeichert…",
|
||||
"noteSaved": "Gespeichert",
|
||||
"noteSaveFailed": "Speichern fehlgeschlagen",
|
||||
"noteConflict": "Diese Notiz wurde auf einem anderen Gerät anders geändert. Behalte eine Version, um weiterzuschreiben.",
|
||||
"noteFieldTitle": "Titel",
|
||||
"noteFieldBody": "Text",
|
||||
"noteKeepThis": "Diese behalten",
|
||||
"noteAddTags": "Tags hinzufügen",
|
||||
"noteTags": "Tags",
|
||||
"studysetFlashcardsEmpty": "Noch keine Karteikarten",
|
||||
"studysetFlashcardsEmptyHint": "Füge welche hinzu, um mit dem Lernen zu beginnen.",
|
||||
"studysetAddMaterial": "Material hinzufügen",
|
||||
"studysetCreateFlashcards": "Karteikarten erstellen",
|
||||
"studysetStudy": "Lernen",
|
||||
"studysetLearnCount": "{count} lernen",
|
||||
"studysetOpenTopics": "Themen anzeigen",
|
||||
"studysetTopics": "Themen",
|
||||
"studysetAllTopics": "Alle Themen",
|
||||
"studysetNewSubStudyset": "Neues Unter-Lernset",
|
||||
"studysetSubStudysetsEmpty": "Noch keine Unter-Lernsets.",
|
||||
"studysetSubStudysetsEmptyHint": "Teile dieses Lernset in Meilensteine oder Lektionen auf.",
|
||||
"studysetReorder": "Umsortieren",
|
||||
"studysetReorderDone": "Fertig",
|
||||
"studysetDragToReorder": "Zum Umsortieren ziehen",
|
||||
"studysetMoveTitle": "Lernset verschieben nach",
|
||||
"studysetMoveTopLevel": "Oberste Ebene",
|
||||
"studysetMoveCurrent": "Aktueller Ort",
|
||||
"studysetMoveConfirm": "Verschieben",
|
||||
"studysetMoveShowInside": "Inhalt anzeigen",
|
||||
"studysetMoveHideInside": "Inhalt ausblenden",
|
||||
"studysetMoved": "Nach „{destination}“ verschoben.",
|
||||
"studysetReadMore": "Mehr anzeigen",
|
||||
"studysetDescriptionTitle": "Beschreibung",
|
||||
"studysetDescriptionEditTitle": "Beschreibung bearbeiten",
|
||||
"richTextSave": "Speichern",
|
||||
"studysetSheetDescription": "Beschreibung",
|
||||
"studysetSheetDescriptionAdd": "Beschreibung hinzufügen",
|
||||
"studysetSheetInheritColor": "Gleiche Farbe wie das Lernset, in dem es liegt",
|
||||
|
||||
"exploreTitle": "Entdecken",
|
||||
"exploreEmpty": "Lernsets, die andere teilen, erscheinen hier.",
|
||||
|
||||
"profileTitle": "Profil",
|
||||
"profileSettingsTooltip": "Einstellungen",
|
||||
"profileIdentified": "Dein Fortschritt wird in deinem Konto gespeichert.",
|
||||
"profileAnonymous": "Dein Fortschritt wird auf diesem Gerät gespeichert. Melde dich an, um ihn zu behalten.",
|
||||
"profileSignInWithGitHub": "Mit GitHub anmelden",
|
||||
"profileSignInFailed": "Anmeldung mit GitHub fehlgeschlagen.",
|
||||
"profileContinueInBrowser": "Fahre in deinem Browser fort.",
|
||||
"profileSwitchTitle": "Zu deinem GitHub-Konto wechseln?",
|
||||
"profileSwitchMessage": "Dieses GitHub-Konto hat bereits Fortschritt von einem anderen Gerät. Die Lernsets auf diesem Gerät lassen sich noch nicht damit zusammenführen: Wenn du wechselst, werden sie gelöscht.",
|
||||
"profileSwitchStay": "Anonym bleiben",
|
||||
"profileSwitchConfirm": "Wechseln",
|
||||
|
||||
"settingsTitle": "Einstellungen",
|
||||
"settingsAccount": "Konto",
|
||||
"settingsSignOut": "Abmelden",
|
||||
"settingsSignOutTitle": "Abmelden?",
|
||||
"settingsSignOutAnonymousMessage": "Bei diesem Konto kannst du dich nicht wieder anmelden, deshalb wird es beim Abmelden samt Fortschritt gelöscht.",
|
||||
"settingsDeleteAccount": "Konto löschen",
|
||||
"settingsDeleteTitle": "Dein Konto löschen?",
|
||||
"settingsDeleteMessage": "Alles, was du hast, also deine Lernsets, Karten und Wiederholungen, wird auf dem Server und auf all deinen Geräten gelöscht. Dies kann nicht rückgängig gemacht werden.",
|
||||
"settingsAbout": "Über Halkyon Learn",
|
||||
"aboutLegalese": "Freie Software: Die App steht unter der GNU Affero General Public License, Version 3 oder jeder späteren Version, der Server unter der Server Side Public License, Version 1.",
|
||||
"aboutSourceCode": "Der Quellcode dessen, was diese Instanz ausführt:",
|
||||
"aboutSourceUnreachable": "Diese Instanz war nicht erreichbar, um nach ihrem Quellcode zu fragen.",
|
||||
"settingsAppearance": "Darstellung",
|
||||
"settingsAppearanceDevice": "Gerätedesign",
|
||||
"settingsAppearanceLight": "Hell",
|
||||
"settingsAppearanceDark": "Dunkel",
|
||||
"settingsLanguage": "Sprache",
|
||||
"settingsLanguageDevice": "Gerätesprache",
|
||||
"settingsVersion": "Version {version} (Build {build})",
|
||||
"syncTitle": "Synchronisierung",
|
||||
"syncStateSyncing": "Wird synchronisiert…",
|
||||
"syncStateSynced": "Alles synchronisiert",
|
||||
"syncStateNotYet": "Noch nicht synchronisiert",
|
||||
"syncStateFailing": "Synchronisierung schlägt fehl",
|
||||
"syncStateMismatch": "Diese App kann nicht mit {server} synchronisieren",
|
||||
"syncMismatchDetail": "Aktualisiere die App. Ist sie schon aktuell, muss der Server aktualisiert werden. Was du bis dahin tust, bleibt auf diesem Gerät und wird danach synchronisiert.",
|
||||
"syncLastCompleted": "Zuletzt abgeschlossen um {time}",
|
||||
"syncNoRunYet": "Seit dem Start der App wurde noch keine Synchronisierung abgeschlossen",
|
||||
"syncLastFailed": "Letzter Versuch fehlgeschlagen um {time}",
|
||||
"syncNow": "Jetzt synchronisieren",
|
||||
"syncTroubleLabel": "Synchronisierung benötigt Aufmerksamkeit",
|
||||
"syncMismatchBanner": "Diese App kann nicht mehr mit {server} synchronisieren. Aktualisiere sie, um weiter zu synchronisieren.",
|
||||
"syncMismatchMore": "Mehr",
|
||||
|
||||
"tagsReadFailed": "Die Tags konnten nicht gelesen werden.",
|
||||
"tagPickerTitle": "Tags",
|
||||
"tagPickerManage": "Tags verwalten",
|
||||
"tagPickerSearchLabel": "Tag suchen oder erstellen",
|
||||
"tagPickerCreate": "„{name}“ erstellen",
|
||||
"tagSheetCreateTitle": "Tag erstellen",
|
||||
"tagSheetEditTitle": "Tag bearbeiten",
|
||||
"tagSheetNameLabel": "Name",
|
||||
"tagSheetNameHelper": "Wird klein geschrieben gespeichert und angezeigt.",
|
||||
"tagSheetNameExists": "Den Tag „{name}“ gibt es bereits.",
|
||||
"tagSheetNameMerges": "Es gibt bereits einen Tag „{name}“: Beim Speichern wird dieser mit ihm zusammengeführt.",
|
||||
"tagSheetColor": "Farbe",
|
||||
"tagSheetCreate": "Erstellen",
|
||||
"tagSheetSave": "Speichern",
|
||||
"tagManageTitle": "Tags verwalten",
|
||||
"tagManageEmpty": "Noch keine Tags. Füge unten einen hinzu.",
|
||||
"tagManageAdd": "Tag hinzufügen",
|
||||
"tagManageRow": "{name} ({count})",
|
||||
"tagManageEdit": "Tag bearbeiten",
|
||||
"tagManageDelete": "Tag löschen",
|
||||
"tagDeleteTitle": "Tag „{name}“ löschen?",
|
||||
"tagDeleteMessage": "{count, plural, =0{Er ist an keiner Karteikarte.} =1{Er wird auf all deinen Geräten von 1 Karteikarte entfernt.} other{Er wird auf all deinen Geräten von {count} Karteikarten entfernt.}}",
|
||||
"tagDeleteConfirm": "Löschen",
|
||||
"tagDeleted": "Tag „{name}“ gelöscht",
|
||||
"studySettingsTitle": "Sitzungseinstellungen",
|
||||
"studySettingsFilterByTags": "Nach Tags filtern",
|
||||
"studySettingsFilterHint": "Lerne nur die Karteikarten mit mindestens einem der gewählten Tags.",
|
||||
"studySettingsNoTags": "Noch keine Tags. Füge deinen Karteikarten Tags hinzu, um sie hier zu filtern.",
|
||||
"studySettingsReset": "Zurücksetzen",
|
||||
"studySettingsSave": "Speichern",
|
||||
"cardTagsLabel": "Tags",
|
||||
"cardTagsAdd": "Tags hinzufügen",
|
||||
"cardTagsEdit": "Tags bearbeiten",
|
||||
"cardFilterTooltip": "Filter",
|
||||
"cardFilterTitle": "Filter",
|
||||
"cardFilterTag": "Tag",
|
||||
"cardFilterAllTags": "Alle Tags",
|
||||
"cardFilterNoTags": "Keine dieser Karteikarten hat bisher einen Tag.",
|
||||
"cardFilterClear": "Alles zurücksetzen",
|
||||
"cardFilterApply": "Anwenden",
|
||||
"cardFilterNothing": "Keine Karteikarte hat diesen Tag.",
|
||||
"cardListFailed": "Die Karten konnten nicht gelesen werden.",
|
||||
"cardListMore": "Mehr",
|
||||
"cardListManageTags": "Tags verwalten",
|
||||
|
||||
"composerTitle": "Karteikarten erstellen",
|
||||
"composerTitleFor": "Karteikarten erstellen für „{title}“",
|
||||
"composerEditTitle": "Karteikarten bearbeiten",
|
||||
"composerEditTitleFor": "Karteikarten bearbeiten in „{title}“",
|
||||
"composerSaving": "Wird gespeichert…",
|
||||
"composerSaveFailed": "Speichern fehlgeschlagen – erneut versuchen",
|
||||
"composerAllSaved": "Alle Änderungen gespeichert",
|
||||
"composerNewFlashcard": "Neue Karteikarte",
|
||||
"composerCollapseStack": "Stapel einklappen",
|
||||
"composerExpandStack": "Stapel ausklappen",
|
||||
"composerStatusSaving": "Wird gespeichert",
|
||||
"composerStatusSaved": "Gespeichert",
|
||||
"composerStatusFailed": "Speichern fehlgeschlagen",
|
||||
"composerSaveFailedInline": "Speichern fehlgeschlagen.",
|
||||
"composerRetry": "Erneut versuchen",
|
||||
"composerEmptyPane": "Füge eine Karteikarte hinzu, um loszulegen.",
|
||||
"composerKindFlashcard": "Karteikarte",
|
||||
"composerKindVocabulary": "Vokabel",
|
||||
"composerKindMatching": "Zuordnung",
|
||||
"composerKindMap": "Landkarte",
|
||||
"composerLabelFront": "Vorderseite",
|
||||
"composerLabelQuestion": "Frage",
|
||||
"composerLabelExpression": "Ausdruck",
|
||||
"composerLabelPrompt": "Aufgabe",
|
||||
"composerLabelBack": "Rückseite",
|
||||
"composerLabelAnswers": "Antworten",
|
||||
"composerLabelAnswer": "Antwort",
|
||||
"composerPlaceholderFront": "Frage hinzufügen",
|
||||
"composerPlaceholderExpression": "Ausdruck hinzufügen",
|
||||
"composerPlaceholderPrompt": "Aufgabe hinzufügen",
|
||||
"composerAddAnswer": "Antwort hinzufügen",
|
||||
"composerCorrectAnswer": "Richtige Antwort",
|
||||
"composerWrongAnswer": "Falsche Antwort",
|
||||
"composerRemoveAnswer": "Antwort entfernen",
|
||||
"composerPin": "{field} anheften: für die nächste Karte behalten",
|
||||
"composerUnpin": "{field} nicht mehr anheften: für die nächste Karte leeren",
|
||||
"composerTags": "Tags",
|
||||
"composerAddTags": "Tags hinzufügen",
|
||||
"composerMoreActions": "Weitere Aktionen",
|
||||
"composerDuplicate": "Duplizieren",
|
||||
"composerClearFields": "Felder leeren",
|
||||
"composerDelete": "Löschen",
|
||||
"composerDeleteTitle": "Diese Karteikarte löschen?",
|
||||
"composerDeleteDraftMessage": "Der Entwurf wird aus dieser Sitzung entfernt. Er wurde noch nicht gespeichert.",
|
||||
"composerDeleteSavedMessage": "Die Karteikarte wird auf all deinen Geräten aus dem Lernset entfernt.",
|
||||
"composerDiscardTitle": "{count, plural, =1{Nicht gespeicherte Karteikarte verwerfen?} other{Nicht gespeicherte Karteikarten verwerfen?}}",
|
||||
"composerDiscardMessage": "{count, plural, =1{1 Karteikarte wurde noch nicht gespeichert. Wenn du jetzt gehst, wird sie verworfen.} other{{count} Karteikarten wurden noch nicht gespeichert. Wenn du jetzt gehst, werden sie verworfen.}}",
|
||||
"composerDiscardConfirm": "Verwerfen",
|
||||
"composerConflict": "Diese Karteikarte wurde auf zwei Geräten unterschiedlich geändert. Wähle unten für jeden Teil die Version, die du behalten möchtest.",
|
||||
"composerKeepThis": "Diese behalten",
|
||||
"formatAlignLeft": "Linksbündig",
|
||||
"formatAlignCenter": "Zentriert",
|
||||
"formatAlignRight": "Rechtsbündig",
|
||||
"formatBold": "Fett",
|
||||
"formatItalic": "Kursiv",
|
||||
"formatUnderline": "Unterstrichen",
|
||||
"formatSuperscript": "Hochgestellt",
|
||||
"formatSubscript": "Tiefgestellt",
|
||||
"vocabularyReading": "Lesung",
|
||||
"vocabularyReadingHint": "Kana-Lesung",
|
||||
"vocabularyMeanings": "Bedeutungen",
|
||||
"vocabularyPrimaryMeaningHint": "Hauptbedeutung",
|
||||
"vocabularyMeaningHint": "Bedeutung",
|
||||
"vocabularyAddMeaning": "Bedeutung hinzufügen",
|
||||
"vocabularyRemoveMeaning": "Bedeutung entfernen",
|
||||
"matchingCategories": "Kategorien",
|
||||
"matchingCategoriesHint": "Die Ablagekarten, auf die Lernende Elemente ziehen. Jede Kategorie braucht mindestens ein Element, daher kann eine Karte nicht mehr Kategorien als Elemente haben.",
|
||||
"matchingCategory": "Kategorie {number}",
|
||||
"matchingAddCategory": "Kategorie hinzufügen",
|
||||
"matchingAddCategoryBlocked": "Füge zuerst ein Element hinzu: Eine Karte kann nicht mehr Kategorien als Elemente haben",
|
||||
"matchingRemoveCategory": "Kategorie entfernen",
|
||||
"matchingMinCategories": "Mindestens zwei Kategorien erforderlich",
|
||||
"matchingItems": "Elemente",
|
||||
"matchingItemsHint": "Die ziehbaren Elemente. Ordne jedes Element seiner richtigen Kategorie zu: Ein nicht zugeordnetes Element kann nie richtig platziert werden.",
|
||||
"matchingItem": "Element {number}",
|
||||
"matchingAddItem": "Element hinzufügen",
|
||||
"matchingUnassigned": "Nicht zugeordnet",
|
||||
"matchingRemoveItem": "Element entfernen",
|
||||
"matchingMinItems": "Mindestens zwei Elemente erforderlich",
|
||||
|
||||
"cardTileOptions": "Optionen",
|
||||
"cardTileEdit": "Bearbeiten",
|
||||
"cardTileDelete": "Löschen",
|
||||
"cardTileUntitled": "Unbenannt",
|
||||
"cardTileConflict": "Auf zwei Geräten geändert",
|
||||
|
||||
"studyClose": "Sitzung schließen",
|
||||
"studyBackFirstOn": "Rückseite der Karten wird zuerst gezeigt: tippe zum Zurückschalten",
|
||||
"studyBackFirstOff": "Rückseite der Karten zuerst zeigen",
|
||||
"studyShowAnswer": "Antwort anzeigen",
|
||||
"studyCheck": "Prüfen",
|
||||
"studyRatingBad": "Schlecht",
|
||||
"studyRatingOk": "OK",
|
||||
"studyRatingGood": "Gut",
|
||||
"studyRatingPerfect": "Perfekt",
|
||||
"studyNothingTitle": "Gerade nichts zu lernen",
|
||||
"studyNothingBody": "Komm später wieder, wenn mehr Karten fällig sind.",
|
||||
"studyNextDue": "Bis zur nächsten fälligen Karte: {interval}.",
|
||||
"studyDone": "Fertig",
|
||||
"studyRoundDone": "Runde abgeschlossen",
|
||||
"studyContinue": "Weiterlernen",
|
||||
"studyPrevious": "Vorherige Karte",
|
||||
"studyNext": "Nächste Karte",
|
||||
"studyReading": "Lesung",
|
||||
"studyMeanings": "Bedeutungen",
|
||||
"studyPlaceItems": "Ziehe jedes Element auf seine Kategorie oder tippe auf ein Element und dann auf eine Kategorie.",
|
||||
"studyMinutes": "{count} Min.",
|
||||
"studyHours": "{count} Std.",
|
||||
"studyDays": "{count, plural, =1{1 Tag} other{{count} Tage}}",
|
||||
"studyMonths": "{count, plural, =1{1 Monat} other{{count} Monate}}",
|
||||
"studyYears": "{count, plural, =1{1 Jahr} other{{count} Jahre}}",
|
||||
"studyFilteredBy": "{count, plural, =1{Eingegrenzt durch 1 Tag-Filter} other{Eingegrenzt durch {count} Tag-Filter}}",
|
||||
"studySettingsCardsPerRound": "Karten pro Runde",
|
||||
"studySettingsNewPerDay": "Neue Karten pro Tag",
|
||||
"studySettingsNewPerDayHint": "Wie viele noch nie gelernte Karten jedes Lernset pro Tag neu beginnt. Fällige Wiederholungen werden nie begrenzt.",
|
||||
"studySettingsUnlimited": "Unbegrenzt",
|
||||
"studySettingsSortBy": "Sortieren nach",
|
||||
"studySettingsLearnMode": "Lernmodus",
|
||||
"studyModeSpaced": "Verteilte Wiederholung",
|
||||
"studyModeSpacedHint": "Fällige Karten nach ihrem Zeitplan; Bewertungen verschieben sie.",
|
||||
"studyModeCram": "Pauken",
|
||||
"studyModeCramHint": "Geh jede Karte durch, ohne die Zeitpläne zu ändern.",
|
||||
"studySortOldestFirst": "Älteste zuerst",
|
||||
"studySortRandom": "Zufällig",
|
||||
"studySortNewestFirst": "Neueste zuerst",
|
||||
"studyFewer": "Weniger",
|
||||
"studyMore": "Mehr",
|
||||
|
||||
"studysetMenuReset": "Fortschritt zurücksetzen",
|
||||
"studysetResetTitle": "Fortschritt zurücksetzen?",
|
||||
"studysetResetMessage": "Jede Karteikarte darin und in den enthaltenen Lernsets beginnt auf all deinen Geräten wieder als neu. Dein Lernverlauf bleibt erhalten. Du kannst dies für einige Sekunden rückgängig machen.",
|
||||
"studysetResetConfirm": "Zurücksetzen",
|
||||
"studysetResetDone": "Fortschritt zurückgesetzt",
|
||||
|
||||
"mapUnknown": "Diese Landkarte braucht eine neuere Version der App.",
|
||||
"mapPickRegion": "Tippe auf die Region, die die Frage beantwortet; zoome mit zwei Fingern oder durch Scrollen.",
|
||||
"mapNonePicked": "Noch keine Region gewählt",
|
||||
"composerPlaceholderQuestion": "Frage hinzufügen",
|
||||
"composerLabelRegion": "Antwort",
|
||||
|
||||
"homeStreakTooltip": "Lernserie",
|
||||
"homeArrange": "Widgets anordnen",
|
||||
"homeDone": "Fertig",
|
||||
"homeDragToReorder": "Zum Umsortieren ziehen",
|
||||
"homeShowWidget": "{label} anzeigen",
|
||||
"homeHideWidget": "{label} ausblenden",
|
||||
"homeAllHidden": "Alle Widgets sind ausgeblendet",
|
||||
"homeAllHiddenHint": "Halte hier lange gedrückt, um welche zurückzuholen.",
|
||||
"homeWidgetGoal": "Lernziel",
|
||||
"homeWidgetWeek": "Wochenübersicht",
|
||||
"homeWidgetActivity": "Aktivitäts-Heatmap",
|
||||
"homeActivityTitle": "Deine Aktivität",
|
||||
"streakDays": "{count, plural, =1{1 Tag in Folge} other{{count} Tage in Folge}}",
|
||||
"streakStart": "Lerne heute, um deine Lernserie zu starten.",
|
||||
"streakGrow": "Bleib dran: Lerne mindestens einmal am Tag, um deine Lernserie auszubauen.",
|
||||
"streakFirstDay": "Wiederhole heute ein paar Karten, um den ersten Tag zu entfachen.",
|
||||
"streakComeBack": "Komm morgen wieder, um deine Lernserie zu halten.",
|
||||
"streakKeepAlive": "Lerne heute, um deine Lernserie am Leben zu halten.",
|
||||
"goalEditTitle": "Lernziel bearbeiten",
|
||||
"goalSetTitle": "Lernziel festlegen",
|
||||
"goalDescriptionDaily": "Wiederhole jeden Tag eine bestimmte Anzahl Karteikarten. Jede Karte, die du bewertest, zählt.",
|
||||
"goalDescriptionWeekly": "Wiederhole jede Woche ab Montag eine bestimmte Anzahl Karteikarten. Jede Karte, die du bewertest, zählt.",
|
||||
"goalPeriod": "Zeitraum",
|
||||
"goalPerDay": "Pro Tag",
|
||||
"goalPerWeek": "Pro Woche",
|
||||
"goalCardsPerDay": "Karten pro Tag",
|
||||
"goalCardsPerWeek": "Karten pro Woche",
|
||||
"goalSave": "Ziel speichern",
|
||||
"goalRemove": "Ziel entfernen",
|
||||
"goalEmptySubtitle": "Wiederhole jeden Tag oder jede Woche eine feste Anzahl Karten.",
|
||||
"goalDailyHeadline": "Tagesziel",
|
||||
"goalWeeklyHeadline": "Wochenziel",
|
||||
"goalProgressToday": "{reviewed} von {target} Karten heute",
|
||||
"goalProgressThisWeek": "{reviewed} von {target} Karten diese Woche",
|
||||
"goalMet": "Ziel erreicht: gut gemacht!",
|
||||
"goalRemaining": "Noch {count}",
|
||||
"weekToday": "Heute",
|
||||
"weekPreviousDay": "Vorheriger Tag",
|
||||
"weekNextDay": "Nächster Tag",
|
||||
"weekRestDay": "Ruhetag",
|
||||
"weekDueCount": "{count, plural, =1{1 Karte fällig} other{{count} Karten fällig}}",
|
||||
"heatmapLess": "Weniger",
|
||||
"heatmapMore": "Mehr",
|
||||
"heatmapTotal": "{count, plural, =1{1 Aktivität im letzten Jahr} other{{count} Aktivitäten im letzten Jahr}}",
|
||||
"heatmapDay": "{count, plural, =0{Nichts} =1{1 Aktivität} other{{count} Aktivitäten}} · {date}",
|
||||
|
||||
"commonCancel": "Abbrechen",
|
||||
"commonSave": "Speichern",
|
||||
"commonClose": "Schließen",
|
||||
"commonUndo": "Rückgängig",
|
||||
"commonTryAgain": "Erneut versuchen",
|
||||
"personDataFailed": "Die Daten dieses Geräts konnten nicht geöffnet werden.",
|
||||
"sessionUnreachable": "Der Server war nicht erreichbar.",
|
||||
"notFoundGone": "Das gibt es nicht mehr. Vielleicht wurde es auf einem anderen Gerät gelöscht.",
|
||||
"fileImage": "Bild",
|
||||
"fileSound": "Audio",
|
||||
"fileImageUnavailable": "Dieses Bild ist noch nicht auf diesem Gerät. Es erscheint, sobald es hochgeladen ist und du online bist.",
|
||||
"fileImageUnreadable": "Dieses Bild kann nicht angezeigt werden. Die Datei ist vielleicht beschädigt oder in einem Format, das dieses Gerät nicht lesen kann.",
|
||||
"fileFromElsewhere": "Dateien aus dem Web werden nicht angezeigt.",
|
||||
"filePlaySound": "Audio abspielen",
|
||||
"fileStopSound": "Audio stoppen",
|
||||
"fileSoundUnavailable": "Diese Audiodatei ist noch nicht auf diesem Gerät. Du kannst sie abspielen, sobald sie hochgeladen ist und du online bist.",
|
||||
"fileSoundUnplayable": "Diese Audiodatei kann nicht abgespielt werden. Die Datei ist vielleicht beschädigt oder in einem Format, das dieses Gerät nicht abspielen kann.",
|
||||
"fileImages": "Bilder",
|
||||
"fileSounds": "Audiodateien",
|
||||
"fileNotAnImage": "Diese Datei ist kein Bild, das die App anzeigen kann.",
|
||||
"fileNotASound": "Diese Datei ist keine Audiodatei, die die App abspielen kann.",
|
||||
"fileTooLarge": "Diese Datei ist größer als {megabytes} MB.",
|
||||
"fileNotKept": "Die Datei konnte nicht auf diesem Gerät gespeichert werden.",
|
||||
"fileRemoveImage": "Bild entfernen",
|
||||
"fileRemoveSound": "Audio entfernen",
|
||||
"editorInsert": "Einfügen",
|
||||
"blockGroupText": "Text",
|
||||
"blockGroupBlocks": "Blöcke",
|
||||
"blockGroupMedia": "Medien",
|
||||
"blockHeading": "Überschrift {level}",
|
||||
"blockBulletedList": "Aufzählung",
|
||||
"blockNumberedList": "Nummerierte Liste",
|
||||
"blockChecklist": "Checkliste",
|
||||
"blockQuote": "Zitat",
|
||||
"blockListItem": "Listenpunkt",
|
||||
"blockChecklistItem": "Aufgabe",
|
||||
"editorSlashHint": "Tippe / für einen Block oder schreib einfach los",
|
||||
"blockDivider": "Trennlinie",
|
||||
"blockTable": "Tabelle",
|
||||
"blockRemove": "Entfernen",
|
||||
"blockUnknown": "Dieser Teil braucht eine neuere Version der App.",
|
||||
"blockCallout": "Hinweisbox",
|
||||
"blockSpoiler": "Spoiler",
|
||||
"calloutNote": "Notiz",
|
||||
"calloutInfo": "Info",
|
||||
"calloutTip": "Tipp",
|
||||
"calloutSuccess": "Erfolg",
|
||||
"calloutQuestion": "Frage",
|
||||
"calloutWarning": "Warnung",
|
||||
"calloutDanger": "Gefahr",
|
||||
"calloutQuote": "Zitat",
|
||||
"calloutOptions": "Hinweisbox-Optionen",
|
||||
"calloutEditTitle": "Titel bearbeiten…",
|
||||
"calloutFoldedAtFirst": "Anfangs eingeklappt",
|
||||
"calloutRemove": "Hinweisbox entfernen",
|
||||
"calloutShow": "Einblenden",
|
||||
"calloutHide": "Ausblenden",
|
||||
"calloutWriteInside": "Hineinschreiben",
|
||||
"calloutTitleDialog": "Titel der Hinweisbox",
|
||||
"calloutTitleLabel": "Titel",
|
||||
"calloutTitleHelper": "Lass ihn leer, um den Namen des Typs anzuzeigen.",
|
||||
"spoilerReveal": "Aufdecken",
|
||||
"spoilerPlaceholder": "Versteckter Text, per Tippen aufgedeckt",
|
||||
"blockFormula": "Formel",
|
||||
"formatInlineMath": "Formel in der Zeile",
|
||||
"formulaTitle": "Formel",
|
||||
"formulaInLineTitle": "Formel in der Zeile",
|
||||
"formulaPreview": "Vorschau",
|
||||
"formulaPreviewEmpty": "Hier erscheint die Formel",
|
||||
"formulaLatex": "LaTeX",
|
||||
"formulaLatexHelper": "\\\\ beginnt eine neue Zeile in aligned, cases oder einer Matrix.",
|
||||
"formulaEmpty": "Eine leere Formel. Tippe, um sie zu schreiben.",
|
||||
"formulaEdit": "Formel bearbeiten",
|
||||
"formulaRemove": "Formel entfernen",
|
||||
"blockYoutube": "YouTube-Video",
|
||||
"youtubeLink": "YouTube-Link",
|
||||
"youtubeLinkHelper": "Füge einen Link von YouTube ein.",
|
||||
"youtubeNotFound": "In diesem Link ist kein YouTube-Video.",
|
||||
"youtubeFound": "Video gefunden.",
|
||||
"youtubeFoundFrom": "Video gefunden, Start bei {time}.",
|
||||
"youtubeCaption": "Beschriftung",
|
||||
"youtubeCaptionHelper": "Wird anstelle des Videos angezeigt, bis es abgespielt wird.",
|
||||
"youtubeVideo": "YouTube-Video",
|
||||
"youtubeSource": "YouTube",
|
||||
"youtubeFrom": "YouTube · ab {time}",
|
||||
"youtubeEdit": "Video bearbeiten",
|
||||
"youtubePlay": "Video abspielen",
|
||||
"youtubeOpen": "Auf YouTube öffnen",
|
||||
"youtubeRemove": "Video entfernen",
|
||||
"youtubeBroken": "Der Link dieses Videos kann nicht gelesen werden.",
|
||||
"youtubeNotOpened": "YouTube konnte nicht geöffnet werden.",
|
||||
"blockStudyButton": "Lernbutton",
|
||||
"studyButtonDefaultLabel": "Lernen",
|
||||
"studyButtonStudyset": "Lernset",
|
||||
"studyButtonTag": "Karten",
|
||||
"studyButtonAllCards": "Alle Karten",
|
||||
"studyButtonLabel": "Beschriftung",
|
||||
"studyButtonTopics": "Mit seinen Themen",
|
||||
"studyButtonBackFirst": "Rückseite zuerst",
|
||||
"studyButtonBackFirstHelper": "Karten mit Vorder- und Rückseite sowie Vokabelkarten fragen ihre Rückseite ab.",
|
||||
"studyButtonWithTag": "Mit Tag {tag}",
|
||||
"studyButtonAlone": "Ohne seine Themen",
|
||||
"studyButtonEdit": "Lernbutton bearbeiten",
|
||||
"studyButtonRemove": "Lernbutton entfernen",
|
||||
"studyButtonBroken": "Dieser Lernbutton gibt nicht an, was gelernt werden soll.",
|
||||
"studyButtonGone": "Dieses Lernset gibt es nicht mehr.",
|
||||
"pdfPageField": "PDF-Seite",
|
||||
"studysetTabFiles": "Dateien",
|
||||
"pdfUploadMenu": "PDF hochladen",
|
||||
"pdfFileType": "PDFs",
|
||||
"pdfAdded": "„{title}“ hinzugefügt.",
|
||||
"pdfNotPdf": "Diese Datei ist kein PDF.",
|
||||
"pdfTooLarge": "Dieses PDF ist größer als {megabytes} MB.",
|
||||
"pdfDuplicateTitle": "PDF schon vorhanden",
|
||||
"pdfDuplicateMessage": "Diese Datei ist „{title}“ und liegt schon in diesem Lernset. Als Kopie hinzufügen oder das vorhandene behalten?",
|
||||
"pdfDuplicateKeep": "Vorhandenes behalten",
|
||||
"pdfDuplicateAdd": "Kopie hinzufügen",
|
||||
"filesEmpty": "Noch keine Dateien",
|
||||
"filesEmptyHint": "Nutze „Material hinzufügen → PDF hochladen“, um eines hinzuzufügen.",
|
||||
"filesSort": "Sortieren",
|
||||
"filesSortName": "Name (A–Z)",
|
||||
"filesSortNewest": "Neueste zuerst",
|
||||
"filesSortOldest": "Älteste zuerst",
|
||||
"filesSortLargest": "Größte zuerst",
|
||||
"pdfPages": "{count, plural, =1{1 Seite} other{{count} Seiten}}",
|
||||
"pdfPercentRead": "{percent} % gelesen",
|
||||
"pdfDeadlineMet": "Lesefrist eingehalten",
|
||||
"pdfDeadlineUpcoming": "Beginnt in {days, plural, =1{1 Tag} other{{days} Tagen}} – {pages}/Tag geplant",
|
||||
"pdfDeadlinePassed": "Frist abgelaufen – noch {pages, plural, =1{1 Seite} other{{pages} Seiten}}",
|
||||
"pdfDeadlineToday": "Heute fällig – noch {pages, plural, =1{1 Seite} other{{pages} Seiten}}",
|
||||
"pdfDeadlinePace": "{pages}/Tag, um in {days, plural, =1{1 Tag} other{{days} Tagen}} Seite {target} zu erreichen",
|
||||
"pdfOptions": "Aktionen",
|
||||
"pdfRename": "Umbenennen",
|
||||
"pdfRenameTitle": "PDF umbenennen",
|
||||
"pdfRenameLabel": "Titel",
|
||||
"pdfSetDeadline": "Lesefrist festlegen",
|
||||
"pdfEditDeadline": "Lesefrist bearbeiten",
|
||||
"pdfMarkFinished": "Als abgeschlossen markieren",
|
||||
"pdfResetReading": "Lesefortschritt zurücksetzen",
|
||||
"pdfResetTitle": "Lesefortschritt zurücksetzen?",
|
||||
"pdfResetMessage": "Wie weit du „{title}“ gelesen hast, wird vergessen. Das PDF, seine Karteikarten und Notizen bleiben.",
|
||||
"pdfResetConfirm": "Zurücksetzen",
|
||||
"pdfMoreInfo": "Weitere Infos",
|
||||
"pdfInfoFileName": "Dateiname",
|
||||
"pdfInfoSize": "Größe",
|
||||
"pdfInfoPages": "Seiten",
|
||||
"pdfInfoUnknown": "Bekannt, sobald es geöffnet wurde",
|
||||
"pdfDelete": "Löschen",
|
||||
"pdfDeleteTitle": "PDF löschen?",
|
||||
"pdfDeleteMessage": "„{title}“ wird samt Lesefortschritt aus diesem Lernset entfernt. Karteikarten und Notizen, die daraus entstanden sind, bleiben.",
|
||||
"pdfDeleteConfirm": "Löschen",
|
||||
"pdfDeleted": "PDF gelöscht",
|
||||
"deadlineSheetSet": "Lesefrist festlegen",
|
||||
"deadlineSheetEdit": "Lesefrist bearbeiten",
|
||||
"deadlineSheetHelp": "Erreiche bis zu einem Tag eine bestimmte Seite. Der Fortschritt folgt der weitesten Seite, die du beim Lesen erreichst.",
|
||||
"deadlineReachPageOf": "Seite erreichen (von {count})",
|
||||
"deadlineReachPage": "Seite erreichen",
|
||||
"deadlineDecrease": "Verringern",
|
||||
"deadlineIncrease": "Erhöhen",
|
||||
"deadlineChapter": "Oder ein Kapitel abschließen",
|
||||
"deadlineChapterEntry": "{title} · bis S. {page}",
|
||||
"deadlineBy": "Bis",
|
||||
"deadlineStart": "Beginn am",
|
||||
"deadlineStartToday": "Heute",
|
||||
"deadlineStartReset": "Heute beginnen",
|
||||
"deadlineStartHelp": "Das Tagespensum zählt ab diesem Tag. Bis dahin wartet das PDF unter „Demnächst“.",
|
||||
"deadlineSave": "Frist speichern",
|
||||
"deadlineRemove": "Frist entfernen",
|
||||
"readerMissing": "Dieses PDF ist noch nicht auf diesem Gerät. Es öffnet sich, sobald das Gerät, das es hinzugefügt hat, es hochgeladen hat und du online bist.",
|
||||
"readerUnreadable": "Diese Datei kann nicht als PDF gelesen werden.",
|
||||
"readerBack": "Zurück zum Lernset",
|
||||
"readerSearch": "PDF durchsuchen",
|
||||
"readerFitWidth": "An Breite anpassen",
|
||||
"readerFitHeight": "An Höhe anpassen",
|
||||
"readerZoomIn": "Vergrößern",
|
||||
"readerZoomOut": "Verkleinern",
|
||||
"readerSetZoom": "Zoom festlegen",
|
||||
"readerZoomTitle": "Zoom",
|
||||
"readerZoomApply": "Anwenden",
|
||||
"readerFullscreen": "Vollbild",
|
||||
"readerExitFullscreen": "Vollbild verlassen",
|
||||
"readerPreviousPage": "Vorherige Seite",
|
||||
"readerNextPage": "Nächste Seite",
|
||||
"readerPageOf": "/ {count}",
|
||||
"readerFindHint": "Im PDF suchen",
|
||||
"readerMatchOf": "{current} von {total}",
|
||||
"readerPreviousMatch": "Vorheriger Treffer",
|
||||
"readerNextMatch": "Nächster Treffer",
|
||||
"readerCloseSearch": "Suche schließen",
|
||||
"readerExpand": "Ausklappen",
|
||||
"readerCollapse": "Einklappen",
|
||||
"readerTabFlashcards": "Karteikarten",
|
||||
"readerTabNotes": "Notizen",
|
||||
"readerCardCreated": "Karteikarte erstellt",
|
||||
"readerCardDeleteTitle": "Karteikarte löschen?",
|
||||
"readerCardDeleteMessage": "Die Karteikarte wird samt ihrem Lernverlauf gelöscht.",
|
||||
"readerCardDeleteConfirm": "Löschen",
|
||||
"readerFrontMatter": "Vorspann",
|
||||
"readerPage": "Seite {page}",
|
||||
"readerByPage": "Seite",
|
||||
"readerByChapter": "Kapitel",
|
||||
"readerCardsEmpty": "Wähle Text aus, um im Nu Karteikarten zu erstellen",
|
||||
"readerCardsEmptyHint": "Wähle die wichtigen Zeilen aus und schicke sie in eine Frage oder eine Antwort, oder nutze „Karteikarte erstellen“.",
|
||||
"readerCreateCard": "Karteikarte erstellen",
|
||||
"readerEditCard": "Karteikarte bearbeiten",
|
||||
"readerBackToCards": "Zurück zu den Karteikarten",
|
||||
"readerReferencedPage": "Referenzierte Seite: {page}",
|
||||
"readerUseCurrentPage": "Aktuelle verwenden ({page})",
|
||||
"readerQuestion": "Frage",
|
||||
"readerAnswer": "Antwort",
|
||||
"readerKeepOpen": "Erstellungsmodus geöffnet lassen",
|
||||
"readerClear": "Leeren",
|
||||
"readerCreate": "Erstellen",
|
||||
"readerUntitledCard": "Unbenannte Karteikarte",
|
||||
"readerCardOptions": "Kartenoptionen",
|
||||
"readerNoteSaved": "Gespeichert",
|
||||
"readerNoteEditing": "Wird bearbeitet…",
|
||||
"readerNoteSaving": "Wird gespeichert…",
|
||||
"readerNoteFailed": "Speichern fehlgeschlagen",
|
||||
"readerToQuestion": "Frage",
|
||||
"readerToAnswer": "Antwort",
|
||||
"readerToNote": "Notizen",
|
||||
"readerLineBreaksJoined": "Zeilenumbrüche zusammengeführt (tippen zum Beibehalten)",
|
||||
"readerLineBreaksKept": "Zeilenumbrüche beibehalten (tippen zum Zusammenführen)",
|
||||
"blockScreenshot": "Bildschirmfoto der Seite",
|
||||
"readerCaptureCancel": "Bildschirmfoto abbrechen",
|
||||
"readerCaptureHint": "Ziehe über den Teil der Seite, den du behalten willst",
|
||||
"readerCaptureFailed": "Die Seite konnte nicht dargestellt werden. Versuche es erneut, sobald sie geladen ist.",
|
||||
"readerScreenshotSaved": "Bildschirmfoto gespeichert",
|
||||
"imageOpenPdfPage": "PDF auf dieser Seite öffnen",
|
||||
"homeWidgetDeadlines": "Lesefristen",
|
||||
"deadlinesTitle": "Lesefristen",
|
||||
"deadlinesCurrent": "Aktuell",
|
||||
"deadlinesUpcoming": "Demnächst",
|
||||
"deadlinesProgress": "S. {furthest}/{target} · {when}",
|
||||
"deadlinePassedShort": "Frist abgelaufen",
|
||||
"deadlineDueTodayShort": "heute fällig",
|
||||
"deadlineDaysLeft": "{days, plural, =1{noch 1 Tag} other{noch {days} Tage}}",
|
||||
"deadlineToPageToday": "heute bis S. {page}",
|
||||
"deadlineStartsIn": "Beginnt in {days, plural, =1{1 Tag} other{{days} Tagen}}",
|
||||
"deadlinesPlaceholder": "Erscheint, sobald du Lesefristen hast",
|
||||
"deadlinesOpenTimeline": "Zeitleiste öffnen",
|
||||
"timelineTitle": "Lesefristen-Zeitleiste",
|
||||
"timelineEmpty": "Noch keine Lesefristen",
|
||||
"timelineEmptyHint": "Lege für ein PDF eine Lesefrist fest, um sie hier zu sehen.",
|
||||
"timelineToday": "Heute",
|
||||
"timelinePages": "S. {furthest}/{target}",
|
||||
"chapterFinished": "„{chapter}“ fertig gelesen – die {count, plural, =1{Karte} other{{count} Karten}} lernen, die du erstellt hast?",
|
||||
"chapterStudyNow": "Jetzt lernen",
|
||||
"chapterDismiss": "Schließen",
|
||||
"deadlineChapterLoading": "Kapitel werden geladen…",
|
||||
"fileSizeKb": "{size} KB",
|
||||
"fileSizeMb": "{size} MB",
|
||||
"pdfPageShort": "S. {page}",
|
||||
"ankiImportMenu": "Aus Anki importieren",
|
||||
"ankiImportFileType": "Anki-Stapel",
|
||||
"ankiImportTitle": "Anki-Stapel importieren",
|
||||
"ankiImportOptionsTitle": "Wie sollen Unterstapel importiert werden?",
|
||||
"ankiImportReading": "Stapel wird gelesen…",
|
||||
"ankiImportSummary": "{cards, plural, =1{1 Karte.} other{{cards} Karten.}}",
|
||||
"ankiImportSummaryDecks": "{cards, plural, =1{1 Karte} other{{cards} Karten}} in {decks} Unterstapeln.",
|
||||
"ankiImportModeFlatten": "In dieses Lernset zusammenführen",
|
||||
"ankiImportModeFlattenSubtitle": "Alle Karten hierher importieren und die Unterstapel ignorieren.",
|
||||
"ankiImportModeSubStudysets": "Als Unter-Lernsets nachbilden",
|
||||
"ankiImportModeSubStudysetsSubtitle": "Die Unterstapel nachbilden: Jeder Unterstapel wird ein verschachteltes Lernset.",
|
||||
"ankiImportModeTag": "Karten nach Unterstapel taggen",
|
||||
"ankiImportModeTagSubtitle": "Alle Karten hier behalten, getaggt mit jedem Unterstapel, aus dem sie stammen.",
|
||||
"ankiImportProgress": "Lernfortschritt importieren",
|
||||
"ankiImportProgressSubtitle": "Den Wiederholungsverlauf aus Anki übernehmen: Karten machen dort weiter, wo sie aufgehört haben. Neue Karten bleiben neu.",
|
||||
"ankiImportMedia": "{count, plural, =1{Medien übernehmen (1 Datei)} other{Medien übernehmen ({count} Dateien)}}",
|
||||
"ankiImportMediaSubtitle": "Bilder und Töne des Stapels auf den Karten behalten.",
|
||||
"ankiImportMediaMayNotFit": "Die Dateien belegen {size} MB, mehr als die {left} MB, die dir noch bleiben: Was nicht hineinpasst, bleibt auf diesem Gerät, und deine anderen Geräte zeigen es nicht an.",
|
||||
"ankiImportRunning": "Wird importiert…",
|
||||
"ankiImportConfirm": "Importieren",
|
||||
"ankiImportNotAPackage": "Das ist kein Anki-Stapel. Exportiere in Anki einen Stapel als .apkg-Datei und importiere diese.",
|
||||
"ankiImportNewerFormat": "Dieser Stapel stammt aus einer neueren Anki-Version, die diese App noch nicht kennt. Exportiere ihn erneut mit „Ältere Anki-Versionen unterstützen“.",
|
||||
"ankiImportUnreadable": "Dieser Stapel ist beschädigt und kann nicht gelesen werden.",
|
||||
"ankiImportBrokeOff": "Der Import wurde abgebrochen. Die bis dahin importierten Karten bleiben erhalten.",
|
||||
"ankiImported": "{count, plural, =1{1 Karteikarte aus Anki importiert.} other{{count} Karteikarten aus Anki importiert.}}",
|
||||
"ankiImportedSubStudysets": "{count, plural, =1{1 Karte in Unter-Lernsets importiert.} other{{count} Karten in Unter-Lernsets importiert.}}",
|
||||
"ankiImportedTagged": "{count, plural, =1{1 Karte importiert und nach Unterstapel getaggt.} other{{count} Karten importiert und nach Unterstapel getaggt.}}",
|
||||
"ankiImportFilesSkipped": "{count, plural, =1{1 Mediendatei konnte nicht importiert werden.} other{{count} Mediendateien konnten nicht importiert werden.}}"
|
||||
}
|
||||
|
|
@ -35,14 +35,14 @@
|
|||
"studysetSheetTitleLabel": "Title",
|
||||
"studysetSheetSecondaryLabel": "Secondary title",
|
||||
"studysetSheetSecondaryHelper": "Shown as a small label above the title.",
|
||||
"studysetSheetColor": "Color",
|
||||
"studysetSheetColor": "Colour",
|
||||
"studysetSheetDiscardTitle": "Discard changes?",
|
||||
"studysetSheetDiscardMessage": "You have unsaved changes to this studyset. Discard them and close the form?",
|
||||
"studysetSheetDiscardConfirm": "Discard",
|
||||
|
||||
"paletteColorName": "{name, select, carrot{Carrot} cerise{Cerise} cobalt{Cobalt} copper{Copper} enzian{Enzian} fern{Fern} forest{Forest} ocean{Ocean} olive{Olive} pacific{Pacific} pickle{Pickle} pine{Pine} pink{Pink} plum{Plum} purple{Purple} reef{Reef} sahara{Sahara} slateblue{Slate blue} soil{Soil} strawberry{Strawberry} other{{name}}}",
|
||||
"@paletteColorName": {"description": "A colour of the palette content can take, by its stored name.", "placeholders": {"name": {"type": "String"}}},
|
||||
"colorPickerLabel": "Color: {color}",
|
||||
"colorPickerLabel": "Colour: {color}",
|
||||
"@colorPickerLabel": {"placeholders": {"color": {"type": "String"}}},
|
||||
|
||||
"studysetCountFlashcards": "{count, plural, =1{1 flashcard} other{{count} flashcards}}",
|
||||
|
|
@ -241,7 +241,7 @@
|
|||
"richTextSave": "Save",
|
||||
"studysetSheetDescription": "Description",
|
||||
"studysetSheetDescriptionAdd": "Add a description",
|
||||
"studysetSheetInheritColor": "Same color as the studyset it is in",
|
||||
"studysetSheetInheritColor": "Same colour as the studyset it is in",
|
||||
|
||||
"exploreTitle": "Explore",
|
||||
"exploreEmpty": "Studysets that others share will appear here.",
|
||||
|
|
@ -267,6 +267,9 @@
|
|||
"settingsDeleteTitle": "Delete your account?",
|
||||
"settingsDeleteMessage": "Everything you have, your studysets, cards and reviews, is deleted on the server and on all your devices. This can't be undone.",
|
||||
"settingsAbout": "About Halkyon Learn",
|
||||
"aboutLegalese": "Free software: the app is under the GNU Affero General Public License, version 3 or any later version, the server under the Server Side Public License, version 1.",
|
||||
"aboutSourceCode": "The source of what this instance runs:",
|
||||
"aboutSourceUnreachable": "Couldn't reach this instance to ask where its source is.",
|
||||
"settingsAppearance": "Appearance",
|
||||
"settingsAppearanceDevice": "Device theme",
|
||||
"settingsAppearanceLight": "Light",
|
||||
|
|
@ -308,7 +311,7 @@
|
|||
"@tagSheetNameExists": {"placeholders": {"name": {"type": "String"}}},
|
||||
"tagSheetNameMerges": "There is a tag '{name}' already: saving merges this one into it.",
|
||||
"@tagSheetNameMerges": {"placeholders": {"name": {"type": "String"}}},
|
||||
"tagSheetColor": "Color",
|
||||
"tagSheetColor": "Colour",
|
||||
"tagSheetCreate": "Create",
|
||||
"tagSheetSave": "Save",
|
||||
"tagManageTitle": "Manage tags",
|
||||
|
|
@ -342,6 +345,7 @@
|
|||
"cardFilterClear": "Clear all",
|
||||
"cardFilterApply": "Apply",
|
||||
"cardFilterNothing": "No flashcards have this tag.",
|
||||
"cardListFailed": "Couldn't read the cards.",
|
||||
"cardListMore": "More",
|
||||
"cardListManageTags": "Manage tags",
|
||||
|
||||
|
|
@ -402,7 +406,7 @@
|
|||
"composerConflict": "This flashcard was changed differently on two devices. Choose the version to keep of each part below.",
|
||||
"composerKeepThis": "Keep this",
|
||||
"formatAlignLeft": "Align left",
|
||||
"formatAlignCenter": "Center",
|
||||
"formatAlignCenter": "Centre",
|
||||
"formatAlignRight": "Align right",
|
||||
"formatBold": "Bold",
|
||||
"formatItalic": "Italic",
|
||||
|
|
@ -559,6 +563,7 @@
|
|||
"commonClose": "Close",
|
||||
"commonUndo": "Undo",
|
||||
"commonTryAgain": "Try again",
|
||||
"personDataFailed": "Couldn't open this device's data.",
|
||||
"sessionUnreachable": "Couldn't reach the server.",
|
||||
"notFoundGone": "This no longer exists. It may have been deleted on another device.",
|
||||
"fileImage": "Image",
|
||||
|
|
@ -597,6 +602,9 @@
|
|||
"blockNumberedList": "Numbered list",
|
||||
"blockChecklist": "Checklist",
|
||||
"blockQuote": "Quote",
|
||||
"blockListItem": "List item",
|
||||
"blockChecklistItem": "To-do",
|
||||
"editorSlashHint": "Type / to insert a block, or just start typing",
|
||||
"blockDivider": "Divider",
|
||||
"blockTable": "Table",
|
||||
"blockRemove": "Remove",
|
||||
|
|
|
|||
3
halkyon_learn_flutter/lib/l10n/app_en_GB.arb
Normal file
3
halkyon_learn_flutter/lib/l10n/app_en_GB.arb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"@@locale": "en_GB"
|
||||
}
|
||||
9
halkyon_learn_flutter/lib/l10n/app_en_US.arb
Normal file
9
halkyon_learn_flutter/lib/l10n/app_en_US.arb
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"@@locale": "en_US",
|
||||
|
||||
"studysetSheetColor": "Color",
|
||||
"colorPickerLabel": "Color: {color}",
|
||||
"studysetSheetInheritColor": "Same color as the studyset it is in",
|
||||
"tagSheetColor": "Color",
|
||||
"formatAlignCenter": "Center"
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart';
|
|||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations_de.dart';
|
||||
import 'app_localizations_en.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
|
@ -92,7 +93,12 @@ abstract class AppLocalizations {
|
|||
];
|
||||
|
||||
/// A list of this localizations delegate's supported locales.
|
||||
static const List<Locale> supportedLocales = <Locale>[Locale('en')];
|
||||
static const List<Locale> supportedLocales = <Locale>[
|
||||
Locale('de'),
|
||||
Locale('en'),
|
||||
Locale('en', 'GB'),
|
||||
Locale('en', 'US'),
|
||||
];
|
||||
|
||||
/// The app's name, e.g. in the window title and the rail's logo.
|
||||
///
|
||||
|
|
@ -271,7 +277,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @studysetSheetColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Color'**
|
||||
/// **'Colour'**
|
||||
String get studysetSheetColor;
|
||||
|
||||
/// No description provided for @studysetSheetDiscardTitle.
|
||||
|
|
@ -301,7 +307,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @colorPickerLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Color: {color}'**
|
||||
/// **'Colour: {color}'**
|
||||
String colorPickerLabel(String color);
|
||||
|
||||
/// No description provided for @studysetCountFlashcards.
|
||||
|
|
@ -1363,7 +1369,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @studysetSheetInheritColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Same color as the studyset it is in'**
|
||||
/// **'Same colour as the studyset it is in'**
|
||||
String get studysetSheetInheritColor;
|
||||
|
||||
/// No description provided for @exploreTitle.
|
||||
|
|
@ -1498,6 +1504,24 @@ abstract class AppLocalizations {
|
|||
/// **'About Halkyon Learn'**
|
||||
String get settingsAbout;
|
||||
|
||||
/// No description provided for @aboutLegalese.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Free software: the app is under the GNU Affero General Public License, version 3 or any later version, the server under the Server Side Public License, version 1.'**
|
||||
String get aboutLegalese;
|
||||
|
||||
/// No description provided for @aboutSourceCode.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The source of what this instance runs:'**
|
||||
String get aboutSourceCode;
|
||||
|
||||
/// No description provided for @aboutSourceUnreachable.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Couldn\'t reach this instance to ask where its source is.'**
|
||||
String get aboutSourceUnreachable;
|
||||
|
||||
/// No description provided for @settingsAppearance.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1693,7 +1717,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @tagSheetColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Color'**
|
||||
/// **'Colour'**
|
||||
String get tagSheetColor;
|
||||
|
||||
/// No description provided for @tagSheetCreate.
|
||||
|
|
@ -1870,6 +1894,12 @@ abstract class AppLocalizations {
|
|||
/// **'No flashcards have this tag.'**
|
||||
String get cardFilterNothing;
|
||||
|
||||
/// No description provided for @cardListFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Couldn\'t read the cards.'**
|
||||
String get cardListFailed;
|
||||
|
||||
/// No description provided for @cardListMore.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -2191,7 +2221,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @formatAlignCenter.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Center'**
|
||||
/// **'Centre'**
|
||||
String get formatAlignCenter;
|
||||
|
||||
/// No description provided for @formatAlignRight.
|
||||
|
|
@ -2992,6 +3022,12 @@ abstract class AppLocalizations {
|
|||
/// **'Try again'**
|
||||
String get commonTryAgain;
|
||||
|
||||
/// No description provided for @personDataFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Couldn\'t open this device\'s data.'**
|
||||
String get personDataFailed;
|
||||
|
||||
/// No description provided for @sessionUnreachable.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3160,6 +3196,24 @@ abstract class AppLocalizations {
|
|||
/// **'Quote'**
|
||||
String get blockQuote;
|
||||
|
||||
/// No description provided for @blockListItem.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'List item'**
|
||||
String get blockListItem;
|
||||
|
||||
/// No description provided for @blockChecklistItem.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'To-do'**
|
||||
String get blockChecklistItem;
|
||||
|
||||
/// No description provided for @editorSlashHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Type / to insert a block, or just start typing'**
|
||||
String get editorSlashHint;
|
||||
|
||||
/// No description provided for @blockDivider.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -4642,15 +4696,31 @@ class _AppLocalizationsDelegate
|
|||
|
||||
@override
|
||||
bool isSupported(Locale locale) =>
|
||||
<String>['en'].contains(locale.languageCode);
|
||||
<String>['de', 'en'].contains(locale.languageCode);
|
||||
|
||||
@override
|
||||
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||
// Lookup logic when language+country codes are specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'en':
|
||||
{
|
||||
switch (locale.countryCode) {
|
||||
case 'GB':
|
||||
return AppLocalizationsEnGb();
|
||||
case 'US':
|
||||
return AppLocalizationsEnUs();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup logic when only language code is specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'de':
|
||||
return AppLocalizationsDe();
|
||||
case 'en':
|
||||
return AppLocalizationsEn();
|
||||
}
|
||||
|
|
|
|||
2842
halkyon_learn_flutter/lib/l10n/app_localizations_de.dart
Normal file
2842
halkyon_learn_flutter/lib/l10n/app_localizations_de.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -99,7 +99,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
'Shown as a small label above the title.';
|
||||
|
||||
@override
|
||||
String get studysetSheetColor => 'Color';
|
||||
String get studysetSheetColor => 'Colour';
|
||||
|
||||
@override
|
||||
String get studysetSheetDiscardTitle => 'Discard changes?';
|
||||
|
|
@ -144,7 +144,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String colorPickerLabel(String color) {
|
||||
return 'Color: $color';
|
||||
return 'Colour: $color';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -788,7 +788,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get studysetSheetDescriptionAdd => 'Add a description';
|
||||
|
||||
@override
|
||||
String get studysetSheetInheritColor => 'Same color as the studyset it is in';
|
||||
String get studysetSheetInheritColor =>
|
||||
'Same colour as the studyset it is in';
|
||||
|
||||
@override
|
||||
String get exploreTitle => 'Explore';
|
||||
|
|
@ -860,6 +861,17 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get settingsAbout => 'About Halkyon Learn';
|
||||
|
||||
@override
|
||||
String get aboutLegalese =>
|
||||
'Free software: the app is under the GNU Affero General Public License, version 3 or any later version, the server under the Server Side Public License, version 1.';
|
||||
|
||||
@override
|
||||
String get aboutSourceCode => 'The source of what this instance runs:';
|
||||
|
||||
@override
|
||||
String get aboutSourceUnreachable =>
|
||||
'Couldn\'t reach this instance to ask where its source is.';
|
||||
|
||||
@override
|
||||
String get settingsAppearance => 'Appearance';
|
||||
|
||||
|
|
@ -974,7 +986,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
}
|
||||
|
||||
@override
|
||||
String get tagSheetColor => 'Color';
|
||||
String get tagSheetColor => 'Colour';
|
||||
|
||||
@override
|
||||
String get tagSheetCreate => 'Create';
|
||||
|
|
@ -1080,6 +1092,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get cardFilterNothing => 'No flashcards have this tag.';
|
||||
|
||||
@override
|
||||
String get cardListFailed => 'Couldn\'t read the cards.';
|
||||
|
||||
@override
|
||||
String get cardListMore => 'More';
|
||||
|
||||
|
|
@ -1268,7 +1283,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get formatAlignLeft => 'Align left';
|
||||
|
||||
@override
|
||||
String get formatAlignCenter => 'Center';
|
||||
String get formatAlignCenter => 'Centre';
|
||||
|
||||
@override
|
||||
String get formatAlignRight => 'Align right';
|
||||
|
|
@ -1769,6 +1784,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get commonTryAgain => 'Try again';
|
||||
|
||||
@override
|
||||
String get personDataFailed => 'Couldn\'t open this device\'s data.';
|
||||
|
||||
@override
|
||||
String get sessionUnreachable => 'Couldn\'t reach the server.';
|
||||
|
||||
|
|
@ -1862,6 +1880,16 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get blockQuote => 'Quote';
|
||||
|
||||
@override
|
||||
String get blockListItem => 'List item';
|
||||
|
||||
@override
|
||||
String get blockChecklistItem => 'To-do';
|
||||
|
||||
@override
|
||||
String get editorSlashHint =>
|
||||
'Type / to insert a block, or just start typing';
|
||||
|
||||
@override
|
||||
String get blockDivider => 'Divider';
|
||||
|
||||
|
|
@ -2783,3 +2811,30 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return '$_temp0';
|
||||
}
|
||||
}
|
||||
|
||||
/// The translations for English, as used in the United Kingdom (`en_GB`).
|
||||
class AppLocalizationsEnGb extends AppLocalizationsEn {
|
||||
AppLocalizationsEnGb() : super('en_GB');
|
||||
}
|
||||
|
||||
/// The translations for English, as used in the United States (`en_US`).
|
||||
class AppLocalizationsEnUs extends AppLocalizationsEn {
|
||||
AppLocalizationsEnUs() : super('en_US');
|
||||
|
||||
@override
|
||||
String get studysetSheetColor => 'Color';
|
||||
|
||||
@override
|
||||
String colorPickerLabel(String color) {
|
||||
return 'Color: $color';
|
||||
}
|
||||
|
||||
@override
|
||||
String get studysetSheetInheritColor => 'Same color as the studyset it is in';
|
||||
|
||||
@override
|
||||
String get tagSheetColor => 'Color';
|
||||
|
||||
@override
|
||||
String get formatAlignCenter => 'Center';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:halkyon_learn_flutter/src/core/device_settings/bloc/device_setti
|
|||
import 'package:halkyon_learn_flutter/src/core/device_settings/widget/device_settings_scope.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/editor/core_blocks.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/editor/editor_blocks.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/l10n/app_languages.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/sync/bloc/sync_status_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/theme/app_theme.dart';
|
||||
|
|
@ -57,7 +58,8 @@ class _PersonAppState() extends State<PersonApp> {
|
|||
darkTheme: appTheme(Brightness.dark),
|
||||
themeMode: settings.themeMode,
|
||||
locale: settings.locale,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
localeListResolutionCallback: resolveAppLocale,
|
||||
localizationsDelegates: appLocalizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
routerConfig: _router,
|
||||
// The person's data opens around the router's navigator, so every screen
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/dependencies/dependencies_scope.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/dependencies/person_dependencies.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/auth/model/auth_status.dart';
|
||||
|
|
@ -61,7 +62,7 @@ class _PersonScopeState() extends State<PersonScope> {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text("Couldn't open this device's data."),
|
||||
Text(AppLocalizations.of(context).personDataFailed),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
onPressed: () => setState(() {
|
||||
|
|
@ -69,7 +70,7 @@ class _PersonScopeState() extends State<PersonScope> {
|
|||
context,
|
||||
).openPerson(widget.person);
|
||||
}),
|
||||
child: const Text('Try again'),
|
||||
child: Text(AppLocalizations.of(context).commonTryAgain),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||
import 'package:halkyon_learn_flutter/src/core/dependencies/dependencies_scope.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/device_settings/bloc/device_settings_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/device_settings/model/device_settings.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/l10n/app_languages.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// Provides the device's settings ([DeviceSettingsBloc]) to [child], starting
|
||||
|
|
@ -34,10 +35,8 @@ extension DeviceSettingsApp on DeviceSettings {
|
|||
};
|
||||
|
||||
/// The app's locale; null for the device's.
|
||||
Locale? get locale => switch (language?.split('-')) {
|
||||
Locale? get locale => switch (language) {
|
||||
final language? => localeOf(language),
|
||||
null => null,
|
||||
[final language] => Locale(language),
|
||||
[final language, final country, ...] => Locale(language, country),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/content_blocks.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/editor/atomic_block.dart';
|
||||
|
|
@ -80,11 +81,73 @@ class const EditorBlocksScope({
|
|||
|
||||
/// The builders of [blocks] with a box for any type they don't know, as
|
||||
/// content from a newer app may hold: it says so, and the block stays.
|
||||
Map<String, BlockComponentBuilder> buildersWithUnknown(EditorBlocks blocks) => {
|
||||
...blocks.builders,
|
||||
///
|
||||
/// The editor's own text blocks say what they are in the app's language
|
||||
/// ([l10n]) while empty: the editor's own placeholders follow a locale of
|
||||
/// their own, set for the whole process, in few languages and not British
|
||||
/// English.
|
||||
Map<String, BlockComponentBuilder> buildersWithUnknown(
|
||||
EditorBlocks blocks,
|
||||
AppLocalizations l10n,
|
||||
) => {
|
||||
for (final MapEntry(key: type, value: builder) in blocks.builders.entries)
|
||||
type: _withPlaceholder(builder, l10n),
|
||||
errorBlockComponentBuilderKey: _UnknownBlockBuilder(),
|
||||
};
|
||||
|
||||
/// [builder] alike, but saying what its block is in [l10n]'s language while
|
||||
/// empty, for the editor's own text blocks; others as they are.
|
||||
BlockComponentBuilder _withPlaceholder(
|
||||
BlockComponentBuilder builder,
|
||||
AppLocalizations l10n,
|
||||
) {
|
||||
BlockComponentConfiguration saying(String Function(Node node) text) =>
|
||||
builder.configuration.copyWith(placeholderText: text);
|
||||
return switch (builder) {
|
||||
final ParagraphBlockComponentBuilder paragraph =>
|
||||
ParagraphBlockComponentBuilder(
|
||||
// A space, not nothing, keeps an empty line its height.
|
||||
configuration: saying(
|
||||
(_) => switch (defaultTargetPlatform) {
|
||||
// Phones insert through the Insert button, not a `/` menu.
|
||||
TargetPlatform.android || TargetPlatform.iOS => ' ',
|
||||
_ => l10n.editorSlashHint,
|
||||
},
|
||||
),
|
||||
showPlaceholder: paragraph.showPlaceholder,
|
||||
),
|
||||
final TodoListBlockComponentBuilder todo => TodoListBlockComponentBuilder(
|
||||
configuration: saying((_) => l10n.blockChecklistItem),
|
||||
textStyleBuilder: todo.textStyleBuilder,
|
||||
iconBuilder: todo.iconBuilder,
|
||||
toggleChildrenTriggers: todo.toggleChildrenTriggers,
|
||||
),
|
||||
final BulletedListBlockComponentBuilder bulleted =>
|
||||
BulletedListBlockComponentBuilder(
|
||||
configuration: saying((_) => l10n.blockListItem),
|
||||
iconBuilder: bulleted.iconBuilder,
|
||||
),
|
||||
final NumberedListBlockComponentBuilder numbered =>
|
||||
NumberedListBlockComponentBuilder(
|
||||
configuration: saying((_) => l10n.blockListItem),
|
||||
iconBuilder: numbered.iconBuilder,
|
||||
),
|
||||
final QuoteBlockComponentBuilder quote => QuoteBlockComponentBuilder(
|
||||
configuration: saying((_) => l10n.blockQuote),
|
||||
iconBuilder: quote.iconBuilder,
|
||||
),
|
||||
final HeadingBlockComponentBuilder heading => HeadingBlockComponentBuilder(
|
||||
configuration: saying(
|
||||
(node) => l10n.blockHeading(
|
||||
node.attributes[HeadingBlockKeys.level] as int? ?? 1,
|
||||
),
|
||||
),
|
||||
textStyleBuilder: heading.textStyleBuilder,
|
||||
),
|
||||
_ => builder,
|
||||
};
|
||||
}
|
||||
|
||||
/// The `/` menu of [blocks] in place of the editor's own, whose items can't
|
||||
/// be translated and whose Image item makes images that never show. Desktop
|
||||
/// only, as the editor's (phones use the Insert button).
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/editor/block_menu.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/editor/core_blocks.dart';
|
||||
|
|
@ -131,7 +132,10 @@ class _RichContentEditorState() extends State<RichContentEditor> {
|
|||
builder: (context, constraints) => AppFlowyEditor(
|
||||
editorState: _editorState,
|
||||
editorStyle: editorStyleOf(context),
|
||||
blockComponentBuilders: buildersWithUnknown(blocks),
|
||||
blockComponentBuilders: buildersWithUnknown(
|
||||
blocks,
|
||||
AppLocalizations.of(context),
|
||||
),
|
||||
characterShortcutEvents: characterShortcutsOf(
|
||||
blocks,
|
||||
slashMenu(context, blocks),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/editor/core_blocks.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/editor/editor_blocks.dart';
|
||||
|
|
@ -54,7 +55,10 @@ class _RichContentViewState() extends State<RichContentView> {
|
|||
key: ObjectKey(_editorState),
|
||||
editorState: _editorState,
|
||||
editorStyle: editorStyleOf(context, editing: false),
|
||||
blockComponentBuilders: buildersWithUnknown(blocksOf(context)),
|
||||
blockComponentBuilders: buildersWithUnknown(
|
||||
blocksOf(context),
|
||||
AppLocalizations.of(context),
|
||||
),
|
||||
editable: false,
|
||||
disableSelectionService: true,
|
||||
disableKeyboardService: true,
|
||||
|
|
|
|||
71
halkyon_learn_flutter/lib/src/core/l10n/app_languages.dart
Normal file
71
halkyon_learn_flutter/lib/src/core/l10n/app_languages.dart
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// The app's own texts, and those of Material, Cupertino and the widgets
|
||||
/// library, in every language the app speaks.
|
||||
///
|
||||
/// Not gen-l10n's `AppLocalizations.localizationsDelegates`: those bring
|
||||
/// flutter_localizations' Material texts, which serve only
|
||||
/// `package:flutter/material.dart`, so material_ui's widgets would find no
|
||||
/// Material texts but English. Packages still on the SDK's Material get
|
||||
/// flutter_localizations' through `MaterialUiCompatibilityBridge`.
|
||||
const appLocalizationsDelegates = <LocalizationsDelegate<Object?>>[
|
||||
AppLocalizations.delegate,
|
||||
...GlobalMaterialLocalizations.delegates,
|
||||
];
|
||||
|
||||
/// The languages a person can choose for the app, in the order the picker
|
||||
/// lists them, each named in its own language (its autonym), as language
|
||||
/// pickers name them: someone looking for their language may not read the
|
||||
/// one the app speaks now. `tag` is the language's BCP 47 tag, as the
|
||||
/// device's settings keep it.
|
||||
const appLanguages = [
|
||||
(tag: 'en-GB', name: 'English (UK)'),
|
||||
(tag: 'en-US', name: 'English (US)'),
|
||||
(tag: 'de', name: 'Deutsch'),
|
||||
];
|
||||
|
||||
/// The English the app speaks wherever it can't speak the language asked for:
|
||||
/// British English, which `app_en.arb` holds.
|
||||
const _fallback = Locale('en', 'GB');
|
||||
|
||||
/// The locale of the BCP 47 [tag], as `en-GB`.
|
||||
Locale localeOf(String tag) => switch (tag.split('-')) {
|
||||
[final language, final country, ...] => Locale(language, country),
|
||||
[final language, ...] => Locale(language),
|
||||
_ => _fallback,
|
||||
};
|
||||
|
||||
/// Which of the [supported] locales the app speaks, for a device that prefers
|
||||
/// the [preferred] ones in this order (or a person who chose one).
|
||||
///
|
||||
/// The first preferred language the app speaks wins: in its country's
|
||||
/// variant where there is one, otherwise as the language alone, and English
|
||||
/// of any other country as British English. Unlike Flutter's own resolution,
|
||||
/// a country alone never picks a language (Spanish in the United States
|
||||
/// isn't American English), and where none matches, the app speaks British
|
||||
/// English rather than the first supported locale.
|
||||
Locale resolveAppLocale(List<Locale>? preferred, Iterable<Locale> supported) {
|
||||
for (final locale in preferred ?? const <Locale>[]) {
|
||||
final sameLanguage = supported.where(
|
||||
(candidate) => candidate.languageCode == locale.languageCode,
|
||||
);
|
||||
if (sameLanguage.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
for (final candidate in sameLanguage) {
|
||||
if (candidate.countryCode != null &&
|
||||
candidate.countryCode == locale.countryCode) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
if (locale.languageCode == _fallback.languageCode) {
|
||||
return _fallback;
|
||||
}
|
||||
return sameLanguage.firstWhere(
|
||||
(candidate) => candidate.countryCode == null,
|
||||
orElse: () => sameLanguage.first,
|
||||
);
|
||||
}
|
||||
return _fallback;
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/about/bloc/about_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/about/data/about_repository.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
|
@ -8,11 +9,8 @@ import 'package:material_ui/material_ui.dart';
|
|||
/// notice, which the BSD and MIT packages require in a distributed app.
|
||||
void showAbout(BuildContext context, AboutRepository about) => showAboutDialog(
|
||||
context: context,
|
||||
applicationName: 'Halkyon Learn',
|
||||
applicationLegalese:
|
||||
'Free software: the app is under the GNU Affero General Public License, '
|
||||
'version 3 or any later version, the server under the Server Side '
|
||||
'Public License, version 1.',
|
||||
applicationName: AppLocalizations.of(context).appTitle,
|
||||
applicationLegalese: AppLocalizations.of(context).aboutLegalese,
|
||||
children: [
|
||||
BlocProvider(
|
||||
create: (context) => AboutBloc(about)..add(const AboutEventStarted()),
|
||||
|
|
@ -32,12 +30,12 @@ class const _SourceCode() extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('The source of what this instance runs:'),
|
||||
Text(AppLocalizations.of(context).aboutSourceCode),
|
||||
SelectableText(sourceCodeUrl),
|
||||
],
|
||||
),
|
||||
AboutStateUnreachable() => const Text(
|
||||
"Couldn't reach this instance to ask where its source is.",
|
||||
AboutStateUnreachable() => Text(
|
||||
AppLocalizations.of(context).aboutSourceUnreachable,
|
||||
),
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
|||
import 'package:halkyon_learn_flutter/src/core/dependencies/dependencies_scope.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/device_settings/bloc/device_settings_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/device_settings/widget/device_settings_scope.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/l10n/app_languages.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/theme/app_theme.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/widget/app_loading_indicator.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/auth/bloc/session_bloc.dart';
|
||||
|
|
@ -57,7 +58,8 @@ class const _PlainApp({required final Widget home}) extends StatelessWidget {
|
|||
darkTheme: appTheme(Brightness.dark),
|
||||
themeMode: settings.themeMode,
|
||||
locale: settings.locale,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
localeListResolutionCallback: resolveAppLocale,
|
||||
localizationsDelegates: appLocalizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: home,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ class const CardList({
|
|||
child: BlocBuilder<CardListBloc, CardListState>(
|
||||
builder: (context, state) => switch (state) {
|
||||
CardListStateLoading() => const SizedBox.shrink(),
|
||||
CardListStateFailed() => const Text("Couldn't read the cards."),
|
||||
CardListStateFailed() => Text(
|
||||
AppLocalizations.of(context).cardListFailed,
|
||||
),
|
||||
CardListStateLoaded(cards: []) => const _NoCards(),
|
||||
final CardListStateLoaded loaded => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
|
|
|||
|
|
@ -2,16 +2,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/device_settings/bloc/device_settings_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/device_settings/model/device_settings.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/l10n/app_languages.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/layout/window_size.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/widget/section_card.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// The languages the app speaks, each in its own name (its autonym), as
|
||||
/// language pickers name them: someone looking for their language may not
|
||||
/// read the one the app speaks now.
|
||||
const _languages = {'en': 'English'};
|
||||
|
||||
/// How this device shows the app: its appearance and language, side by side
|
||||
/// where there is room. Both stay on the device.
|
||||
class const DeviceSettingsCard({super.key}) extends StatelessWidget {
|
||||
|
|
@ -49,14 +45,15 @@ class const DeviceSettingsCard({super.key}) extends StatelessWidget {
|
|||
label: l10n.settingsLanguage,
|
||||
field: DropdownButtonFormField<String?>(
|
||||
// A language this version no longer speaks shows as the device's.
|
||||
initialValue: _languages.containsKey(settings.language)
|
||||
initialValue:
|
||||
appLanguages.any((language) => language.tag == settings.language)
|
||||
? settings.language
|
||||
: null,
|
||||
decoration: _decoration,
|
||||
icon: const Icon(LucideIcons.chevronDown),
|
||||
items: [
|
||||
DropdownMenuItem(child: Text(l10n.settingsLanguageDevice)),
|
||||
for (final MapEntry(key: tag, value: name) in _languages.entries)
|
||||
for (final (:tag, :name) in appLanguages)
|
||||
DropdownMenuItem(value: tag, child: Text(name)),
|
||||
],
|
||||
onChanged: (language) => context.read<DeviceSettingsBloc>().add(
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue