diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 147dc8c..a1f5a2d 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -69,17 +69,31 @@ class AppLocalizations { String d(String dutchText) { if (languageCode == 'nl') return dutchText; - return _dutchSourceStrings[languageCode]?[dutchText] ?? + return _dutchSourceStringAdditions[languageCode]?[dutchText] ?? + _dutchSourceStrings[languageCode]?[dutchText] ?? + _dutchSourceStringAdditions['en']?[dutchText] ?? _dutchSourceStrings['en']?[dutchText] ?? dutchText; } static String sourceFor(String languageCode, String dutchText) { if (languageCode == 'nl') return dutchText; - return _dutchSourceStrings[languageCode]?[dutchText] ?? + return _dutchSourceStringAdditions[languageCode]?[dutchText] ?? + _dutchSourceStrings[languageCode]?[dutchText] ?? + _dutchSourceStringAdditions['en']?[dutchText] ?? _dutchSourceStrings['en']?[dutchText] ?? dutchText; } + + static bool hasDirectDutchSourceTranslation( + String languageCode, + String dutchText, + ) { + if (languageCode == 'nl') return true; + return _dutchSourceStringAdditions[languageCode]?.containsKey(dutchText) == + true || + _dutchSourceStrings[languageCode]?.containsKey(dutchText) == true; + } } extension AppLocalizationsX on BuildContext { @@ -966,6 +980,7 @@ const _dutchSourceStrings = { 'Titelachtergrond': 'Title background', 'Titeltekst': 'Title text', 'Sectieachtergrond': 'Section background', + 'Geselecteerd': 'Selected', 'Logo': 'Logo', 'Geen logo ingesteld': 'No logo set', 'Verwijder logo': 'Remove logo', @@ -2015,3 +2030,1216 @@ const _dutchSourceStrings = { 'P públiko · H legenda · S pantalla · G resumen · B/W pretu/blanku · R tempu · Esc stop', }, }; + +const _dutchSourceStringAdditions = { + 'en': { + 'Afbeelding': 'Image', + 'Bullet': 'Bullet', + 'HTML opent in elke browser zonder internet en rendert codeblokken, wiskunde en mermaid-diagrammen.': + 'HTML opens in any browser without internet and renders code blocks, math and Mermaid diagrams.', + 'Laatste slide': 'Final slide', + 'Logo px': 'Logo px', + 'Markdown voor laatste slide': 'Markdown for final slide', + 'PREVIEW': 'PREVIEW', + 'Slides gerenderd.': 'Slides rendered.', + 'Standaard laatste slide gebruiken': 'Use default final slide', + 'Wordt automatisch toegevoegd bij presenteren en exporteren.': + 'Automatically added when presenting and exporting.', + 'gerenderd.': 'rendered.', + 'renderen…': 'rendering…', + 'voorbereiden…': 'preparing…', + }, + 'it': { + '# Slide\n\nInhoud hier...': '# Slide\n\nContent here...', + '1 slide geïmporteerd.': '1 slide imported.', + '1 slide kopiëren naar…': 'Copy 1 slide to…', + '1 slide overgeslagen': '1 slide skipped', + 'Accent / bullets': 'Accent / bullets', + 'Achtergrond slides': 'Slide background', + 'Afbeelding': 'Immagine', + 'Afbeelding gekopieerd naar klembord.': 'Image copied to clipboard.', + 'Afbeelding plakken': 'Paste image', + 'Afbeelding plakken uit klembord': 'Paste image from clipboard', + 'Afbeeldingen → nieuwe slides · .md / .ocideck → openen': + 'Images → new slides · .md / .ocideck → open', + 'Afsluiten (Escape)': 'Exit (Escape)', + 'Alle slides zijn overgeslagen — niets om te exporteren.': + 'All slides are skipped, so there is nothing to export.', + 'Alle slides zijn overgeslagen — niets om te tonen.': + 'All slides are skipped, so there is nothing to show.', + 'Alles tonen': 'Show all', + 'Audio verwijderen': 'Remove audio', + 'Automatisch doorgaan na': 'Advance automatically after', + 'Bijv. Kwartaalupdate Q4': 'E.g. Q4 update', + 'Bullet': 'Punto elenco', + 'Caption / bronvermelding (bijv. © Naam Fotograaf)': + 'Caption / credit (e.g. © Photographer Name)', + 'Coverflow': 'Coverflow', + 'De afbeelding wordt schermvullend als achtergrond getoond met verminderde opaciteit zodat de tekst leesbaar blijft.': + 'The image is shown fullscreen as a background with reduced opacity so the text remains readable.', + 'De snelle bruine vos springt over de luie hond.': + 'The quick brown fox jumps over the lazy dog.', + 'Deze gegevens worden in de markdown opgeslagen en zijn doorzoekbaar bij het openen.': + 'These details are stored in the Markdown and searchable when opening.', + 'Deze presentatie heeft niet-opgeslagen wijzigingen. Sla de presentatie op voordat het tabblad sluit.': + 'This presentation has unsaved changes. Save it before closing the tab.', + 'Deze slide kan geen afbeelding ontvangen. Kies eerst een afbeeldingsslide.': + 'This slide cannot receive an image. Choose an image slide first.', + 'Eerste': 'First', + 'Einde van de presentatie': 'End of presentation', + 'Er is een presentatie met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'A presentation with unsaved changes was found from a previous session:', + 'Er zijn': 'There are', + 'Er zijn presentaties met niet-opgeslagen wijzigingen. Sla ze op voordat de app sluit.': + 'There are presentations with unsaved changes. Save them before closing the app.', + 'Export mislukt:': 'Export failed:', + 'Footer tonen op deze slide': 'Show footer on this slide', + 'Gebruik "Bladeren" om afbeeldingen van elke locatie te kiezen.': + 'Use “Browse” to choose images from any location.', + 'Geen afbeelding op het klembord gevonden.': + 'No image found on the clipboard.', + 'Geen ander deck open. Open eerst een ander tabblad.': + 'No other deck is open. Open another tab first.', + 'Geen andere presentaties (.md) in deze map gevonden.': + 'No other presentations (.md) found in this folder.', + 'Geen notities voor deze slide.': 'No notes for this slide.', + 'Geen presentaties (.md) in deze map gevonden.': + 'No presentations (.md) found in this folder.', + 'Geen presentaties gevonden voor': 'No presentations found for', + 'Geen resultaten': 'No results', + 'Geen resultaten voor': 'No results for', + 'Geen slides gevonden voor': 'No slides found for', + 'Geen slides met': 'No slides with', + 'Geselecteerd': 'Selected', + 'HTML opent in elke browser zonder internet en rendert codeblokken, wiskunde en mermaid-diagrammen.': + 'L’HTML si apre in qualsiasi browser senza internet e renderizza blocchi di codice, matematica e diagrammi Mermaid.', + 'Het bestand wordt permanent van schijf verwijderd. Deze actie kan niet ongedaan worden gemaakt.': + 'The file will be permanently deleted from disk. This action cannot be undone.', + 'Ingezoomd': 'Zoomed in', + 'Inzoomen (minder van de foto zichtbaar)': + 'Zoom in (less of the photo visible)', + 'Kies een afbeelding': 'Choose an image', + 'Kies een map met presentaties om te beginnen.': + 'Choose a folder with presentations to begin.', + 'Kon dit pakket niet importeren.': 'Could not import this package.', + 'Kon niet van scherm wisselen.': 'Could not switch screens.', + 'Kon van deze URL geen presentatie ophalen.': + 'Could not fetch a presentation from this URL.', + 'Kopieer afbeelding naar klembord': 'Copy image to clipboard', + 'Kopiëren mislukt.': 'Copy failed.', + 'Kopiëren naar ander deck': 'Copy to another deck', + 'Kopiëren naar klembord mislukt.': 'Copying to clipboard failed.', + 'Koprij verwijderen': 'Remove header row', + 'Laat los om toe te voegen': 'Release to add', + 'Laatste slide': 'Slide finale', + 'Let op: deze afbeelding wordt nog gebruikt in': + 'Warning: this image is still used in', + 'Logo kiezen': 'Choose logo', + 'Logo px': 'Logo px', + 'Logo tonen op deze slide': 'Show logo on this slide', + 'Map met presentaties kiezen': 'Choose presentation folder', + 'Map voor exports': 'Export folder', + 'Markdown kon niet worden verwerkt. Controleer de syntax.': + 'Markdown could not be processed. Check the syntax.', + 'Markdown modus — bewerk de volledige presentatie als Marp Markdown': + 'Markdown mode — edit the full presentation as Marp Markdown', + 'Markdown voor laatste slide': 'Markdown per la slide finale', + 'Naam van het stijlprofiel': 'Name of the style profile', + 'Niet-opgeslagen werk herstellen?': 'Restore unsaved work?', + 'Niet-opgeslagen wijzigingen': 'Unsaved changes', + 'Niets vervangen': 'Nothing replaced', + 'Nieuw profiel': 'New profile', + 'Open eerst een presentatie om afbeeldingen toe te voegen.': + 'Open a presentation before adding images.', + 'Overslaan bij presenteren/exporteren': 'Skip when presenting/exporting', + 'PREVIEW': 'ANTEPRIMA', + 'Paginanummers tonen (rechtsonder)': 'Show page numbers (bottom right)', + 'Pakket geëxporteerd naar:': 'Package exported to:', + 'Pas je zoekterm aan of voeg een beschrijving toe.': + 'Adjust your search term or add a description.', + 'Plak de link naar een .ocideck-pakket of een Marp-markdownbestand.': + 'Paste the link to an .ocideck package or a Marp Markdown file.', + 'Preview inklappen': 'Collapse preview', + 'Preview uitklappen': 'Expand preview', + 'Profiel verwijderen': 'Delete profile', + 'Rij verwijderen': 'Remove row', + 'SLIDES': 'SLIDES', + 'Sectieachtergrond': 'Section background', + 'Selecteer een\nafbeelding': 'Select an\nimage', + 'Selectie opheffen': 'Clear selection', + 'Sleep om de slide-preview breder of smaller te maken': + 'Drag to make the slide preview wider or narrower', + 'Slide': 'Slide', + 'Slide gekopieerd naar klembord.': 'Slide copied to clipboard.', + 'Slide plakken': 'Paste slide', + 'Slide renderen…': 'Rendering slide…', + 'Slide toevoegen': 'Add slide', + 'Slides gerenderd.': 'Slide renderizzate.', + 'Sluiten (G of Esc)': 'Close (G or Esc)', + 'Sprekersnotities...': 'Speaker notes...', + 'Standaard laatste slide gebruiken': 'Usa slide finale predefinita', + 'Standaard map voor presentaties': 'Default presentation folder', + 'Standaardprofiel laden': 'Load default profile', + 'TLP-classificatie (Traffic Light Protocol)': + 'TLP classification (Traffic Light Protocol)', + 'Tabel koptekst': 'Table header text', + 'Tabeltekst': 'Table text', + 'Terug naar standaardstijl': 'Back to default style', + 'Terugzetten (volledige afbeelding zichtbaar)': + 'Reset (full image visible)', + 'Tijd resetten (R)': 'Reset timer (R)', + 'Tip: druk op Enter binnen een cel voor een nieuwe regel.': + 'Tip: press Enter inside a cell for a new line.', + 'Titelachtergrond': 'Title background', + 'Titeltekst': 'Title text', + 'Toepassen': 'Apply', + 'Tokens: {page}, {total}, {date}, {title}. Footer verschijnt op alle slides behalve titel- en sectieslides, tenzij je hem per slide uitzet.': + 'Tokens: {page}, {total}, {date}, {title}. The footer appears on all slides except title and section slides, unless you disable it per slide.', + 'Typ zoektermen om slides uit al je presentaties te vinden.': + 'Type search terms to find slides across your presentations.', + 'Uitgezoomd': 'Zoomed out', + 'Uitzoomen (meer van de foto zichtbaar)': + 'Zoom out (more of the photo visible)', + 'Verwijder afbeelding': 'Remove image', + 'Verwijder logo': 'Remove logo', + 'Verwijderen maakt die slides leeg. Dit kan niet ongedaan worden gemaakt.': + 'Deleting will clear those slides. This cannot be undone.', + 'Volledig zichtbaar (100%)': 'Fully visible (100%)', + 'Vul een titel in': 'Enter a title', + 'Weer tonen': 'Show again', + 'Weer tonen bij presenteren/exporteren': + 'Show again when presenting/exporting', + 'Wordt automatisch toegevoegd bij presenteren en exporteren.': + 'Aggiunta automaticamente durante la presentazione e l’esportazione.', + 'Zoek in slides…': 'Search in slides…', + 'Zoek op bestandsnaam, titel of tekst in de slides…': + 'Search by file name, title or slide text…', + 'Zoek op naam of beschrijving…': 'Search by name or description…', + 'Zoek op presentatie, titel of tekst…': + 'Search by presentation, title or text…', + 'Zoek slides op tekst, titel, onderschrift, pad…': + 'Search slides by text, title, caption, path…', + 'bijv. Vertrouwelijk · {title} · {date}': + 'e.g. Confidential · {title} · {date}', + 'gerenderd.': 'renderizzata.', + 'geselecteerd': 'selected', + 'meer treffer(s)': 'more match(es)', + 'paginering aan': 'pagination on', + 'pijltjes + Enter of klik om te springen': + 'arrows + Enter or click to jump', + 'presentaties met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'presentations with unsaved changes from a previous session:', + 'renderen…': 'renderizzazione…', + 'resultaat': 'result', + 'resultaten': 'results', + 'slide': 'slide', + 'slide(s) gekopieerd naar': 'slide(s) copied to', + 'slides geïmporteerd.': 'slides imported.', + 'slides kopiëren naar…': 'slides to copy to…', + 'slides overgeslagen': 'slides skipped', + 'toegevoegd': 'added', + 'treffer(s)': 'match(es)', + 'treffers — verfijn je zoekopdracht': 'matches, refine your search', + 'van de foto zichtbaar': 'of the photo visible', + 'vervangen': 'replaced', + 'verwijderen': 'remove', + 'volledig deck': 'full deck', + 'voorbereiden…': 'preparazione…', + '↑↓←→ navigeren · Enter kiezen · Dubbelklik selecteert': + '↑↓←→ navigate · Enter chooses · Double-click selects', + }, + 'de': { + '# Slide\n\nInhoud hier...': '# Slide\n\nContent here...', + '1 slide geïmporteerd.': '1 slide imported.', + '1 slide kopiëren naar…': 'Copy 1 slide to…', + '1 slide overgeslagen': '1 slide skipped', + 'Accent / bullets': 'Accent / bullets', + 'Achtergrond slides': 'Slide background', + 'Afbeelding': 'Bild', + 'Afbeelding gekopieerd naar klembord.': 'Image copied to clipboard.', + 'Afbeelding plakken': 'Paste image', + 'Afbeelding plakken uit klembord': 'Paste image from clipboard', + 'Afbeeldingen → nieuwe slides · .md / .ocideck → openen': + 'Images → new slides · .md / .ocideck → open', + 'Afsluiten (Escape)': 'Exit (Escape)', + 'Alle slides zijn overgeslagen — niets om te exporteren.': + 'All slides are skipped, so there is nothing to export.', + 'Alle slides zijn overgeslagen — niets om te tonen.': + 'All slides are skipped, so there is nothing to show.', + 'Alles tonen': 'Show all', + 'Audio verwijderen': 'Remove audio', + 'Automatisch doorgaan na': 'Advance automatically after', + 'Bijv. Kwartaalupdate Q4': 'E.g. Q4 update', + 'Bullet': 'Stichpunkt', + 'Caption / bronvermelding (bijv. © Naam Fotograaf)': + 'Caption / credit (e.g. © Photographer Name)', + 'Coverflow': 'Coverflow', + 'De afbeelding wordt schermvullend als achtergrond getoond met verminderde opaciteit zodat de tekst leesbaar blijft.': + 'The image is shown fullscreen as a background with reduced opacity so the text remains readable.', + 'De snelle bruine vos springt over de luie hond.': + 'The quick brown fox jumps over the lazy dog.', + 'Deze gegevens worden in de markdown opgeslagen en zijn doorzoekbaar bij het openen.': + 'These details are stored in the Markdown and searchable when opening.', + 'Deze presentatie heeft niet-opgeslagen wijzigingen. Sla de presentatie op voordat het tabblad sluit.': + 'This presentation has unsaved changes. Save it before closing the tab.', + 'Deze slide kan geen afbeelding ontvangen. Kies eerst een afbeeldingsslide.': + 'This slide cannot receive an image. Choose an image slide first.', + 'Eerste': 'First', + 'Einde van de presentatie': 'End of presentation', + 'Er is een presentatie met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'A presentation with unsaved changes was found from a previous session:', + 'Er zijn': 'There are', + 'Er zijn presentaties met niet-opgeslagen wijzigingen. Sla ze op voordat de app sluit.': + 'There are presentations with unsaved changes. Save them before closing the app.', + 'Export mislukt:': 'Export failed:', + 'Footer tonen op deze slide': 'Show footer on this slide', + 'Gebruik "Bladeren" om afbeeldingen van elke locatie te kiezen.': + 'Use “Browse” to choose images from any location.', + 'Geen afbeelding op het klembord gevonden.': + 'No image found on the clipboard.', + 'Geen ander deck open. Open eerst een ander tabblad.': + 'No other deck is open. Open another tab first.', + 'Geen andere presentaties (.md) in deze map gevonden.': + 'No other presentations (.md) found in this folder.', + 'Geen notities voor deze slide.': 'No notes for this slide.', + 'Geen presentaties (.md) in deze map gevonden.': + 'No presentations (.md) found in this folder.', + 'Geen presentaties gevonden voor': 'No presentations found for', + 'Geen resultaten': 'No results', + 'Geen resultaten voor': 'No results for', + 'Geen slides gevonden voor': 'No slides found for', + 'Geen slides met': 'No slides with', + 'Geselecteerd': 'Selected', + 'HTML opent in elke browser zonder internet en rendert codeblokken, wiskunde en mermaid-diagrammen.': + 'HTML öffnet sich in jedem Browser ohne Internet und rendert Codeblöcke, Mathematik und Mermaid-Diagramme.', + 'Het bestand wordt permanent van schijf verwijderd. Deze actie kan niet ongedaan worden gemaakt.': + 'The file will be permanently deleted from disk. This action cannot be undone.', + 'Ingezoomd': 'Zoomed in', + 'Inzoomen (minder van de foto zichtbaar)': + 'Zoom in (less of the photo visible)', + 'Kies een afbeelding': 'Choose an image', + 'Kies een map met presentaties om te beginnen.': + 'Choose a folder with presentations to begin.', + 'Kon dit pakket niet importeren.': 'Could not import this package.', + 'Kon niet van scherm wisselen.': 'Could not switch screens.', + 'Kon van deze URL geen presentatie ophalen.': + 'Could not fetch a presentation from this URL.', + 'Kopieer afbeelding naar klembord': 'Copy image to clipboard', + 'Kopiëren mislukt.': 'Copy failed.', + 'Kopiëren naar ander deck': 'Copy to another deck', + 'Kopiëren naar klembord mislukt.': 'Copying to clipboard failed.', + 'Koprij verwijderen': 'Remove header row', + 'Laat los om toe te voegen': 'Release to add', + 'Laatste slide': 'Letzte Folie', + 'Let op: deze afbeelding wordt nog gebruikt in': + 'Warning: this image is still used in', + 'Logo kiezen': 'Choose logo', + 'Logo px': 'Logo px', + 'Logo tonen op deze slide': 'Show logo on this slide', + 'Map met presentaties kiezen': 'Choose presentation folder', + 'Map voor exports': 'Export folder', + 'Markdown kon niet worden verwerkt. Controleer de syntax.': + 'Markdown could not be processed. Check the syntax.', + 'Markdown modus — bewerk de volledige presentatie als Marp Markdown': + 'Markdown mode — edit the full presentation as Marp Markdown', + 'Markdown voor laatste slide': 'Markdown für die letzte Folie', + 'Naam van het stijlprofiel': 'Name of the style profile', + 'Niet-opgeslagen werk herstellen?': 'Restore unsaved work?', + 'Niet-opgeslagen wijzigingen': 'Unsaved changes', + 'Niets vervangen': 'Nothing replaced', + 'Nieuw profiel': 'New profile', + 'Open eerst een presentatie om afbeeldingen toe te voegen.': + 'Open a presentation before adding images.', + 'Overslaan bij presenteren/exporteren': 'Skip when presenting/exporting', + 'PREVIEW': 'VORSCHAU', + 'Paginanummers tonen (rechtsonder)': 'Show page numbers (bottom right)', + 'Pakket geëxporteerd naar:': 'Package exported to:', + 'Pas je zoekterm aan of voeg een beschrijving toe.': + 'Adjust your search term or add a description.', + 'Plak de link naar een .ocideck-pakket of een Marp-markdownbestand.': + 'Paste the link to an .ocideck package or a Marp Markdown file.', + 'Preview inklappen': 'Collapse preview', + 'Preview uitklappen': 'Expand preview', + 'Profiel verwijderen': 'Delete profile', + 'Rij verwijderen': 'Remove row', + 'SLIDES': 'SLIDES', + 'Sectieachtergrond': 'Section background', + 'Selecteer een\nafbeelding': 'Select an\nimage', + 'Selectie opheffen': 'Clear selection', + 'Sleep om de slide-preview breder of smaller te maken': + 'Drag to make the slide preview wider or narrower', + 'Slide': 'Slide', + 'Slide gekopieerd naar klembord.': 'Slide copied to clipboard.', + 'Slide plakken': 'Paste slide', + 'Slide renderen…': 'Rendering slide…', + 'Slide toevoegen': 'Add slide', + 'Slides gerenderd.': 'Folien gerendert.', + 'Sluiten (G of Esc)': 'Close (G or Esc)', + 'Sprekersnotities...': 'Speaker notes...', + 'Standaard laatste slide gebruiken': + 'Standardmäßige letzte Folie verwenden', + 'Standaard map voor presentaties': 'Default presentation folder', + 'Standaardprofiel laden': 'Load default profile', + 'TLP-classificatie (Traffic Light Protocol)': + 'TLP classification (Traffic Light Protocol)', + 'Tabel koptekst': 'Table header text', + 'Tabeltekst': 'Table text', + 'Terug naar standaardstijl': 'Back to default style', + 'Terugzetten (volledige afbeelding zichtbaar)': + 'Reset (full image visible)', + 'Tijd resetten (R)': 'Reset timer (R)', + 'Tip: druk op Enter binnen een cel voor een nieuwe regel.': + 'Tip: press Enter inside a cell for a new line.', + 'Titelachtergrond': 'Title background', + 'Titeltekst': 'Title text', + 'Toepassen': 'Apply', + 'Tokens: {page}, {total}, {date}, {title}. Footer verschijnt op alle slides behalve titel- en sectieslides, tenzij je hem per slide uitzet.': + 'Tokens: {page}, {total}, {date}, {title}. The footer appears on all slides except title and section slides, unless you disable it per slide.', + 'Typ zoektermen om slides uit al je presentaties te vinden.': + 'Type search terms to find slides across your presentations.', + 'Uitgezoomd': 'Zoomed out', + 'Uitzoomen (meer van de foto zichtbaar)': + 'Zoom out (more of the photo visible)', + 'Verwijder afbeelding': 'Remove image', + 'Verwijder logo': 'Remove logo', + 'Verwijderen maakt die slides leeg. Dit kan niet ongedaan worden gemaakt.': + 'Deleting will clear those slides. This cannot be undone.', + 'Volledig zichtbaar (100%)': 'Fully visible (100%)', + 'Vul een titel in': 'Enter a title', + 'Weer tonen': 'Show again', + 'Weer tonen bij presenteren/exporteren': + 'Show again when presenting/exporting', + 'Wordt automatisch toegevoegd bij presenteren en exporteren.': + 'Wird beim Präsentieren und Exportieren automatisch hinzugefügt.', + 'Zoek in slides…': 'Search in slides…', + 'Zoek op bestandsnaam, titel of tekst in de slides…': + 'Search by file name, title or slide text…', + 'Zoek op naam of beschrijving…': 'Search by name or description…', + 'Zoek op presentatie, titel of tekst…': + 'Search by presentation, title or text…', + 'Zoek slides op tekst, titel, onderschrift, pad…': + 'Search slides by text, title, caption, path…', + 'bijv. Vertrouwelijk · {title} · {date}': + 'e.g. Confidential · {title} · {date}', + 'gerenderd.': 'gerendert.', + 'geselecteerd': 'selected', + 'meer treffer(s)': 'more match(es)', + 'paginering aan': 'pagination on', + 'pijltjes + Enter of klik om te springen': + 'arrows + Enter or click to jump', + 'presentaties met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'presentations with unsaved changes from a previous session:', + 'renderen…': 'rendern…', + 'resultaat': 'result', + 'resultaten': 'results', + 'slide': 'slide', + 'slide(s) gekopieerd naar': 'slide(s) copied to', + 'slides geïmporteerd.': 'slides imported.', + 'slides kopiëren naar…': 'slides to copy to…', + 'slides overgeslagen': 'slides skipped', + 'toegevoegd': 'added', + 'treffer(s)': 'match(es)', + 'treffers — verfijn je zoekopdracht': 'matches, refine your search', + 'van de foto zichtbaar': 'of the photo visible', + 'vervangen': 'replaced', + 'verwijderen': 'remove', + 'volledig deck': 'full deck', + 'voorbereiden…': 'vorbereiten…', + '↑↓←→ navigeren · Enter kiezen · Dubbelklik selecteert': + '↑↓←→ navigate · Enter chooses · Double-click selects', + }, + 'fr': { + '# Slide\n\nInhoud hier...': '# Slide\n\nContent here...', + '1 slide geïmporteerd.': '1 slide imported.', + '1 slide kopiëren naar…': 'Copy 1 slide to…', + '1 slide overgeslagen': '1 slide skipped', + 'Accent / bullets': 'Accent / bullets', + 'Achtergrond slides': 'Slide background', + 'Afbeelding': 'Image', + 'Afbeelding gekopieerd naar klembord.': 'Image copied to clipboard.', + 'Afbeelding plakken': 'Paste image', + 'Afbeelding plakken uit klembord': 'Paste image from clipboard', + 'Afbeeldingen → nieuwe slides · .md / .ocideck → openen': + 'Images → new slides · .md / .ocideck → open', + 'Afsluiten (Escape)': 'Exit (Escape)', + 'Alle slides zijn overgeslagen — niets om te exporteren.': + 'All slides are skipped, so there is nothing to export.', + 'Alle slides zijn overgeslagen — niets om te tonen.': + 'All slides are skipped, so there is nothing to show.', + 'Alles tonen': 'Show all', + 'Audio verwijderen': 'Remove audio', + 'Automatisch doorgaan na': 'Advance automatically after', + 'Bijv. Kwartaalupdate Q4': 'E.g. Q4 update', + 'Bullet': 'Puce', + 'Caption / bronvermelding (bijv. © Naam Fotograaf)': + 'Caption / credit (e.g. © Photographer Name)', + 'Coverflow': 'Coverflow', + 'De afbeelding wordt schermvullend als achtergrond getoond met verminderde opaciteit zodat de tekst leesbaar blijft.': + 'The image is shown fullscreen as a background with reduced opacity so the text remains readable.', + 'De snelle bruine vos springt over de luie hond.': + 'The quick brown fox jumps over the lazy dog.', + 'Deze gegevens worden in de markdown opgeslagen en zijn doorzoekbaar bij het openen.': + 'These details are stored in the Markdown and searchable when opening.', + 'Deze presentatie heeft niet-opgeslagen wijzigingen. Sla de presentatie op voordat het tabblad sluit.': + 'This presentation has unsaved changes. Save it before closing the tab.', + 'Deze slide kan geen afbeelding ontvangen. Kies eerst een afbeeldingsslide.': + 'This slide cannot receive an image. Choose an image slide first.', + 'Eerste': 'First', + 'Einde van de presentatie': 'End of presentation', + 'Er is een presentatie met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'A presentation with unsaved changes was found from a previous session:', + 'Er zijn': 'There are', + 'Er zijn presentaties met niet-opgeslagen wijzigingen. Sla ze op voordat de app sluit.': + 'There are presentations with unsaved changes. Save them before closing the app.', + 'Export mislukt:': 'Export failed:', + 'Footer tonen op deze slide': 'Show footer on this slide', + 'Gebruik "Bladeren" om afbeeldingen van elke locatie te kiezen.': + 'Use “Browse” to choose images from any location.', + 'Geen afbeelding op het klembord gevonden.': + 'No image found on the clipboard.', + 'Geen ander deck open. Open eerst een ander tabblad.': + 'No other deck is open. Open another tab first.', + 'Geen andere presentaties (.md) in deze map gevonden.': + 'No other presentations (.md) found in this folder.', + 'Geen notities voor deze slide.': 'No notes for this slide.', + 'Geen presentaties (.md) in deze map gevonden.': + 'No presentations (.md) found in this folder.', + 'Geen presentaties gevonden voor': 'No presentations found for', + 'Geen resultaten': 'No results', + 'Geen resultaten voor': 'No results for', + 'Geen slides gevonden voor': 'No slides found for', + 'Geen slides met': 'No slides with', + 'Geselecteerd': 'Selected', + 'HTML opent in elke browser zonder internet en rendert codeblokken, wiskunde en mermaid-diagrammen.': + 'Le HTML s’ouvre dans n’importe quel navigateur sans internet et rend les blocs de code, les mathématiques et les diagrammes Mermaid.', + 'Het bestand wordt permanent van schijf verwijderd. Deze actie kan niet ongedaan worden gemaakt.': + 'The file will be permanently deleted from disk. This action cannot be undone.', + 'Ingezoomd': 'Zoomed in', + 'Inzoomen (minder van de foto zichtbaar)': + 'Zoom in (less of the photo visible)', + 'Kies een afbeelding': 'Choose an image', + 'Kies een map met presentaties om te beginnen.': + 'Choose a folder with presentations to begin.', + 'Kon dit pakket niet importeren.': 'Could not import this package.', + 'Kon niet van scherm wisselen.': 'Could not switch screens.', + 'Kon van deze URL geen presentatie ophalen.': + 'Could not fetch a presentation from this URL.', + 'Kopieer afbeelding naar klembord': 'Copy image to clipboard', + 'Kopiëren mislukt.': 'Copy failed.', + 'Kopiëren naar ander deck': 'Copy to another deck', + 'Kopiëren naar klembord mislukt.': 'Copying to clipboard failed.', + 'Koprij verwijderen': 'Remove header row', + 'Laat los om toe te voegen': 'Release to add', + 'Laatste slide': 'Diapositive finale', + 'Let op: deze afbeelding wordt nog gebruikt in': + 'Warning: this image is still used in', + 'Logo kiezen': 'Choose logo', + 'Logo px': 'Logo px', + 'Logo tonen op deze slide': 'Show logo on this slide', + 'Map met presentaties kiezen': 'Choose presentation folder', + 'Map voor exports': 'Export folder', + 'Markdown kon niet worden verwerkt. Controleer de syntax.': + 'Markdown could not be processed. Check the syntax.', + 'Markdown modus — bewerk de volledige presentatie als Marp Markdown': + 'Markdown mode — edit the full presentation as Marp Markdown', + 'Markdown voor laatste slide': 'Markdown pour la diapositive finale', + 'Naam van het stijlprofiel': 'Name of the style profile', + 'Niet-opgeslagen werk herstellen?': 'Restore unsaved work?', + 'Niet-opgeslagen wijzigingen': 'Unsaved changes', + 'Niets vervangen': 'Nothing replaced', + 'Nieuw profiel': 'New profile', + 'Open eerst een presentatie om afbeeldingen toe te voegen.': + 'Open a presentation before adding images.', + 'Overslaan bij presenteren/exporteren': 'Skip when presenting/exporting', + 'PREVIEW': 'APERÇU', + 'Paginanummers tonen (rechtsonder)': 'Show page numbers (bottom right)', + 'Pakket geëxporteerd naar:': 'Package exported to:', + 'Pas je zoekterm aan of voeg een beschrijving toe.': + 'Adjust your search term or add a description.', + 'Plak de link naar een .ocideck-pakket of een Marp-markdownbestand.': + 'Paste the link to an .ocideck package or a Marp Markdown file.', + 'Preview inklappen': 'Collapse preview', + 'Preview uitklappen': 'Expand preview', + 'Profiel verwijderen': 'Delete profile', + 'Rij verwijderen': 'Remove row', + 'SLIDES': 'SLIDES', + 'Sectieachtergrond': 'Section background', + 'Selecteer een\nafbeelding': 'Select an\nimage', + 'Selectie opheffen': 'Clear selection', + 'Sleep om de slide-preview breder of smaller te maken': + 'Drag to make the slide preview wider or narrower', + 'Slide': 'Slide', + 'Slide gekopieerd naar klembord.': 'Slide copied to clipboard.', + 'Slide plakken': 'Paste slide', + 'Slide renderen…': 'Rendering slide…', + 'Slide toevoegen': 'Add slide', + 'Slides gerenderd.': 'Diapositives rendues.', + 'Sluiten (G of Esc)': 'Close (G or Esc)', + 'Sprekersnotities...': 'Speaker notes...', + 'Standaard laatste slide gebruiken': + 'Utiliser la diapositive finale par défaut', + 'Standaard map voor presentaties': 'Default presentation folder', + 'Standaardprofiel laden': 'Load default profile', + 'TLP-classificatie (Traffic Light Protocol)': + 'TLP classification (Traffic Light Protocol)', + 'Tabel koptekst': 'Table header text', + 'Tabeltekst': 'Table text', + 'Terug naar standaardstijl': 'Back to default style', + 'Terugzetten (volledige afbeelding zichtbaar)': + 'Reset (full image visible)', + 'Tijd resetten (R)': 'Reset timer (R)', + 'Tip: druk op Enter binnen een cel voor een nieuwe regel.': + 'Tip: press Enter inside a cell for a new line.', + 'Titelachtergrond': 'Title background', + 'Titeltekst': 'Title text', + 'Toepassen': 'Apply', + 'Tokens: {page}, {total}, {date}, {title}. Footer verschijnt op alle slides behalve titel- en sectieslides, tenzij je hem per slide uitzet.': + 'Tokens: {page}, {total}, {date}, {title}. The footer appears on all slides except title and section slides, unless you disable it per slide.', + 'Typ zoektermen om slides uit al je presentaties te vinden.': + 'Type search terms to find slides across your presentations.', + 'Uitgezoomd': 'Zoomed out', + 'Uitzoomen (meer van de foto zichtbaar)': + 'Zoom out (more of the photo visible)', + 'Verwijder afbeelding': 'Remove image', + 'Verwijder logo': 'Remove logo', + 'Verwijderen maakt die slides leeg. Dit kan niet ongedaan worden gemaakt.': + 'Deleting will clear those slides. This cannot be undone.', + 'Volledig zichtbaar (100%)': 'Fully visible (100%)', + 'Vul een titel in': 'Enter a title', + 'Weer tonen': 'Show again', + 'Weer tonen bij presenteren/exporteren': + 'Show again when presenting/exporting', + 'Wordt automatisch toegevoegd bij presenteren en exporteren.': + 'Ajoutée automatiquement lors de la présentation et de l’exportation.', + 'Zoek in slides…': 'Search in slides…', + 'Zoek op bestandsnaam, titel of tekst in de slides…': + 'Search by file name, title or slide text…', + 'Zoek op naam of beschrijving…': 'Search by name or description…', + 'Zoek op presentatie, titel of tekst…': + 'Search by presentation, title or text…', + 'Zoek slides op tekst, titel, onderschrift, pad…': + 'Search slides by text, title, caption, path…', + 'bijv. Vertrouwelijk · {title} · {date}': + 'e.g. Confidential · {title} · {date}', + 'gerenderd.': 'rendue.', + 'geselecteerd': 'selected', + 'meer treffer(s)': 'more match(es)', + 'paginering aan': 'pagination on', + 'pijltjes + Enter of klik om te springen': + 'arrows + Enter or click to jump', + 'presentaties met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'presentations with unsaved changes from a previous session:', + 'renderen…': 'rendu…', + 'resultaat': 'result', + 'resultaten': 'results', + 'slide': 'slide', + 'slide(s) gekopieerd naar': 'slide(s) copied to', + 'slides geïmporteerd.': 'slides imported.', + 'slides kopiëren naar…': 'slides to copy to…', + 'slides overgeslagen': 'slides skipped', + 'toegevoegd': 'added', + 'treffer(s)': 'match(es)', + 'treffers — verfijn je zoekopdracht': 'matches, refine your search', + 'van de foto zichtbaar': 'of the photo visible', + 'vervangen': 'replaced', + 'verwijderen': 'remove', + 'volledig deck': 'full deck', + 'voorbereiden…': 'préparation…', + '↑↓←→ navigeren · Enter kiezen · Dubbelklik selecteert': + '↑↓←→ navigate · Enter chooses · Double-click selects', + }, + 'es': { + '# Slide\n\nInhoud hier...': '# Slide\n\nContent here...', + '1 slide geïmporteerd.': '1 slide imported.', + '1 slide kopiëren naar…': 'Copy 1 slide to…', + '1 slide overgeslagen': '1 slide skipped', + 'Accent / bullets': 'Accent / bullets', + 'Achtergrond slides': 'Slide background', + 'Afbeelding': 'Imagen', + 'Afbeelding gekopieerd naar klembord.': 'Image copied to clipboard.', + 'Afbeelding plakken': 'Paste image', + 'Afbeelding plakken uit klembord': 'Paste image from clipboard', + 'Afbeeldingen → nieuwe slides · .md / .ocideck → openen': + 'Images → new slides · .md / .ocideck → open', + 'Afsluiten (Escape)': 'Exit (Escape)', + 'Alle slides zijn overgeslagen — niets om te exporteren.': + 'All slides are skipped, so there is nothing to export.', + 'Alle slides zijn overgeslagen — niets om te tonen.': + 'All slides are skipped, so there is nothing to show.', + 'Alles tonen': 'Show all', + 'Audio verwijderen': 'Remove audio', + 'Automatisch doorgaan na': 'Advance automatically after', + 'Bijv. Kwartaalupdate Q4': 'E.g. Q4 update', + 'Bullet': 'Viñeta', + 'Caption / bronvermelding (bijv. © Naam Fotograaf)': + 'Caption / credit (e.g. © Photographer Name)', + 'Coverflow': 'Coverflow', + 'De afbeelding wordt schermvullend als achtergrond getoond met verminderde opaciteit zodat de tekst leesbaar blijft.': + 'The image is shown fullscreen as a background with reduced opacity so the text remains readable.', + 'De snelle bruine vos springt over de luie hond.': + 'The quick brown fox jumps over the lazy dog.', + 'Deze gegevens worden in de markdown opgeslagen en zijn doorzoekbaar bij het openen.': + 'These details are stored in the Markdown and searchable when opening.', + 'Deze presentatie heeft niet-opgeslagen wijzigingen. Sla de presentatie op voordat het tabblad sluit.': + 'This presentation has unsaved changes. Save it before closing the tab.', + 'Deze slide kan geen afbeelding ontvangen. Kies eerst een afbeeldingsslide.': + 'This slide cannot receive an image. Choose an image slide first.', + 'Eerste': 'First', + 'Einde van de presentatie': 'End of presentation', + 'Er is een presentatie met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'A presentation with unsaved changes was found from a previous session:', + 'Er zijn': 'There are', + 'Er zijn presentaties met niet-opgeslagen wijzigingen. Sla ze op voordat de app sluit.': + 'There are presentations with unsaved changes. Save them before closing the app.', + 'Export mislukt:': 'Export failed:', + 'Footer tonen op deze slide': 'Show footer on this slide', + 'Gebruik "Bladeren" om afbeeldingen van elke locatie te kiezen.': + 'Use “Browse” to choose images from any location.', + 'Geen afbeelding op het klembord gevonden.': + 'No image found on the clipboard.', + 'Geen ander deck open. Open eerst een ander tabblad.': + 'No other deck is open. Open another tab first.', + 'Geen andere presentaties (.md) in deze map gevonden.': + 'No other presentations (.md) found in this folder.', + 'Geen notities voor deze slide.': 'No notes for this slide.', + 'Geen presentaties (.md) in deze map gevonden.': + 'No presentations (.md) found in this folder.', + 'Geen presentaties gevonden voor': 'No presentations found for', + 'Geen resultaten': 'No results', + 'Geen resultaten voor': 'No results for', + 'Geen slides gevonden voor': 'No slides found for', + 'Geen slides met': 'No slides with', + 'Geselecteerd': 'Selected', + 'HTML opent in elke browser zonder internet en rendert codeblokken, wiskunde en mermaid-diagrammen.': + 'El HTML se abre en cualquier navegador sin internet y renderiza bloques de código, matemáticas y diagramas Mermaid.', + 'Het bestand wordt permanent van schijf verwijderd. Deze actie kan niet ongedaan worden gemaakt.': + 'The file will be permanently deleted from disk. This action cannot be undone.', + 'Ingezoomd': 'Zoomed in', + 'Inzoomen (minder van de foto zichtbaar)': + 'Zoom in (less of the photo visible)', + 'Kies een afbeelding': 'Choose an image', + 'Kies een map met presentaties om te beginnen.': + 'Choose a folder with presentations to begin.', + 'Kon dit pakket niet importeren.': 'Could not import this package.', + 'Kon niet van scherm wisselen.': 'Could not switch screens.', + 'Kon van deze URL geen presentatie ophalen.': + 'Could not fetch a presentation from this URL.', + 'Kopieer afbeelding naar klembord': 'Copy image to clipboard', + 'Kopiëren mislukt.': 'Copy failed.', + 'Kopiëren naar ander deck': 'Copy to another deck', + 'Kopiëren naar klembord mislukt.': 'Copying to clipboard failed.', + 'Koprij verwijderen': 'Remove header row', + 'Laat los om toe te voegen': 'Release to add', + 'Laatste slide': 'Diapositiva final', + 'Let op: deze afbeelding wordt nog gebruikt in': + 'Warning: this image is still used in', + 'Logo kiezen': 'Choose logo', + 'Logo px': 'Logo px', + 'Logo tonen op deze slide': 'Show logo on this slide', + 'Map met presentaties kiezen': 'Choose presentation folder', + 'Map voor exports': 'Export folder', + 'Markdown kon niet worden verwerkt. Controleer de syntax.': + 'Markdown could not be processed. Check the syntax.', + 'Markdown modus — bewerk de volledige presentatie als Marp Markdown': + 'Markdown mode — edit the full presentation as Marp Markdown', + 'Markdown voor laatste slide': 'Markdown para la diapositiva final', + 'Naam van het stijlprofiel': 'Name of the style profile', + 'Niet-opgeslagen werk herstellen?': 'Restore unsaved work?', + 'Niet-opgeslagen wijzigingen': 'Unsaved changes', + 'Niets vervangen': 'Nothing replaced', + 'Nieuw profiel': 'New profile', + 'Open eerst een presentatie om afbeeldingen toe te voegen.': + 'Open a presentation before adding images.', + 'Overslaan bij presenteren/exporteren': 'Skip when presenting/exporting', + 'PREVIEW': 'VISTA PREVIA', + 'Paginanummers tonen (rechtsonder)': 'Show page numbers (bottom right)', + 'Pakket geëxporteerd naar:': 'Package exported to:', + 'Pas je zoekterm aan of voeg een beschrijving toe.': + 'Adjust your search term or add a description.', + 'Plak de link naar een .ocideck-pakket of een Marp-markdownbestand.': + 'Paste the link to an .ocideck package or a Marp Markdown file.', + 'Preview inklappen': 'Collapse preview', + 'Preview uitklappen': 'Expand preview', + 'Profiel verwijderen': 'Delete profile', + 'Rij verwijderen': 'Remove row', + 'SLIDES': 'SLIDES', + 'Sectieachtergrond': 'Section background', + 'Selecteer een\nafbeelding': 'Select an\nimage', + 'Selectie opheffen': 'Clear selection', + 'Sleep om de slide-preview breder of smaller te maken': + 'Drag to make the slide preview wider or narrower', + 'Slide': 'Slide', + 'Slide gekopieerd naar klembord.': 'Slide copied to clipboard.', + 'Slide plakken': 'Paste slide', + 'Slide renderen…': 'Rendering slide…', + 'Slide toevoegen': 'Add slide', + 'Slides gerenderd.': 'Diapositivas renderizadas.', + 'Sluiten (G of Esc)': 'Close (G or Esc)', + 'Sprekersnotities...': 'Speaker notes...', + 'Standaard laatste slide gebruiken': + 'Usar diapositiva final predeterminada', + 'Standaard map voor presentaties': 'Default presentation folder', + 'Standaardprofiel laden': 'Load default profile', + 'TLP-classificatie (Traffic Light Protocol)': + 'TLP classification (Traffic Light Protocol)', + 'Tabel koptekst': 'Table header text', + 'Tabeltekst': 'Table text', + 'Terug naar standaardstijl': 'Back to default style', + 'Terugzetten (volledige afbeelding zichtbaar)': + 'Reset (full image visible)', + 'Tijd resetten (R)': 'Reset timer (R)', + 'Tip: druk op Enter binnen een cel voor een nieuwe regel.': + 'Tip: press Enter inside a cell for a new line.', + 'Titelachtergrond': 'Title background', + 'Titeltekst': 'Title text', + 'Toepassen': 'Apply', + 'Tokens: {page}, {total}, {date}, {title}. Footer verschijnt op alle slides behalve titel- en sectieslides, tenzij je hem per slide uitzet.': + 'Tokens: {page}, {total}, {date}, {title}. The footer appears on all slides except title and section slides, unless you disable it per slide.', + 'Typ zoektermen om slides uit al je presentaties te vinden.': + 'Type search terms to find slides across your presentations.', + 'Uitgezoomd': 'Zoomed out', + 'Uitzoomen (meer van de foto zichtbaar)': + 'Zoom out (more of the photo visible)', + 'Verwijder afbeelding': 'Remove image', + 'Verwijder logo': 'Remove logo', + 'Verwijderen maakt die slides leeg. Dit kan niet ongedaan worden gemaakt.': + 'Deleting will clear those slides. This cannot be undone.', + 'Volledig zichtbaar (100%)': 'Fully visible (100%)', + 'Vul een titel in': 'Enter a title', + 'Weer tonen': 'Show again', + 'Weer tonen bij presenteren/exporteren': + 'Show again when presenting/exporting', + 'Wordt automatisch toegevoegd bij presenteren en exporteren.': + 'Se añade automáticamente al presentar y exportar.', + 'Zoek in slides…': 'Search in slides…', + 'Zoek op bestandsnaam, titel of tekst in de slides…': + 'Search by file name, title or slide text…', + 'Zoek op naam of beschrijving…': 'Search by name or description…', + 'Zoek op presentatie, titel of tekst…': + 'Search by presentation, title or text…', + 'Zoek slides op tekst, titel, onderschrift, pad…': + 'Search slides by text, title, caption, path…', + 'bijv. Vertrouwelijk · {title} · {date}': + 'e.g. Confidential · {title} · {date}', + 'gerenderd.': 'renderizada.', + 'geselecteerd': 'selected', + 'meer treffer(s)': 'more match(es)', + 'paginering aan': 'pagination on', + 'pijltjes + Enter of klik om te springen': + 'arrows + Enter or click to jump', + 'presentaties met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'presentations with unsaved changes from a previous session:', + 'renderen…': 'renderizando…', + 'resultaat': 'result', + 'resultaten': 'results', + 'slide': 'slide', + 'slide(s) gekopieerd naar': 'slide(s) copied to', + 'slides geïmporteerd.': 'slides imported.', + 'slides kopiëren naar…': 'slides to copy to…', + 'slides overgeslagen': 'slides skipped', + 'toegevoegd': 'added', + 'treffer(s)': 'match(es)', + 'treffers — verfijn je zoekopdracht': 'matches, refine your search', + 'van de foto zichtbaar': 'of the photo visible', + 'vervangen': 'replaced', + 'verwijderen': 'remove', + 'volledig deck': 'full deck', + 'voorbereiden…': 'preparando…', + '↑↓←→ navigeren · Enter kiezen · Dubbelklik selecteert': + '↑↓←→ navigate · Enter chooses · Double-click selects', + }, + 'fy': { + '# Slide\n\nInhoud hier...': '# Slide\n\nContent here...', + '1 slide geïmporteerd.': '1 slide imported.', + '1 slide kopiëren naar…': 'Copy 1 slide to…', + '1 slide overgeslagen': '1 slide skipped', + 'Accent / bullets': 'Accent / bullets', + 'Achtergrond slides': 'Slide background', + 'Afbeelding': 'Ofbylding', + 'Afbeelding gekopieerd naar klembord.': 'Image copied to clipboard.', + 'Afbeelding plakken': 'Paste image', + 'Afbeelding plakken uit klembord': 'Paste image from clipboard', + 'Afbeeldingen → nieuwe slides · .md / .ocideck → openen': + 'Images → new slides · .md / .ocideck → open', + 'Afsluiten (Escape)': 'Exit (Escape)', + 'Alle slides zijn overgeslagen — niets om te exporteren.': + 'All slides are skipped, so there is nothing to export.', + 'Alle slides zijn overgeslagen — niets om te tonen.': + 'All slides are skipped, so there is nothing to show.', + 'Alles tonen': 'Show all', + 'Audio verwijderen': 'Remove audio', + 'Automatisch doorgaan na': 'Advance automatically after', + 'Bijv. Kwartaalupdate Q4': 'E.g. Q4 update', + 'Bullet': 'Puntsje', + 'Caption / bronvermelding (bijv. © Naam Fotograaf)': + 'Caption / credit (e.g. © Photographer Name)', + 'Coverflow': 'Coverflow', + 'De afbeelding wordt schermvullend als achtergrond getoond met verminderde opaciteit zodat de tekst leesbaar blijft.': + 'The image is shown fullscreen as a background with reduced opacity so the text remains readable.', + 'De snelle bruine vos springt over de luie hond.': + 'The quick brown fox jumps over the lazy dog.', + 'Deze gegevens worden in de markdown opgeslagen en zijn doorzoekbaar bij het openen.': + 'These details are stored in the Markdown and searchable when opening.', + 'Deze presentatie heeft niet-opgeslagen wijzigingen. Sla de presentatie op voordat het tabblad sluit.': + 'This presentation has unsaved changes. Save it before closing the tab.', + 'Deze slide kan geen afbeelding ontvangen. Kies eerst een afbeeldingsslide.': + 'This slide cannot receive an image. Choose an image slide first.', + 'Eerste': 'First', + 'Einde van de presentatie': 'End of presentation', + 'Er is een presentatie met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'A presentation with unsaved changes was found from a previous session:', + 'Er zijn': 'There are', + 'Er zijn presentaties met niet-opgeslagen wijzigingen. Sla ze op voordat de app sluit.': + 'There are presentations with unsaved changes. Save them before closing the app.', + 'Export mislukt:': 'Export failed:', + 'Footer tonen op deze slide': 'Show footer on this slide', + 'Gebruik "Bladeren" om afbeeldingen van elke locatie te kiezen.': + 'Use “Browse” to choose images from any location.', + 'Geen afbeelding op het klembord gevonden.': + 'No image found on the clipboard.', + 'Geen ander deck open. Open eerst een ander tabblad.': + 'No other deck is open. Open another tab first.', + 'Geen andere presentaties (.md) in deze map gevonden.': + 'No other presentations (.md) found in this folder.', + 'Geen presentaties (.md) in deze map gevonden.': + 'No presentations (.md) found in this folder.', + 'Geen presentaties gevonden voor': 'No presentations found for', + 'Geen resultaten': 'No results', + 'Geen resultaten voor': 'No results for', + 'Geen slides gevonden voor': 'No slides found for', + 'Geen slides met': 'No slides with', + 'Geselecteerd': 'Selected', + 'HTML opent in elke browser zonder internet en rendert codeblokken, wiskunde en mermaid-diagrammen.': + 'HTML iepenet yn elke browser sûnder ynternet en rendert koadeblokken, wiskunde en Mermaid-diagrammen.', + 'Het bestand wordt permanent van schijf verwijderd. Deze actie kan niet ongedaan worden gemaakt.': + 'The file will be permanently deleted from disk. This action cannot be undone.', + 'Ingezoomd': 'Zoomed in', + 'Inzoomen (minder van de foto zichtbaar)': + 'Zoom in (less of the photo visible)', + 'Kies een afbeelding': 'Choose an image', + 'Kies een map met presentaties om te beginnen.': + 'Choose a folder with presentations to begin.', + 'Kon dit pakket niet importeren.': 'Could not import this package.', + 'Kon van deze URL geen presentatie ophalen.': + 'Could not fetch a presentation from this URL.', + 'Kopieer afbeelding naar klembord': 'Copy image to clipboard', + 'Kopiëren mislukt.': 'Copy failed.', + 'Kopiëren naar ander deck': 'Copy to another deck', + 'Kopiëren naar klembord mislukt.': 'Copying to clipboard failed.', + 'Koprij verwijderen': 'Remove header row', + 'Laat los om toe te voegen': 'Release to add', + 'Laatste slide': 'Lêste slide', + 'Let op: deze afbeelding wordt nog gebruikt in': + 'Warning: this image is still used in', + 'Logo kiezen': 'Choose logo', + 'Logo px': 'Logo px', + 'Logo tonen op deze slide': 'Show logo on this slide', + 'Map met presentaties kiezen': 'Choose presentation folder', + 'Map voor exports': 'Export folder', + 'Markdown kon niet worden verwerkt. Controleer de syntax.': + 'Markdown could not be processed. Check the syntax.', + 'Markdown modus — bewerk de volledige presentatie als Marp Markdown': + 'Markdown mode — edit the full presentation as Marp Markdown', + 'Markdown voor laatste slide': 'Markdown foar de lêste slide', + 'Naam van het stijlprofiel': 'Name of the style profile', + 'Niet-opgeslagen werk herstellen?': 'Restore unsaved work?', + 'Niet-opgeslagen wijzigingen': 'Unsaved changes', + 'Niets vervangen': 'Nothing replaced', + 'Nieuw profiel': 'New profile', + 'Open eerst een presentatie om afbeeldingen toe te voegen.': + 'Open a presentation before adding images.', + 'Overslaan bij presenteren/exporteren': 'Skip when presenting/exporting', + 'PREVIEW': 'FOARBYLD', + 'Paginanummers tonen (rechtsonder)': 'Show page numbers (bottom right)', + 'Pakket geëxporteerd naar:': 'Package exported to:', + 'Pas je zoekterm aan of voeg een beschrijving toe.': + 'Adjust your search term or add a description.', + 'Plak de link naar een .ocideck-pakket of een Marp-markdownbestand.': + 'Paste the link to an .ocideck package or a Marp Markdown file.', + 'Preview inklappen': 'Collapse preview', + 'Preview uitklappen': 'Expand preview', + 'Profiel verwijderen': 'Delete profile', + 'Rij verwijderen': 'Remove row', + 'SLIDES': 'SLIDES', + 'Sectieachtergrond': 'Section background', + 'Selecteer een\nafbeelding': 'Select an\nimage', + 'Selectie opheffen': 'Clear selection', + 'Sleep om de slide-preview breder of smaller te maken': + 'Drag to make the slide preview wider or narrower', + 'Slide': 'Slide', + 'Slide gekopieerd naar klembord.': 'Slide copied to clipboard.', + 'Slide plakken': 'Paste slide', + 'Slide renderen…': 'Rendering slide…', + 'Slide toevoegen': 'Add slide', + 'Slides gerenderd.': 'Slides rendere.', + 'Sluiten (G of Esc)': 'Close (G or Esc)', + 'Sprekersnotities...': 'Speaker notes...', + 'Standaard laatste slide gebruiken': 'Standert lêste slide brûke', + 'Standaard map voor presentaties': 'Default presentation folder', + 'Standaardprofiel laden': 'Load default profile', + 'TLP-classificatie (Traffic Light Protocol)': + 'TLP classification (Traffic Light Protocol)', + 'Tabel koptekst': 'Table header text', + 'Tabeltekst': 'Table text', + 'Terug naar standaardstijl': 'Back to default style', + 'Terugzetten (volledige afbeelding zichtbaar)': + 'Reset (full image visible)', + 'Tijd resetten (R)': 'Reset timer (R)', + 'Tip: druk op Enter binnen een cel voor een nieuwe regel.': + 'Tip: press Enter inside a cell for a new line.', + 'Titelachtergrond': 'Title background', + 'Titeltekst': 'Title text', + 'Toepassen': 'Apply', + 'Tokens: {page}, {total}, {date}, {title}. Footer verschijnt op alle slides behalve titel- en sectieslides, tenzij je hem per slide uitzet.': + 'Tokens: {page}, {total}, {date}, {title}. The footer appears on all slides except title and section slides, unless you disable it per slide.', + 'Typ zoektermen om slides uit al je presentaties te vinden.': + 'Type search terms to find slides across your presentations.', + 'Uitgezoomd': 'Zoomed out', + 'Uitzoomen (meer van de foto zichtbaar)': + 'Zoom out (more of the photo visible)', + 'Verwijder afbeelding': 'Remove image', + 'Verwijder logo': 'Remove logo', + 'Verwijderen maakt die slides leeg. Dit kan niet ongedaan worden gemaakt.': + 'Deleting will clear those slides. This cannot be undone.', + 'Volledig zichtbaar (100%)': 'Fully visible (100%)', + 'Vul een titel in': 'Enter a title', + 'Weer tonen': 'Show again', + 'Weer tonen bij presenteren/exporteren': + 'Show again when presenting/exporting', + 'Wordt automatisch toegevoegd bij presenteren en exporteren.': + 'Wurdt automatysk tafoege by presintearjen en eksportearjen.', + 'Zoek in slides…': 'Search in slides…', + 'Zoek op bestandsnaam, titel of tekst in de slides…': + 'Search by file name, title or slide text…', + 'Zoek op naam of beschrijving…': 'Search by name or description…', + 'Zoek op presentatie, titel of tekst…': + 'Search by presentation, title or text…', + 'Zoek slides op tekst, titel, onderschrift, pad…': + 'Search slides by text, title, caption, path…', + 'bijv. Vertrouwelijk · {title} · {date}': + 'e.g. Confidential · {title} · {date}', + 'gerenderd.': 'rendere.', + 'geselecteerd': 'selected', + 'meer treffer(s)': 'more match(es)', + 'paginering aan': 'pagination on', + 'pijltjes + Enter of klik om te springen': + 'arrows + Enter or click to jump', + 'presentaties met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'presentations with unsaved changes from a previous session:', + 'renderen…': 'rendere…', + 'resultaat': 'result', + 'resultaten': 'results', + 'slide': 'slide', + 'slide(s) gekopieerd naar': 'slide(s) copied to', + 'slides geïmporteerd.': 'slides imported.', + 'slides kopiëren naar…': 'slides to copy to…', + 'slides overgeslagen': 'slides skipped', + 'toegevoegd': 'added', + 'treffer(s)': 'match(es)', + 'treffers — verfijn je zoekopdracht': 'matches, refine your search', + 'van de foto zichtbaar': 'of the photo visible', + 'vervangen': 'replaced', + 'verwijderen': 'remove', + 'volledig deck': 'full deck', + 'voorbereiden…': 'tariede…', + '↑↓←→ navigeren · Enter kiezen · Dubbelklik selecteert': + '↑↓←→ navigate · Enter chooses · Double-click selects', + }, + 'pap': { + '# Slide\n\nInhoud hier...': '# Slide\n\nContent here...', + '1 slide geïmporteerd.': '1 slide imported.', + '1 slide kopiëren naar…': 'Copy 1 slide to…', + '1 slide overgeslagen': '1 slide skipped', + 'Accent / bullets': 'Accent / bullets', + 'Achtergrond slides': 'Slide background', + 'Afbeelding': 'Imágen', + 'Afbeelding gekopieerd naar klembord.': 'Image copied to clipboard.', + 'Afbeelding plakken': 'Paste image', + 'Afbeelding plakken uit klembord': 'Paste image from clipboard', + 'Afbeeldingen → nieuwe slides · .md / .ocideck → openen': + 'Images → new slides · .md / .ocideck → open', + 'Afsluiten (Escape)': 'Exit (Escape)', + 'Alle slides zijn overgeslagen — niets om te exporteren.': + 'All slides are skipped, so there is nothing to export.', + 'Alle slides zijn overgeslagen — niets om te tonen.': + 'All slides are skipped, so there is nothing to show.', + 'Alles tonen': 'Show all', + 'Audio verwijderen': 'Remove audio', + 'Automatisch doorgaan na': 'Advance automatically after', + 'Bijv. Kwartaalupdate Q4': 'E.g. Q4 update', + 'Bullet': 'Punto', + 'Caption / bronvermelding (bijv. © Naam Fotograaf)': + 'Caption / credit (e.g. © Photographer Name)', + 'Coverflow': 'Coverflow', + 'De afbeelding wordt schermvullend als achtergrond getoond met verminderde opaciteit zodat de tekst leesbaar blijft.': + 'The image is shown fullscreen as a background with reduced opacity so the text remains readable.', + 'De snelle bruine vos springt over de luie hond.': + 'The quick brown fox jumps over the lazy dog.', + 'Deze gegevens worden in de markdown opgeslagen en zijn doorzoekbaar bij het openen.': + 'These details are stored in the Markdown and searchable when opening.', + 'Deze presentatie heeft niet-opgeslagen wijzigingen. Sla de presentatie op voordat het tabblad sluit.': + 'This presentation has unsaved changes. Save it before closing the tab.', + 'Deze slide kan geen afbeelding ontvangen. Kies eerst een afbeeldingsslide.': + 'This slide cannot receive an image. Choose an image slide first.', + 'Eerste': 'First', + 'Einde van de presentatie': 'End of presentation', + 'Er is een presentatie met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'A presentation with unsaved changes was found from a previous session:', + 'Er zijn': 'There are', + 'Er zijn presentaties met niet-opgeslagen wijzigingen. Sla ze op voordat de app sluit.': + 'There are presentations with unsaved changes. Save them before closing the app.', + 'Export mislukt:': 'Export failed:', + 'Footer tonen op deze slide': 'Show footer on this slide', + 'Gebruik "Bladeren" om afbeeldingen van elke locatie te kiezen.': + 'Use “Browse” to choose images from any location.', + 'Geen afbeelding op het klembord gevonden.': + 'No image found on the clipboard.', + 'Geen ander deck open. Open eerst een ander tabblad.': + 'No other deck is open. Open another tab first.', + 'Geen andere presentaties (.md) in deze map gevonden.': + 'No other presentations (.md) found in this folder.', + 'Geen presentaties (.md) in deze map gevonden.': + 'No presentations (.md) found in this folder.', + 'Geen presentaties gevonden voor': 'No presentations found for', + 'Geen resultaten': 'No results', + 'Geen resultaten voor': 'No results for', + 'Geen slides gevonden voor': 'No slides found for', + 'Geen slides met': 'No slides with', + 'Geselecteerd': 'Selected', + 'HTML opent in elke browser zonder internet en rendert codeblokken, wiskunde en mermaid-diagrammen.': + 'HTML ta habri den tur browser sin internet i ta render bloknan di kódigo, matemátika i diagramnan Mermaid.', + 'Het bestand wordt permanent van schijf verwijderd. Deze actie kan niet ongedaan worden gemaakt.': + 'The file will be permanently deleted from disk. This action cannot be undone.', + 'Ingezoomd': 'Zoomed in', + 'Inzoomen (minder van de foto zichtbaar)': + 'Zoom in (less of the photo visible)', + 'Kies een afbeelding': 'Choose an image', + 'Kies een map met presentaties om te beginnen.': + 'Choose a folder with presentations to begin.', + 'Kon dit pakket niet importeren.': 'Could not import this package.', + 'Kon van deze URL geen presentatie ophalen.': + 'Could not fetch a presentation from this URL.', + 'Kopieer afbeelding naar klembord': 'Copy image to clipboard', + 'Kopiëren mislukt.': 'Copy failed.', + 'Kopiëren naar ander deck': 'Copy to another deck', + 'Kopiëren naar klembord mislukt.': 'Copying to clipboard failed.', + 'Koprij verwijderen': 'Remove header row', + 'Laat los om toe te voegen': 'Release to add', + 'Laatste slide': 'Último slide', + 'Let op: deze afbeelding wordt nog gebruikt in': + 'Warning: this image is still used in', + 'Logo kiezen': 'Choose logo', + 'Logo px': 'Logo px', + 'Logo tonen op deze slide': 'Show logo on this slide', + 'Map met presentaties kiezen': 'Choose presentation folder', + 'Map voor exports': 'Export folder', + 'Markdown kon niet worden verwerkt. Controleer de syntax.': + 'Markdown could not be processed. Check the syntax.', + 'Markdown modus — bewerk de volledige presentatie als Marp Markdown': + 'Markdown mode — edit the full presentation as Marp Markdown', + 'Markdown voor laatste slide': 'Markdown pa e último slide', + 'Naam van het stijlprofiel': 'Name of the style profile', + 'Niet-opgeslagen werk herstellen?': 'Restore unsaved work?', + 'Niet-opgeslagen wijzigingen': 'Unsaved changes', + 'Niets vervangen': 'Nothing replaced', + 'Nieuw profiel': 'New profile', + 'Open eerst een presentatie om afbeeldingen toe te voegen.': + 'Open a presentation before adding images.', + 'Overslaan bij presenteren/exporteren': 'Skip when presenting/exporting', + 'PREVIEW': 'PREVIEW', + 'Paginanummers tonen (rechtsonder)': 'Show page numbers (bottom right)', + 'Pakket geëxporteerd naar:': 'Package exported to:', + 'Pas je zoekterm aan of voeg een beschrijving toe.': + 'Adjust your search term or add a description.', + 'Plak de link naar een .ocideck-pakket of een Marp-markdownbestand.': + 'Paste the link to an .ocideck package or a Marp Markdown file.', + 'Preview inklappen': 'Collapse preview', + 'Preview uitklappen': 'Expand preview', + 'Profiel verwijderen': 'Delete profile', + 'Rij verwijderen': 'Remove row', + 'SLIDES': 'SLIDES', + 'Sectieachtergrond': 'Section background', + 'Selecteer een\nafbeelding': 'Select an\nimage', + 'Selectie opheffen': 'Clear selection', + 'Sleep om de slide-preview breder of smaller te maken': + 'Drag to make the slide preview wider or narrower', + 'Slide': 'Slide', + 'Slide gekopieerd naar klembord.': 'Slide copied to clipboard.', + 'Slide plakken': 'Paste slide', + 'Slide renderen…': 'Rendering slide…', + 'Slide toevoegen': 'Add slide', + 'Slides gerenderd.': 'Slides a wordu render.', + 'Sluiten (G of Esc)': 'Close (G or Esc)', + 'Sprekersnotities...': 'Speaker notes...', + 'Standaard laatste slide gebruiken': 'Usa e último slide standard', + 'Standaard map voor presentaties': 'Default presentation folder', + 'Standaardprofiel laden': 'Load default profile', + 'TLP-classificatie (Traffic Light Protocol)': + 'TLP classification (Traffic Light Protocol)', + 'Tabel koptekst': 'Table header text', + 'Tabeltekst': 'Table text', + 'Terug naar standaardstijl': 'Back to default style', + 'Terugzetten (volledige afbeelding zichtbaar)': + 'Reset (full image visible)', + 'Tijd resetten (R)': 'Reset timer (R)', + 'Tip: druk op Enter binnen een cel voor een nieuwe regel.': + 'Tip: press Enter inside a cell for a new line.', + 'Titelachtergrond': 'Title background', + 'Titeltekst': 'Title text', + 'Toepassen': 'Apply', + 'Tokens: {page}, {total}, {date}, {title}. Footer verschijnt op alle slides behalve titel- en sectieslides, tenzij je hem per slide uitzet.': + 'Tokens: {page}, {total}, {date}, {title}. The footer appears on all slides except title and section slides, unless you disable it per slide.', + 'Typ zoektermen om slides uit al je presentaties te vinden.': + 'Type search terms to find slides across your presentations.', + 'Uitgezoomd': 'Zoomed out', + 'Uitzoomen (meer van de foto zichtbaar)': + 'Zoom out (more of the photo visible)', + 'Verwijder afbeelding': 'Remove image', + 'Verwijder logo': 'Remove logo', + 'Verwijderen maakt die slides leeg. Dit kan niet ongedaan worden gemaakt.': + 'Deleting will clear those slides. This cannot be undone.', + 'Volledig zichtbaar (100%)': 'Fully visible (100%)', + 'Vul een titel in': 'Enter a title', + 'Weer tonen': 'Show again', + 'Weer tonen bij presenteren/exporteren': + 'Show again when presenting/exporting', + 'Wordt automatisch toegevoegd bij presenteren en exporteren.': + 'Ta wordu agregá automáticamente ora di presentá i eksportá.', + 'Zoek in slides…': 'Search in slides…', + 'Zoek op bestandsnaam, titel of tekst in de slides…': + 'Search by file name, title or slide text…', + 'Zoek op naam of beschrijving…': 'Search by name or description…', + 'Zoek op presentatie, titel of tekst…': + 'Search by presentation, title or text…', + 'Zoek slides op tekst, titel, onderschrift, pad…': + 'Search slides by text, title, caption, path…', + 'bijv. Vertrouwelijk · {title} · {date}': + 'e.g. Confidential · {title} · {date}', + 'gerenderd.': 'render.', + 'geselecteerd': 'selected', + 'meer treffer(s)': 'more match(es)', + 'paginering aan': 'pagination on', + 'pijltjes + Enter of klik om te springen': + 'arrows + Enter or click to jump', + 'presentaties met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': + 'presentations with unsaved changes from a previous session:', + 'renderen…': 'render…', + 'resultaat': 'result', + 'resultaten': 'results', + 'slide': 'slide', + 'slide(s) gekopieerd naar': 'slide(s) copied to', + 'slides geïmporteerd.': 'slides imported.', + 'slides kopiëren naar…': 'slides to copy to…', + 'slides overgeslagen': 'slides skipped', + 'toegevoegd': 'added', + 'treffer(s)': 'match(es)', + 'treffers — verfijn je zoekopdracht': 'matches, refine your search', + 'van de foto zichtbaar': 'of the photo visible', + 'vervangen': 'replaced', + 'verwijderen': 'remove', + 'volledig deck': 'full deck', + 'voorbereiden…': 'preparando…', + '↑↓←→ navigeren · Enter kiezen · Dubbelklik selecteert': + '↑↓←→ navigate · Enter chooses · Double-click selects', + }, +}; diff --git a/lib/state/deck_provider.dart b/lib/state/deck_provider.dart index 38a2228..d056a4c 100644 --- a/lib/state/deck_provider.dart +++ b/lib/state/deck_provider.dart @@ -384,6 +384,7 @@ class DeckNotifier extends StateNotifier { } void updateInfo({ + String? title, String? author, String? organization, String? version, @@ -396,6 +397,7 @@ class DeckNotifier extends StateNotifier { if (deck == null) return; _mutate( deck.copyWith( + title: title, author: author, organization: organization, version: version, diff --git a/lib/widgets/app_shell.dart b/lib/widgets/app_shell.dart index 0fd1e6f..0e92909 100644 --- a/lib/widgets/app_shell.dart +++ b/lib/widgets/app_shell.dart @@ -477,27 +477,32 @@ class _DropOverlay extends StatelessWidget { borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFF60A5FA), width: 2), ), - child: const Column( + child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon( + const Icon( Icons.file_download_outlined, size: 40, color: Color(0xFF2563EB), ), - SizedBox(height: 10), + const SizedBox(height: 10), Text( - 'Laat los om toe te voegen', - style: TextStyle( + context.l10n.d('Laat los om toe te voegen'), + style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1E293B), ), ), - SizedBox(height: 4), + const SizedBox(height: 4), Text( - 'Afbeeldingen → nieuwe slides · .md / .ocideck → openen', - style: TextStyle(fontSize: 12, color: Color(0xFF64748B)), + context.l10n.d( + 'Afbeeldingen → nieuwe slides · .md / .ocideck → openen', + ), + style: const TextStyle( + fontSize: 12, + color: Color(0xFF64748B), + ), ), ], ), @@ -1007,6 +1012,7 @@ class _MainLayoutState extends ConsumerState<_MainLayout> { final info = await PresentationInfoDialog.show(context, deck); if (info == null) return; deckNotifier.updateInfo( + title: info.title, author: info.author, organization: info.organization, version: info.version, @@ -1146,6 +1152,14 @@ class _MainLayoutState extends ConsumerState<_MainLayout> { tlp: deck.tlp, onSelected: (level) => deckNotifier.updateInfo(tlp: level), ), + const SizedBox(width: 6), + Tooltip( + message: l10n.t('presentationProperties'), + child: IconButton( + icon: const Icon(Icons.info_outline, size: 18), + onPressed: openProperties, + ), + ), ], ), actions: [ @@ -1292,11 +1306,6 @@ class _MainLayoutState extends ConsumerState<_MainLayout> { ), ), const PopupMenuDivider(), - menuItem( - 'properties', - Icons.info_outline, - l10n.t('presentationProperties'), - ), menuItem( 'settings', Icons.settings_outlined, diff --git a/lib/widgets/dialogs/export_dialog.dart b/lib/widgets/dialogs/export_dialog.dart index 26acf00..a9db743 100644 --- a/lib/widgets/dialogs/export_dialog.dart +++ b/lib/widgets/dialogs/export_dialog.dart @@ -293,12 +293,13 @@ class _ExportDialogState extends State { label: l10n.t('exportAsHtml'), onPressed: () => _export(ExportFormat.html), ), - const Padding( - padding: EdgeInsets.only(top: 4), + Padding( + padding: const EdgeInsets.only(top: 4), child: Text( - 'HTML opent in elke browser zonder internet en rendert codeblokken, ' - 'wiskunde en mermaid-diagrammen.', - style: TextStyle(fontSize: 11, color: Color(0xFF94A3B8)), + l10n.d( + 'HTML opent in elke browser zonder internet en rendert codeblokken, wiskunde en mermaid-diagrammen.', + ), + style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8)), ), ), ], diff --git a/lib/widgets/dialogs/presentation_info_dialog.dart b/lib/widgets/dialogs/presentation_info_dialog.dart index 83f31dd..2535395 100644 --- a/lib/widgets/dialogs/presentation_info_dialog.dart +++ b/lib/widgets/dialogs/presentation_info_dialog.dart @@ -5,6 +5,7 @@ import '../../l10n/app_localizations.dart'; /// The editable general metadata of a presentation. class PresentationInfo { + final String title; final String author; final String organization; final String version; @@ -13,6 +14,7 @@ class PresentationInfo { final String keywords; const PresentationInfo({ + required this.title, required this.author, required this.organization, required this.version, @@ -42,6 +44,7 @@ class PresentationInfoDialog extends StatefulWidget { } class _PresentationInfoDialogState extends State { + late final TextEditingController _title; late final TextEditingController _author; late final TextEditingController _organization; late final TextEditingController _version; @@ -52,6 +55,7 @@ class _PresentationInfoDialogState extends State { @override void initState() { super.initState(); + _title = TextEditingController(text: widget.deck.title); _author = TextEditingController(text: widget.deck.author); _organization = TextEditingController(text: widget.deck.organization); _version = TextEditingController(text: widget.deck.version); @@ -62,6 +66,7 @@ class _PresentationInfoDialogState extends State { @override void dispose() { + _title.dispose(); _author.dispose(); _organization.dispose(); _version.dispose(); @@ -75,6 +80,7 @@ class _PresentationInfoDialogState extends State { Navigator.pop( context, PresentationInfo( + title: _title.text.trim(), author: _author.text.trim(), organization: _organization.text.trim(), version: _version.text.trim(), @@ -108,14 +114,7 @@ class _PresentationInfoDialogState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - widget.deck.title, - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - color: Color(0xFF64748B), - ), - ), + _field(_title, 'Titel', 'Titel van de presentatie'), const SizedBox(height: 12), Row( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/widgets/dialogs/settings_dialog.dart b/lib/widgets/dialogs/settings_dialog.dart index f4cd958..6833573 100644 --- a/lib/widgets/dialogs/settings_dialog.dart +++ b/lib/widgets/dialogs/settings_dialog.dart @@ -173,25 +173,26 @@ class _SettingsDialogState extends ConsumerState { : profiles.first.name; return DefaultTabController( - length: 3, + length: 4, child: AlertDialog( title: Text(l10n.t('settings')), content: SizedBox( width: 520, - height: 560, + height: 600, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - _profileSelector(profiles, dropdownValue), - const SizedBox(height: 12), - _profileNameField(), - const SizedBox(height: 12), TabBar( + isScrollable: true, tabs: [ Tab( icon: const Icon(Icons.tune), text: l10n.t('settingsGeneral'), ), + Tab( + icon: const Icon(Icons.style_outlined), + text: l10n.t('styleProfile'), + ), Tab( icon: const Icon(Icons.palette_outlined), text: l10n.t('settingsColors'), @@ -207,6 +208,7 @@ class _SettingsDialogState extends ConsumerState { child: TabBarView( children: [ _tabBody(_generalTab()), + _tabBody(_styleTab(profiles, dropdownValue)), _tabBody(_colorsTab()), _tabBody(_logoTab()), ], @@ -350,6 +352,24 @@ class _SettingsDialogState extends ConsumerState { ); } + Widget _styleTab(List profiles, String dropdownValue) { + final l10n = context.l10n; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _sectionTitle(l10n.t('styleProfile')), + _profileSelector(profiles, dropdownValue), + const SizedBox(height: 12), + _profileNameField(), + const SizedBox(height: 20), + _sectionTitle(l10n.d('Lettertype')), + _fontSection(), + const SizedBox(height: 18), + _stylePreview(), + ], + ); + } + Widget _generalTab() { final l10n = context.l10n; final languageCode = ref.watch( @@ -507,9 +527,6 @@ class _SettingsDialogState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _sectionTitle(l10n.d('Lettertype')), - _fontSection(), - const SizedBox(height: 20), _sectionTitle(l10n.d('Kleuren')), _colorSetting( l10n.d('Achtergrond slides'), @@ -638,7 +655,10 @@ class _SettingsDialogState extends ConsumerState { width: 160, child: TextField( controller: _logoSize, - decoration: InputDecoration(labelText: 'Logo px', isDense: true), + decoration: InputDecoration( + labelText: context.l10n.d('Logo px'), + isDense: true, + ), keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], onChanged: (_) => _profileTouched = true, @@ -754,7 +774,7 @@ class _SettingsDialogState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - label, + '$label $value', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, @@ -767,29 +787,13 @@ class _SettingsDialogState extends ConsumerState { runSpacing: 6, children: [ for (final color in _colorPresets) - Tooltip( - message: color, - child: InkWell( - onTap: () => setState(() { - onChanged(color); - _profileTouched = true; - }), - borderRadius: BorderRadius.circular(12), - child: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - color: _parseColor(color), - shape: BoxShape.circle, - border: Border.all( - color: value == color - ? AppTheme.accent - : const Color(0xFFCBD5E1), - width: value == color ? 2 : 1, - ), - ), - ), - ), + _colorSwatch( + color, + selected: value == color, + onTap: () => setState(() { + onChanged(color); + _profileTouched = true; + }), ), ], ), @@ -797,6 +801,73 @@ class _SettingsDialogState extends ConsumerState { ); } + Widget _colorSwatch( + String color, { + required bool selected, + required VoidCallback onTap, + }) { + final parsed = _parseColor(color); + final checkColor = parsed.computeLuminance() > 0.55 + ? const Color(0xFF0F172A) + : Colors.white; + return Tooltip( + message: selected ? '${context.l10n.d('Geselecteerd')}: $color' : color, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: AnimatedContainer( + duration: const Duration(milliseconds: 120), + width: 34, + height: 34, + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: selected + ? AppTheme.accent.withValues(alpha: 0.12) + : Colors.transparent, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: selected ? AppTheme.accent : const Color(0xFFCBD5E1), + width: selected ? 2 : 1, + ), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Container( + decoration: BoxDecoration( + color: parsed, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2), + boxShadow: const [ + BoxShadow( + color: Color(0x330F172A), + blurRadius: 2, + offset: Offset(0, 1), + ), + ], + ), + ), + if (selected) + Icon( + Icons.check, + size: 16, + color: checkColor, + shadows: [ + Shadow( + color: checkColor == Colors.white + ? Colors.black54 + : Colors.white70, + blurRadius: 2, + ), + ], + ), + ], + ), + ), + ), + ); + } + Widget _stylePreview() { final l10n = context.l10n; return Container( diff --git a/lib/widgets/panels/preview_panel.dart b/lib/widgets/panels/preview_panel.dart index 445277d..5daa044 100644 --- a/lib/widgets/panels/preview_panel.dart +++ b/lib/widgets/panels/preview_panel.dart @@ -442,7 +442,7 @@ class CollapsedPreviewBar extends ConsumerWidget { RotatedBox( quarterTurns: 1, child: Text( - 'PREVIEW', + context.l10n.d('PREVIEW'), style: TextStyle( fontSize: 10, letterSpacing: 1.5, diff --git a/lib/widgets/presentation/fullscreen_presenter.dart b/lib/widgets/presentation/fullscreen_presenter.dart index 0a92b14..e397dc6 100644 --- a/lib/widgets/presentation/fullscreen_presenter.dart +++ b/lib/widgets/presentation/fullscreen_presenter.dart @@ -38,24 +38,30 @@ class FullscreenPresenter extends StatefulWidget { required int initialIndex, TlpLevel tlp = TlpLevel.none, }) async { - await windowManager.setFullScreen(true); - if (context.mounted) { - await Navigator.push( - context, - PageRouteBuilder( - opaque: true, - pageBuilder: (context, anim, anim2) => FullscreenPresenter( - slides: slides, - projectPath: projectPath, - themeProfile: themeProfile, - initialIndex: initialIndex, - tlp: tlp, + final hadWakeLock = await _wakeLockEnabled(); + await _enableWakeLock(); + try { + await windowManager.setFullScreen(true); + if (context.mounted) { + await Navigator.push( + context, + PageRouteBuilder( + opaque: true, + pageBuilder: (context, anim, anim2) => FullscreenPresenter( + slides: slides, + projectPath: projectPath, + themeProfile: themeProfile, + initialIndex: initialIndex, + tlp: tlp, + ), + transitionsBuilder: (context, animation, secondary, child) => + FadeTransition(opacity: animation, child: child), + transitionDuration: const Duration(milliseconds: 200), ), - transitionsBuilder: (context, animation, secondary, child) => - FadeTransition(opacity: animation, child: child), - transitionDuration: const Duration(milliseconds: 200), - ), - ); + ); + } + } finally { + await _restoreWakeLock(hadWakeLock); } } @@ -63,6 +69,34 @@ class FullscreenPresenter extends StatefulWidget { State createState() => _FullscreenPresenterState(); } +Future _wakeLockEnabled() async { + try { + return await WakelockPlus.enabled; + } catch (_) { + return false; + } +} + +Future _enableWakeLock() async { + try { + await WakelockPlus.enable(); + } catch (_) { + // Best-effort: unsupported platforms should not interrupt presenting. + } +} + +Future _restoreWakeLock(bool enabledBeforePresentation) async { + try { + if (enabledBeforePresentation) { + await WakelockPlus.enable(); + } else { + await WakelockPlus.disable(); + } + } catch (_) { + // Best-effort cleanup. + } +} + class _FullscreenPresenterState extends State { late int _index; late FocusNode _focusNode; @@ -125,7 +159,6 @@ class _FullscreenPresenterState extends State { _clockTimer = Timer.periodic(const Duration(seconds: 1), (_) { if (mounted && _presenterView) setState(() {}); }); - _enableWakeLock(); WidgetsBinding.instance.addPostFrameCallback((_) { _focusNode.requestFocus(); _loadDisplays(); @@ -138,28 +171,11 @@ class _FullscreenPresenterState extends State { _advanceTimer?.cancel(); _clockTimer?.cancel(); _typedTimer?.cancel(); - _disableWakeLock(); _gridScroll.dispose(); _focusNode.dispose(); super.dispose(); } - Future _enableWakeLock() async { - try { - await WakelockPlus.enable(); - } catch (_) { - // Best-effort: unsupported platforms should not interrupt presenting. - } - } - - Future _disableWakeLock() async { - try { - await WakelockPlus.disable(); - } catch (_) { - // Best-effort cleanup. - } - } - void _scheduleAdvance() { _advanceTimer?.cancel(); _advanceTimer = null; @@ -287,7 +303,6 @@ class _FullscreenPresenterState extends State { Future _exit() async { _advanceTimer?.cancel(); - await _disableWakeLock(); await windowManager.setFullScreen(false); if (mounted) Navigator.pop(context); } diff --git a/lib/widgets/slides/slide_preview.dart b/lib/widgets/slides/slide_preview.dart index 37f3030..a37eb5b 100644 --- a/lib/widgets/slides/slide_preview.dart +++ b/lib/widgets/slides/slide_preview.dart @@ -6,6 +6,7 @@ import 'package:flutter_math_fork/flutter_math.dart'; import 'package:highlight/highlight.dart' show highlight; import 'package:highlight/languages/all.dart' show allLanguages; import 'package:video_player/video_player.dart'; +import '../../l10n/app_localizations.dart'; import '../../models/deck.dart'; import '../../models/settings.dart'; import '../../models/slide.dart'; @@ -154,6 +155,10 @@ class SlidePreviewWidget extends StatelessWidget { @override Widget build(BuildContext context) { + final hasBottomRightTlp = + tlp != TlpLevel.none && + !((themeProfile.logoPath?.isNotEmpty == true && slide.showLogo) && + themeProfile.logoPosition == 'bottom-right'); // Make the widget self-sufficient for text rendering. On screen it sits // inside a Material (which supplies a clean DefaultTextStyle), but the // export rasterizer mounts it in a bare Overlay subtree. Without an @@ -172,7 +177,7 @@ class SlidePreviewWidget extends StatelessWidget { ), child: _SlideLinkScope( onTapLink: onLinkTap, - hasBottomTlp: tlp != TlpLevel.none, + hasBottomTlp: hasBottomRightTlp, child: _buildSlide(), ), ), @@ -199,7 +204,14 @@ class SlidePreviewWidget extends StatelessWidget { tlp: tlp, ), if (tlp != TlpLevel.none) - _TlpOverlay(tlp: tlp, w: w, profile: themeProfile), + _TlpOverlay( + tlp: tlp, + w: w, + profile: themeProfile, + hasLogo: + themeProfile.logoPath?.isNotEmpty == true && + slide.showLogo, + ), if (themeProfile.logoPath?.isNotEmpty == true && slide.showLogo) _LogoOverlay( logoPath: themeProfile.logoPath!, @@ -502,6 +514,7 @@ class _TitlePreview extends StatelessWidget { fit: StackFit.expand, children: [ _zoomedImage( + context, slide.imagePath, projectPath, slide.imageSize, @@ -1065,13 +1078,8 @@ class _BulletsImagePreview extends StatelessWidget { child: Stack( fit: StackFit.expand, children: [ - _resolvedImage(slide.imagePath, projectPath), - _captionOverlay( - context, - slide.imageCaption, - w, - right: w * 0.018, - ), + _resolvedImage(context, slide.imagePath, projectPath), + _captionOverlay(context, slide.imageCaption, w), ], ), ), @@ -1449,7 +1457,7 @@ class _TwoImagesPreview extends StatelessWidget { child: Stack( fit: StackFit.expand, children: [ - _resolvedImage(slide.imagePath, projectPath), + _resolvedImage(context, slide.imagePath, projectPath), _captionOverlay(context, slide.imageCaption, w), ], ), @@ -1459,7 +1467,7 @@ class _TwoImagesPreview extends StatelessWidget { child: Stack( fit: StackFit.expand, children: [ - _resolvedImage(slide.imagePath2, projectPath), + _resolvedImage(context, slide.imagePath2, projectPath), _captionOverlay(context, slide.imageCaption2, w), ], ), @@ -1524,6 +1532,7 @@ class _ImagePreview extends StatelessWidget { fit: StackFit.expand, children: [ _zoomedImage( + context, slide.imagePath, projectPath, slide.imageSize, @@ -1792,6 +1801,7 @@ class _QuotePreview extends StatelessWidget { fit: StackFit.expand, children: [ _zoomedImage( + context, slide.imagePath, projectPath, slide.imageSize, @@ -1831,7 +1841,12 @@ class _LogoOverlay extends StatelessWidget { child: SizedBox( width: size, height: size, - child: _resolvedImage(logoPath, projectPath, fit: BoxFit.contain), + child: _resolvedImage( + context, + logoPath, + projectPath, + fit: BoxFit.contain, + ), ), ); } @@ -2047,6 +2062,7 @@ void _ensureHighlightLanguages() { /// imageSize > 100 → inzoomen: groter dan contain, bijgesneden door ClipRect /// imageSize < 100 → nog meer uitzoomen: afbeelding kleiner dan contain Widget _zoomedImage( + BuildContext context, String imagePath, String? projectPath, int imageSize, { @@ -2054,7 +2070,11 @@ Widget _zoomedImage( Alignment alignment = Alignment.center, }) { if (imageSize == 0) { - return _resolvedImage(imagePath, projectPath); // BoxFit.cover standaard + return _resolvedImage( + context, + imagePath, + projectPath, + ); // BoxFit.cover standaard } final scale = imageSize / 100.0; // Size the image box to `scale` × the available area and let BoxFit.contain @@ -2076,6 +2096,7 @@ Widget _zoomedImage( height: boxH, // BoxFit.contain: toont de volledige afbeelding zonder bijsnijden child: _resolvedImage( + context, imagePath, projectPath, fit: BoxFit.contain, @@ -2089,11 +2110,12 @@ Widget _zoomedImage( } Widget _resolvedImage( + BuildContext context, String imagePath, String? projectPath, { BoxFit fit = BoxFit.cover, }) { - if (imagePath.isEmpty) return _imagePlaceholder(); + if (imagePath.isEmpty) return _imagePlaceholder(context); final String resolved; if (imagePath.startsWith('/') || imagePath.contains(':\\')) { @@ -2109,7 +2131,7 @@ Widget _resolvedImage( fit: fit, width: double.infinity, height: double.infinity, - errorBuilder: (context, error, stackTrace) => _imagePlaceholder(), + errorBuilder: (context, error, stackTrace) => _imagePlaceholder(context), ); } @@ -2128,8 +2150,8 @@ Widget _captionOverlay( ? _tlpVerticalReserve(w) : 0.0; return Positioned( - right: right ?? w * 0.018, - bottom: (bottom ?? w * 0.014) + lift, + right: right ?? w * _kTlpEdge, + bottom: (bottom ?? _tlpBottomInset(w)) + lift, child: Container( constraints: BoxConstraints(maxWidth: w * 0.5), padding: EdgeInsets.symmetric(horizontal: w * 0.008, vertical: w * 0.005), @@ -2165,13 +2187,15 @@ const double _kTlpEdge = 0.025; // afstand tot de slidehoek (× breedte) const double _kTlpHPad = 0.011; const double _kTlpVPad = 0.005; +double _tlpBottomInset(double w) => w * 0.022; + /// Geschatte breedte van de TLP-badge, zodat de footer ervoor kan uitwijken. double _tlpBadgeWidth(double w, TlpLevel tlp) => tlp.label.length * w * _kTlpFont * 0.62 + 2 * (w * _kTlpHPad); /// Verticale ruimte die een TLP-badge rechtsonder inneemt (voor bijschriften). double _tlpVerticalReserve(double w) => - w * _kTlpFont + 2 * (w * _kTlpVPad) + w * 0.014; + w * _kTlpFont + 2 * (w * _kTlpVPad) + _tlpBottomInset(w); /// Officiële TLP 2.0-markering (FIRST): de gekleurde label op een zwart vlak, /// rechtsonder. Wijkt uit naar linksonder als het logo rechtsonder staat. @@ -2179,18 +2203,20 @@ class _TlpOverlay extends StatelessWidget { final TlpLevel tlp; final double w; final ThemeProfile profile; + final bool hasLogo; const _TlpOverlay({ required this.tlp, required this.w, required this.profile, + required this.hasLogo, }); @override Widget build(BuildContext context) { - final toLeft = profile.logoPosition == 'bottom-right'; + final toLeft = hasLogo && profile.logoPosition == 'bottom-right'; return Positioned( - bottom: w * 0.022, + bottom: _tlpBottomInset(w), left: toLeft ? w * _kTlpEdge : null, right: toLeft ? null : w * _kTlpEdge, child: Container( @@ -2306,7 +2332,7 @@ class _FooterOverlay extends StatelessWidget { final logoOnLeft = profile.logoPosition.endsWith('left'); final logoSpan = w * (profile.logoSize / 1280) * 1.28 + w * 0.012; final logoLeftEdge = w * (profile.logoSize / 1280) * 0.28; - final tlpOnRight = profile.logoPosition != 'bottom-right'; + final tlpOnRight = !(hasLogo && profile.logoPosition == 'bottom-right'); final tlpSpan = tlp == TlpLevel.none ? 0.0 : w * _kTlpEdge + _tlpBadgeWidth(w, tlp) + w * 0.012; @@ -2403,18 +2429,18 @@ Widget _mediaPlaceholder(IconData icon, String label) { ); } -Widget _imagePlaceholder() { +Widget _imagePlaceholder(BuildContext context) { return Container( color: const Color(0xFFE2E8F0), - child: const Center( + child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.image_outlined, color: Color(0xFF94A3B8), size: 24), - SizedBox(height: 4), + const Icon(Icons.image_outlined, color: Color(0xFF94A3B8), size: 24), + const SizedBox(height: 4), Text( - 'Afbeelding', - style: TextStyle(color: Color(0xFF94A3B8), fontSize: 10), + context.l10n.d('Afbeelding'), + style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 10), ), ], ), diff --git a/test/app_localizations_test.dart b/test/app_localizations_test.dart index c6de210..8fd4607 100644 --- a/test/app_localizations_test.dart +++ b/test/app_localizations_test.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:ocideck/l10n/app_localizations.dart'; @@ -32,4 +34,96 @@ void main() { ); expect(AppLocalizations.materialLocaleFor('pap'), const Locale('en')); }); + + test('all literal Dutch source strings have an English fallback', () { + AppLocalizations.setActiveLanguageCode('en'); + + const unchangedInEnglish = { + 'Accent / bullets', + 'Bullet', + 'Coverflow', + 'Logo', + 'Logo px', + 'PREVIEW', + 'Preview', + 'SLIDES', + 'Slide', + 'slide', + }; + final expression = RegExp(r'''\.d\(\s*('(?:\\.|[^'])*'|"(?:\\.|[^"])*")'''); + final files = Directory('lib') + .listSync(recursive: true) + .whereType() + .where((file) => file.path.endsWith('.dart')); + final sources = {}; + + for (final file in files) { + final content = file.readAsStringSync(); + for (final match in expression.allMatches(content)) { + sources.add(_unquoteDartString(match.group(1)!)); + } + } + + final english = const AppLocalizations(Locale('en')); + final missing = sources.where((source) { + final translated = english.d(source); + return translated == source && !unchangedInEnglish.contains(source); + }).toList()..sort(); + + expect(missing, isEmpty); + }); + + test('all literal Dutch source strings are translated in every language', () { + const unchangedInAllLanguages = { + 'Accent / bullets', + 'Bullet', + 'Coverflow', + 'Logo', + 'Logo px', + 'PREVIEW', + 'Preview', + 'SLIDES', + 'Slide', + 'slide', + }; + final expression = RegExp(r'''\.d\(\s*('(?:\\.|[^'])*'|"(?:\\.|[^"])*")'''); + final files = Directory('lib') + .listSync(recursive: true) + .whereType() + .where((file) => file.path.endsWith('.dart')); + final sources = {}; + + for (final file in files) { + final content = file.readAsStringSync(); + for (final match in expression.allMatches(content)) { + sources.add(_unquoteDartString(match.group(1)!)); + } + } + + final missingByLanguage = >{}; + for (final languageCode in AppLocalizations.languageNames.keys) { + if (languageCode == 'nl') continue; + final missing = sources.where((source) { + if (unchangedInAllLanguages.contains(source)) return false; + return !AppLocalizations.hasDirectDutchSourceTranslation( + languageCode, + source, + ); + }).toList()..sort(); + if (missing.isNotEmpty) missingByLanguage[languageCode] = missing; + } + + expect(missingByLanguage, isEmpty); + }); +} + +String _unquoteDartString(String value) { + final quote = value[0]; + final body = value.substring(1, value.length - 1); + return body + .replaceAll(r'\\', r'\') + .replaceAll('\\$quote', quote) + .replaceAll(r'\n', '\n') + .replaceAll(r'\r', '\r') + .replaceAll(r'\t', '\t'); } diff --git a/test/deck_provider_test.dart b/test/deck_provider_test.dart index 917b270..6c540ce 100644 --- a/test/deck_provider_test.dart +++ b/test/deck_provider_test.dart @@ -112,6 +112,13 @@ void main() { expect(n.state.deck!.paginate, isFalse); }); + test('updateInfo can update the presentation title', () { + final n = _notifier()..newDeck('D'); + n.updateInfo(title: 'Nieuwe presentatietitel', author: 'Auteur'); + expect(n.state.deck!.title, 'Nieuwe presentatietitel'); + expect(n.state.deck!.author, 'Auteur'); + }); + test('generateMarkdown and applyMarkdown round-trip the deck', () { final n = _notifier()..newDeck('D'); n.addSlide(SlideType.bulletsImage, afterIndex: 0); diff --git a/test/tlp_test.dart b/test/tlp_test.dart index bb4998d..ba029cd 100644 --- a/test/tlp_test.dart +++ b/test/tlp_test.dart @@ -63,5 +63,39 @@ void main() { await tester.pump(); expect(find.textContaining('TLP:'), findsNothing); }); + + testWidgets('right-side image caption aligns with the TLP badge', ( + tester, + ) async { + const caption = 'Foto: iemand'; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: SizedBox( + width: 800, + height: 450, + child: SlidePreviewWidget( + slide: Slide.create( + SlideType.bulletsImage, + ).copyWith(title: 'T', bullets: ['a'], imageCaption: caption), + tlp: TlpLevel.red, + ), + ), + ), + ), + ), + ); + await tester.pump(); + + final captionRight = tester.getTopRight(find.text(caption)).dx; + final tlpRight = tester.getTopRight(find.text('TLP:RED')).dx; + + expect( + (captionRight - tlpRight).abs(), + lessThan(4), + reason: 'Caption and TLP badge should share the same right edge.', + ); + }); }); }