diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..469b050 --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,4 @@ +arb-dir: lib/l10n +template-arb-file: app_fr.arb +output-localization-file: app_localizations.dart +output-class: AppLocalizations diff --git a/lib/Components/common_loader.dart b/lib/Components/common_loader.dart index bf349f5..88aea22 100644 --- a/lib/Components/common_loader.dart +++ b/lib/Components/common_loader.dart @@ -13,4 +13,4 @@ class CommonLoader extends StatelessWidget { child: LoaderWaveFloat(size: size), ); } -} +} diff --git a/lib/Components/multi_select_container.dart b/lib/Components/multi_select_container.dart index 82b4136..04530bf 100644 --- a/lib/Components/multi_select_container.dart +++ b/lib/Components/multi_select_container.dart @@ -40,7 +40,7 @@ class MultiSelectContainer extends StatelessWidget { if(label != null) Align( alignment: AlignmentDirectional.centerStart, - child: Text(label!, style: TextStyle(fontSize: 25, fontWeight: FontWeight.w300)) + child: Text(label!, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)) ), Padding( padding: const EdgeInsets.all(10.0), @@ -95,7 +95,10 @@ class _MultiSelectChipState extends State { choices.add(Container( padding: const EdgeInsets.all(2.0), child: ChoiceChip( - label: Text(widget.isHTMLLabel ? parse(item).documentElement!.text : item, style: TextStyle(color: kBlack)), + label: Text( + widget.isHTMLLabel ? parse(item).documentElement!.text : item, + style: TextStyle(color: widget.selectedValues.contains(item) ? kWhite : kPrimaryColor), + ), selected: widget.selectedValues.contains(item), selectedColor: kPrimaryColor, onSelected: (selected) { diff --git a/lib/Components/multi_string_input_html_modal.dart b/lib/Components/multi_string_input_html_modal.dart index 331e590..24234f0 100644 --- a/lib/Components/multi_string_input_html_modal.dart +++ b/lib/Components/multi_string_input_html_modal.dart @@ -25,8 +25,10 @@ showMultiStringInputHTML (String label, String modalLabel, bool isTitle, List createState() => _QuotaBarsWidgetState(); +} + +class _QuotaBarsWidgetState extends State { + InstanceQuotaDTO? _quota; + bool _loading = true; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) => _fetchQuota()); + } + + Future _fetchQuota() async { + final managerContext = Provider.of(context, listen: false).getContext() as ManagerAppContext; + final instanceId = managerContext.instanceId; + final client = managerContext.clientAPI; + + if (instanceId == null || client == null) { + setState(() => _loading = false); + return; + } + + try { + final quota = await client.instanceApi!.instanceGetQuota(instanceId); + if (mounted) setState(() { _quota = quota; _loading = false; }); + } catch (_) { + if (mounted) setState(() => _loading = false); + } + } + + Color _barColor(int used, int quota) { + if (quota == 0) return kPrimaryColor; + final ratio = used / quota; + if (ratio >= 0.95) return Colors.red; + if (ratio >= 0.80) return Colors.orange; + return kPrimaryColor; + } + + double? _barValue(int used, int quota) { + if (quota == 0) return null; + return (used / quota).clamp(0.0, 1.0); + } + + String _formatBytes(int bytes) { + if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(0)} KB'; + if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; + return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; + } + + @override + Widget build(BuildContext context) { + final managerContext = Provider.of(context).getContext() as ManagerAppContext; + final isAssistant = managerContext.instanceDTO?.isAssistant ?? false; + final l10n = AppLocalizations.of(context)!; + + if (_loading) { + return const SizedBox( + height: 8, + child: LinearProgressIndicator(), + ); + } + + if (_quota == null) return const SizedBox.shrink(); + + final storageUsed = _quota!.storageUsedBytes ?? 0; + final storageQuota = _quota!.storageQuotaBytes ?? 0; + final aiUsed = _quota!.aiRequestsUsed ?? 0; + final aiQuota = _quota!.aiRequestsPerMonth ?? 0; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _QuotaBar( + icon: Icons.storage, + label: l10n.storageLabel, + value: _barValue(storageUsed, storageQuota), + color: _barColor(storageUsed, storageQuota), + subtitle: storageQuota == 0 + ? '${_formatBytes(storageUsed)} · ${l10n.unlimited}' + : '${_formatBytes(storageUsed)} / ${_formatBytes(storageQuota)}', + ), + if (isAssistant) ...[ + const SizedBox(height: 8), + _QuotaBar( + icon: Icons.chat_bubble_outline, + label: l10n.aiThisMonthLabel, + value: _barValue(aiUsed, aiQuota), + color: _barColor(aiUsed, aiQuota), + subtitle: aiQuota == 0 + ? '$aiUsed ${l10n.requestsAbbr} · ${l10n.unlimited}' + : '$aiUsed / $aiQuota ${l10n.requestsAbbr}.', + ), + ], + ], + ), + ); + } +} + +class _QuotaBar extends StatelessWidget { + final IconData icon; + final String label; + final double? value; + final Color color; + final String subtitle; + + const _QuotaBar({ + required this.icon, + required this.label, + required this.value, + required this.color, + required this.subtitle, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, size: 14, color: kBodyTextColor), + const SizedBox(width: 4), + Text( + label, + style: const TextStyle( + fontSize: 11, + color: kBodyTextColor, + fontWeight: FontWeight.w500, + fontFamily: 'Helvetica', + ), + ), + ], + ), + const SizedBox(height: 4), + ClipRRect( + borderRadius: BorderRadius.circular(2), + child: LinearProgressIndicator( + value: value, + minHeight: 5, + backgroundColor: Colors.grey.shade200, + valueColor: AlwaysStoppedAnimation(color), + ), + ), + const SizedBox(height: 2), + Text( + subtitle, + style: TextStyle( + fontSize: 10, + color: kBodyTextColor.withValues(alpha: 0.6), + fontFamily: 'Helvetica', + ), + ), + ], + ); + } +} diff --git a/lib/Components/translation_input_and_resource_container.dart b/lib/Components/translation_input_and_resource_container.dart index 5e2bf17..dda159e 100644 --- a/lib/Components/translation_input_and_resource_container.dart +++ b/lib/Components/translation_input_and_resource_container.dart @@ -4,9 +4,15 @@ import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/Components/resource_input_container.dart'; +import 'package:manager_app/Components/rounded_button.dart'; +import 'package:manager_app/Models/managerContext.dart' show ManagerAppContext; +import 'package:manager_app/Services/ai_translate_service.dart'; +import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:provider/provider.dart'; import 'flag_decoration.dart'; +import 'message_notification.dart'; class TranslationInputAndResourceContainer extends StatefulWidget { TranslationInputAndResourceContainer({ @@ -35,6 +41,7 @@ class _TranslationInputAndResourceContainerState extends State { late Map _controllers; bool _isEnforcingLimit = false; + bool _isTranslating = false; @override void initState() { @@ -92,6 +99,65 @@ class _TranslationInputAndResourceContainerState ).convert(); } + Future _translateWithAI() async { + final appContext = context.read().getContext() as ManagerAppContext; + if (appContext.instanceDTO?.isAssistant != true) return; + + final source = widget.newValues.firstWhere( + (t) => t.value != null && t.value!.trim().isNotEmpty && t.value!.trim() != '


', + orElse: () => widget.newValues.first, + ); + + if (source.value == null || source.value!.trim().isEmpty) { + showNotification(kError, kWhite, 'Aucun texte source à traduire', context, null); + return; + } + + final targetLangs = widget.newValues + .where((t) => t.language != source.language) + .map((t) => t.language!) + .toList(); + + if (targetLangs.isEmpty) return; + + setState(() => _isTranslating = true); + + try { + final translations = await AiTranslateService.translate( + host: appContext.host!, + accessToken: appContext.accessToken!, + instanceId: appContext.instanceId!, + text: source.value!, + sourceLang: source.language!, + targetLangs: targetLangs, + ); + + setState(() { + for (final translation in widget.newValues) { + final lang = translation.language!; + if (lang == source.language) continue; + final translated = translations[lang]; + if (translated == null) continue; + // On ne touche qu'au texte, pas à la ressource liée + translation.value = translated; + _controllers[lang]?.dispose(); + final controller = QuillController( + document: Document.fromJson(_htmlToDeltaJson(translated)), + selection: const TextSelection.collapsed(offset: 0), + ); + _setupListener(lang, controller); + _controllers[lang] = controller; + } + }); + + showNotification(kSuccess, kWhite, 'Traduction appliquée', context, null); + } catch (e) { + showNotification(kError, kWhite, 'Erreur lors de la traduction IA', context, null); + } finally { + setState(() => _isTranslating = false); + } + } + @override void dispose() { for (final c in _controllers.values) { @@ -104,7 +170,29 @@ class _TranslationInputAndResourceContainerState Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, - children: widget.newValues.map((t) => _buildLanguageSection(t)).toList(), + children: [ + ...widget.newValues.map((t) => _buildLanguageSection(t)), + const SizedBox(height: 8), + Builder(builder: (ctx) { + final instance = ctx.watch().getContext()?.instanceDTO; + if (instance?.isAssistant != true) return const SizedBox.shrink(); + return Center( + child: SizedBox( + width: 370, + height: 70, + child: _isTranslating + ? const Center(child: CircularProgressIndicator()) + : RoundedButton( + text: "Traduire via IA", + icon: Icons.auto_awesome, + color: kPrimaryColor, + press: _translateWithAI, + fontSize: 20, + ), + ), + ); + }), + ], ); } diff --git a/lib/Components/translation_input_container.dart b/lib/Components/translation_input_container.dart index 8f687e1..1ed64a9 100644 --- a/lib/Components/translation_input_container.dart +++ b/lib/Components/translation_input_container.dart @@ -5,10 +5,14 @@ import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/rounded_button.dart'; +import 'package:manager_app/Models/managerContext.dart' show ManagerAppContext; +import 'package:manager_app/Services/ai_translate_service.dart'; import 'package:manager_app/constants.dart'; +import 'package:provider/provider.dart'; import 'flag_decoration.dart'; import 'message_notification.dart'; +import 'package:manager_app/app_context.dart'; class TranslationInputContainer extends StatefulWidget { TranslationInputContainer({ @@ -35,6 +39,7 @@ class TranslationInputContainer extends StatefulWidget { class _TranslationInputContainerState extends State { late Map _controllers; bool _isEnforcingLimit = false; + bool _isTranslating = false; @override void initState() { @@ -92,6 +97,64 @@ class _TranslationInputContainerState extends State { ).convert(); } + Future _translateWithAI() async { + final appContext = context.read().getContext() as ManagerAppContext; + if (appContext.instanceDTO?.isAssistant != true) return; + + final source = widget.newValues.firstWhere( + (t) => t.value != null && t.value!.trim().isNotEmpty && t.value!.trim() != '


', + orElse: () => widget.newValues.first, + ); + + if (source.value == null || source.value!.trim().isEmpty) { + showNotification(kError, kWhite, 'Aucun texte source à traduire', context, null); + return; + } + + final targetLangs = widget.newValues + .where((t) => t.language != source.language) + .map((t) => t.language!) + .toList(); + + if (targetLangs.isEmpty) return; + + setState(() => _isTranslating = true); + + try { + final translations = await AiTranslateService.translate( + host: appContext.host!, + accessToken: appContext.accessToken!, + instanceId: appContext.instanceId!, + text: source.value!, + sourceLang: source.language!, + targetLangs: targetLangs, + ); + + setState(() { + for (final translation in widget.newValues) { + final lang = translation.language!; + if (lang == source.language) continue; + final translated = translations[lang]; + if (translated == null) continue; + translation.value = translated; + _controllers[lang]?.dispose(); + final controller = QuillController( + document: Document.fromJson(_htmlToDeltaJson(translated)), + selection: const TextSelection.collapsed(offset: 0), + ); + _setupListener(lang, controller); + _controllers[lang] = controller; + } + }); + + showNotification(kSuccess, kWhite, 'Traduction appliquée', context, null); + } catch (e) { + showNotification(kError, kWhite, 'Erreur lors de la traduction IA', context, null); + } finally { + setState(() => _isTranslating = false); + } + } + void _applyToAllLanguages() { if (_controllers.isEmpty) return; final firstLang = widget.newValues.first.language!; @@ -130,7 +193,7 @@ class _TranslationInputContainerState extends State { children: [ ...widget.newValues.map((t) => _buildLanguageSection(t)), const SizedBox(height: 8), - if (widget.resourceTypes == null) + if (widget.resourceTypes == null) ...[ Center( child: SizedBox( width: 370, @@ -144,6 +207,26 @@ class _TranslationInputContainerState extends State { ), ), ), + Builder(builder: (ctx) { + final instance = ctx.watch().getContext()?.instanceDTO; + if (instance?.isAssistant != true) return const SizedBox.shrink(); + return Center( + child: SizedBox( + width: 370, + height: 70, + child: _isTranslating + ? const Center(child: CircularProgressIndicator()) + : RoundedButton( + text: "Traduire via IA", + icon: Icons.auto_awesome, + color: kPrimaryColor, + press: _translateWithAI, + fontSize: 20, + ), + ), + ); + }), + ] ], ); } diff --git a/lib/Models/managerContext.dart b/lib/Models/managerContext.dart index 55678eb..9d3289e 100644 --- a/lib/Models/managerContext.dart +++ b/lib/Models/managerContext.dart @@ -16,6 +16,7 @@ class ManagerAppContext with ChangeNotifier{ SectionDTO? selectedSection; bool? isLoading = false; UserRole? role; + Locale locale = const Locale('fr'); bool get canEdit => role != null && role!.value <= 2; diff --git a/lib/Screens/ApiKeys/api_keys_screen.dart b/lib/Screens/ApiKeys/api_keys_screen.dart index 607164a..d8f63c5 100644 --- a/lib/Screens/ApiKeys/api_keys_screen.dart +++ b/lib/Screens/ApiKeys/api_keys_screen.dart @@ -2,8 +2,10 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; +import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; @@ -61,6 +63,7 @@ class _ApiKeysScreenState extends State { } void _showCreateDialog(BuildContext context, ManagerAppContext ctx) { + final l = AppLocalizations.of(context)!; final nameCtrl = TextEditingController(); ApiKeyAppType selectedType = ApiKeyAppType.number0; DateTime? selectedExpiration; @@ -69,14 +72,14 @@ class _ApiKeysScreenState extends State { context: context, builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) { return AlertDialog( - title: const Text('Créer une clé API'), + title: Text(l.createApiKey), content: Column(mainAxisSize: MainAxisSize.min, children: [ - TextField(controller: nameCtrl, decoration: const InputDecoration(labelText: 'Nom')), + TextField(controller: nameCtrl, decoration: InputDecoration(labelText: l.name)), const SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Type d\'application', style: TextStyle(fontSize: 12, color: Colors.grey)), + Text(l.appType, style: const TextStyle(fontSize: 12, color: Colors.grey)), DropdownButton( value: selectedType, isExpanded: true, @@ -93,8 +96,8 @@ class _ApiKeysScreenState extends State { Expanded( child: Text( selectedExpiration == null - ? 'Pas d\'expiration' - : 'Expire le ${selectedExpiration!.day.toString().padLeft(2, '0')}/${selectedExpiration!.month.toString().padLeft(2, '0')}/${selectedExpiration!.year}', + ? l.noExpiration + : l.expiresOn('${selectedExpiration!.day.toString().padLeft(2, '0')}/${selectedExpiration!.month.toString().padLeft(2, '0')}/${selectedExpiration!.year}'), style: const TextStyle(fontSize: 14), ), ), @@ -108,19 +111,19 @@ class _ApiKeysScreenState extends State { ); if (picked != null) setLocal(() => selectedExpiration = picked); }, - child: const Text('Choisir'), + child: Text(l.choose), ), if (selectedExpiration != null) IconButton( icon: const Icon(Icons.close, size: 16), onPressed: () => setLocal(() => selectedExpiration = null), - tooltip: 'Supprimer l\'expiration', + tooltip: l.removeExpiration, ), ], ), ]), actions: [ - TextButton(onPressed: () => Navigator.of(ctx2, rootNavigator: true).pop(), child: const Text('Annuler')), + TextButton(onPressed: () => Navigator.of(ctx2, rootNavigator: true).pop(), child: Text(l.cancel)), ElevatedButton( onPressed: () async { Navigator.of(ctx2, rootNavigator: true).pop(); @@ -129,7 +132,7 @@ class _ApiKeysScreenState extends State { _showPlainKeyDialog(context, plainKey); } }, - child: const Text('Créer'), + child: Text(l.create), ), ], ); @@ -138,15 +141,16 @@ class _ApiKeysScreenState extends State { } void _showPlainKeyDialog(BuildContext context, String plainKey) { + final l = AppLocalizations.of(context)!; showDialog( context: context, barrierDismissible: false, builder: (_) => AlertDialog( - title: const Text('Clé API créée'), + title: Text(l.apiKeyCreatedTitle), content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Copiez cette clé maintenant — elle ne sera plus affichée.', - style: TextStyle(color: Colors.orange, fontWeight: FontWeight.w600), + Text( + l.copyKeyWarning, + style: const TextStyle(color: Colors.orange, fontWeight: FontWeight.w600), ), const SizedBox(height: 12), Container( @@ -160,7 +164,7 @@ class _ApiKeysScreenState extends State { Expanded(child: SelectableText(plainKey, style: const TextStyle(fontFamily: 'monospace', fontSize: 13))), IconButton( icon: const Icon(Icons.copy, size: 18), - tooltip: 'Copier', + tooltip: l.copy, onPressed: () => Clipboard.setData(ClipboardData(text: plainKey)), ), ]), @@ -169,7 +173,7 @@ class _ApiKeysScreenState extends State { actions: [ ElevatedButton( onPressed: () => Navigator.of(context, rootNavigator: true).pop(), - child: const Text('J\'ai copié la clé'), + child: Text(l.copiedKey), ), ], ), @@ -177,20 +181,21 @@ class _ApiKeysScreenState extends State { } void _confirmRevoke(BuildContext context, ManagerAppContext ctx, Map key) { + final l = AppLocalizations.of(context)!; showDialog( context: context, builder: (dialogContext) => AlertDialog( - title: const Text('Révoquer la clé API'), - content: Text('Révoquer « ${key['name']} » ? Les apps utilisant cette clé perdront l\'accès.'), + title: Text(l.revokeApiKeyTitle), + content: Text(l.revokeApiKeyConfirm(key['name'] as String? ?? '')), actions: [ - TextButton(onPressed: () => Navigator.pop(dialogContext), child: const Text('Annuler')), + TextButton(onPressed: () => Navigator.pop(dialogContext), child: Text(l.cancel)), ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () async { Navigator.pop(dialogContext); await _revokeKey(ctx, key['id'] as String); }, - child: const Text('Révoquer', style: TextStyle(color: Colors.white)), + child: Text(l.revoke, style: const TextStyle(color: Colors.white)), ), ], ), @@ -208,6 +213,7 @@ class _ApiKeysScreenState extends State { @override Widget build(BuildContext context) { + final l = AppLocalizations.of(context)!; final appContext = Provider.of(context); final managerCtx = appContext.getContext() as ManagerAppContext; @@ -217,19 +223,19 @@ class _ApiKeysScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Clés API', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)), + Text(l.apiKeysTitle, style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)), ElevatedButton.icon( onPressed: () => _showCreateDialog(context, managerCtx), icon: const Icon(Icons.add), - label: const Text('Créer une clé'), + label: Text(l.createKeyBtn), ), ], ), const SizedBox(height: 16), if (_loading) - const Center(child: CircularProgressIndicator()) + const CommonLoader() else if (_keys.isEmpty) - const Center(child: Text('Aucune clé API')) + Center(child: Text(l.noApiKeys)) else Expanded( child: Card( @@ -248,13 +254,13 @@ class _ApiKeysScreenState extends State { columnSpacing: 24, headingRowColor: WidgetStateProperty.all(Colors.grey.shade50), dividerThickness: 1, - columns: const [ - DataColumn(label: Text('Nom', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Type', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Créée le', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Expiration', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Statut', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Actions', style: TextStyle(fontWeight: FontWeight.w600))), + columns: [ + DataColumn(label: Text(l.name, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.type, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.createdOn, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.expiration, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.status, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.actions, style: const TextStyle(fontWeight: FontWeight.w600))), ], rows: _keys.map((key) { final isActive = key['isActive'] as bool? ?? false; @@ -276,7 +282,7 @@ class _ApiKeysScreenState extends State { DataCell(Text(expStr, style: TextStyle(color: exp != null && exp.isBefore(DateTime.now()) ? Colors.red : Colors.grey))), DataCell(Chip( label: Text( - isActive ? 'Active' : 'Révoquée', + isActive ? l.activeKey : l.revokedKey, style: TextStyle( color: isActive ? Colors.green.shade700 : Colors.red.shade700, fontSize: 12, @@ -290,7 +296,7 @@ class _ApiKeysScreenState extends State { DataCell(isActive ? IconButton( icon: const Icon(Icons.block, color: Colors.red, size: 20), - tooltip: 'Révoquer', + tooltip: l.tooltipRevoke, onPressed: () => _confirmRevoke(context, managerCtx, key), ) : const SizedBox()), diff --git a/lib/Screens/Applications/add_configuration_link_popup.dart b/lib/Screens/Applications/add_configuration_link_popup.dart index 4dda236..65c2f9f 100644 --- a/lib/Screens/Applications/add_configuration_link_popup.dart +++ b/lib/Screens/Applications/add_configuration_link_popup.dart @@ -5,6 +5,7 @@ import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Screens/Resources/resources_screen.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import '../Kiosk_devices/change_device_info_modal.dart'; @@ -19,7 +20,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0)) ), - title: Center(child: Text("Sélectionner une configuration à ajouter")), + title: Center(child: Text(AppLocalizations.of(context)!.selectConfigToAdd)), content: FutureBuilder( future: getConfigurations(appContext), builder: (context, AsyncSnapshot snapshot) { @@ -35,7 +36,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex height: size.height * 0.5, width: size.width * 0.5, constraints: const BoxConstraints(minHeight: 250, minWidth: 250), - child: configurations.length == 0 ? Center(child: Text("Aucun élément à afficher")) : ListView.builder( + child: configurations.length == 0 ? Center(child: Text(AppLocalizations.of(context)!.noItems)) : ListView.builder( padding: const EdgeInsets.all(20), itemCount: configurations.length, itemBuilder: (context, index) { @@ -115,7 +116,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex width: 180, height: 70, child: RoundedButton( - text: "Annuler", + text: AppLocalizations.of(context)!.cancel, icon: Icons.undo, color: kSecond, press: () { @@ -128,7 +129,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex width: 180, height: 70, child: RoundedButton( - text: "Ajouter", + text: AppLocalizations.of(context)!.add, icon: Icons.add, color: kPrimaryColor, press: () { diff --git a/lib/Screens/Applications/app_configuration_link_screen.dart b/lib/Screens/Applications/app_configuration_link_screen.dart index 70ec2ed..a14ec32 100644 --- a/lib/Screens/Applications/app_configuration_link_screen.dart +++ b/lib/Screens/Applications/app_configuration_link_screen.dart @@ -17,6 +17,7 @@ import 'package:manager_app/Screens/Applications/add_configuration_link_popup.da import 'package:manager_app/Screens/Applications/phone_mockup.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; @@ -57,7 +58,7 @@ class _AppConfigurationLinkScreenState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Informations générales", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)), + Text(AppLocalizations.of(context)!.generalInfo, style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)), SizedBox(height: 8), Expanded( child: Center( @@ -75,7 +76,7 @@ class _AppConfigurationLinkScreenState extends State height: elementHeight, child: Center( child: ResourceInputContainer( - label: "Image principale :", + label: AppLocalizations.of(context)!.mainImageLabel, initialValue: _applicationInstanceDTO.mainImageId, color: kPrimaryColor, imageFit: BoxFit.fitHeight, @@ -91,7 +92,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); /*setState(() { });*/ @@ -106,7 +107,7 @@ class _AppConfigurationLinkScreenState extends State height: elementHeight, child: Center( child: ResourceInputContainer( - label: "Loader :", + label: AppLocalizations.of(context)!.loaderLabel, initialValue: _applicationInstanceDTO.loaderImageId, color: kPrimaryColor, imageFit: BoxFit.fitHeight, @@ -122,7 +123,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); /*setState(() { });*/ @@ -137,7 +138,7 @@ class _AppConfigurationLinkScreenState extends State height: elementHeight, child: Center( child: ColorPickerInputContainer( - label: "Couleur principale :", + label: AppLocalizations.of(context)!.primaryColorLabel, fontSize: 20, color: _applicationInstanceDTO.primaryColor, onChanged: (value) async { @@ -145,7 +146,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); /*setState(() { });*/ @@ -160,7 +161,7 @@ class _AppConfigurationLinkScreenState extends State height: elementHeight, child: Center( child: ColorPickerInputContainer( - label: "Couleur secondaire :", + label: AppLocalizations.of(context)!.secondaryColorLabel, fontSize: 20, color: _applicationInstanceDTO.secondaryColor, onChanged: (value) async { @@ -168,7 +169,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); /*setState(() { });*/ @@ -183,17 +184,17 @@ class _AppConfigurationLinkScreenState extends State height: elementHeight, child: Center( child: SegmentedEnumInputContainer( - label: "Affichage :", + label: AppLocalizations.of(context)!.layoutLabel, selected: _applicationInstanceDTO.layoutMainPage, values: LayoutMainPageType.values, - inputValues: { LayoutMainPageType.SimpleGrid: {'label': 'Grille', 'icon': Icons.grid_view}, LayoutMainPageType.MasonryGrid : {'label': 'Masonry', 'icon': Icons.view_quilt }}, + inputValues: { LayoutMainPageType.SimpleGrid: {'label': AppLocalizations.of(context)!.layoutGrid, 'icon': Icons.grid_view}, LayoutMainPageType.MasonryGrid : {'label': 'Masonry', 'icon': Icons.view_quilt }}, onChanged: (value) async { var tempOutput = value; _applicationInstanceDTO.layoutMainPage = tempOutput; // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); /*setState(() { });*/ @@ -209,7 +210,7 @@ class _AppConfigurationLinkScreenState extends State height: elementHeight, child: Center( child: MultiSelectDropdownLanguageContainer( - label: "Langues :", + label: AppLocalizations.of(context)!.languagesLabel, initialValue: _applicationInstanceDTO.languages != null ? _applicationInstanceDTO.languages!: [], values: languages, isMultiple: true, @@ -221,7 +222,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); /*setState(() { });*/ @@ -245,14 +246,14 @@ class _AppConfigurationLinkScreenState extends State List? sectionEvents = rawSubsections?.whereType().toList(); sectionEvents = sectionEvents == null ? [] : sectionEvents; - sectionEvents.add(SectionEventDTO(id: null, label: "Aucun")); + sectionEvents.add(SectionEventDTO(id: null, label: AppLocalizations.of(context)!.noneOption)); print(_applicationInstanceDTO.sectionEventId); print(_applicationInstanceDTO.sectionEventDTO); return SingleChoiceInputContainer( - label: "Evènement à l'affiche :", - selectLabel: "Choisir un évènement", + label: AppLocalizations.of(context)!.featuredEventLabel, + selectLabel: AppLocalizations.of(context)!.chooseEvent, selected: _applicationInstanceDTO.sectionEventDTO, values: sectionEvents.toList(), valueExtractor: (SectionEventDTO? dto) => dto?.id ?? "", @@ -273,7 +274,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); //setState(() { _applicationInstanceDTO.sectionEventDTO = applicationLink.sectionEventDTO; //_applicationInstanceDTO = applicationLink; @@ -296,7 +297,7 @@ class _AppConfigurationLinkScreenState extends State return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("Assistant IA :", style: TextStyle(fontSize: 16)), + Text(AppLocalizations.of(context)!.aiAssistantLabel, style: TextStyle(fontSize: 16)), Switch( activeThumbColor: kPrimaryColor, inactiveThumbColor: kBodyTextColor, @@ -311,10 +312,10 @@ class _AppConfigurationLinkScreenState extends State localSetState(() { _applicationInstanceDTO.isAssistant = applicationLink.isAssistant; }); - showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); } } catch (e) { - showNotification(kError, kWhite, "Une erreur est survenue", context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null); } }, ), @@ -347,7 +348,7 @@ class _AppConfigurationLinkScreenState extends State children: [ Padding( padding: const EdgeInsets.all(16), - child: Text("Configurations sur le téléphone", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)), + child: Text(AppLocalizations.of(context)!.phoneConfigTitle, style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)), ), appConfigurationLinks != null ? Padding( padding: const EdgeInsets.only(left: 32, right: 32, top: 75), @@ -375,7 +376,7 @@ class _AppConfigurationLinkScreenState extends State // TODO use order put method var result = await updateAppConfigurationOrder(appContext, updatedList); localSetState(() {}); - showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); }, actions: [ (BuildContext context, int index, AppConfigurationLinkDTO link) { @@ -394,16 +395,16 @@ class _AppConfigurationLinkScreenState extends State var applicationLink = await updateApplicationLink(appContext, link); if(applicationLink != null) { if(newValue) { - showNotification(kSuccess, kWhite, "Configuration activée avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configActivatedSuccess, context, null); } else { - showNotification(kSuccess, kWhite, "Configuration désactivée avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeactivatedSuccess, context, null); } localSetState(() { link.isActive = applicationLink.isActive; }); } } catch (e) { - showNotification(kError, kWhite, "Une erreur est survenue", context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null); } }, ), @@ -416,19 +417,19 @@ class _AppConfigurationLinkScreenState extends State child: InkWell( onTap: () async { showConfirmationDialog( - "Êtes-vous sûr de vouloir retirer cette configuration de l'application ?", + AppLocalizations.of(context)!.configRemoveConfirm, () {}, () async { try { var result = await deleteConfigurationToApp(appContext, link, _applicationInstanceDTO); - showNotification(kSuccess, kWhite, "La configuration a été retirée de l'application avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null); setState(() { // for refresh ui }); } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors du retrait de la configuration', context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null); } }, context @@ -498,19 +499,19 @@ class _AppConfigurationLinkScreenState extends State child: InkWell( onTap: () async { showConfirmationDialog( - "Êtes-vous sûr de vouloir retirer cette configuration de l'application ?", + AppLocalizations.of(context)!.configRemoveConfirm, () {}, () async { try { var result = await deleteConfigurationToApp(appContext, appConfigurationLink, _applicationInstanceDTO); - showNotification(kSuccess, kWhite, "La configuration a été retirée de l'application avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null); setState(() { // for refresh ui }); } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors du retrait de la configuration', context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null); } }, context @@ -526,7 +527,7 @@ class _AppConfigurationLinkScreenState extends State ), ), ),*/ - ): Center(child: Text("No data")), + ): Center(child: Text(AppLocalizations.of(context)!.noData)), appConfigurationLinks != null ? Positioned( top: 8, right: 8, @@ -630,7 +631,7 @@ class _AppConfigurationLinkScreenState extends State alignment: AlignmentDirectional.centerStart, child: Padding( padding: const EdgeInsets.all(8.0), - child: Text("Informations générales"), + child: Text(AppLocalizations.of(context)!.generalInfo), ) ), Align( @@ -656,7 +657,7 @@ class _AppConfigurationLinkScreenState extends State ),*/ // Image principale ResourceInputContainer( - label: "Image principale :", + label: AppLocalizations.of(context)!.mainImageLabel, initialValue: _applicationInstanceDTO.mainImageId, color: kPrimaryColor, onChanged: (ResourceDTO resource) { @@ -671,7 +672,7 @@ class _AppConfigurationLinkScreenState extends State ), // Image Loader ResourceInputContainer( - label: "Loader :", + label: AppLocalizations.of(context)!.loaderLabel, initialValue: _applicationInstanceDTO.loaderImageId, color: kPrimaryColor, onChanged: (ResourceDTO resource) { @@ -686,7 +687,7 @@ class _AppConfigurationLinkScreenState extends State ), // Primary color ColorPickerInputContainer( - label: "Couleur principale :", + label: AppLocalizations.of(context)!.primaryColorLabel, fontSize: 20, color: _applicationInstanceDTO.primaryColor, onChanged: (value) { @@ -695,7 +696,7 @@ class _AppConfigurationLinkScreenState extends State ), // Secondary color ColorPickerInputContainer( - label: "Couleur secondaire :", + label: AppLocalizations.of(context)!.secondaryColorLabel, fontSize: 20, color: _applicationInstanceDTO.secondaryColor, onChanged: (value) { @@ -706,7 +707,7 @@ class _AppConfigurationLinkScreenState extends State Text('Todo Type selector Grid or Mansonry'), // Langues MultiSelectDropdownLanguageContainer( - label: "Langues :", + label: AppLocalizations.of(context)!.languagesLabel, initialValue: _applicationInstanceDTO.languages != null ? _applicationInstanceDTO.languages!: [], values: languages, isMultiple: true, @@ -728,7 +729,7 @@ class _AppConfigurationLinkScreenState extends State alignment: AlignmentDirectional.centerStart, child: Padding( padding: const EdgeInsets.all(8.0), - child: Text("Configurations sur le téléphone"), + child: Text(AppLocalizations.of(context)!.phoneConfigTitle), ) ), Align( @@ -774,16 +775,16 @@ class _AppConfigurationLinkScreenState extends State var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { if(newValue) { - showNotification(kSuccess, kWhite, "Configuration activée avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configActivatedSuccess, context, null); } else { - showNotification(kSuccess, kWhite, "Configuration désactivée avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeactivatedSuccess, context, null); } setState(() { link.isActive = applicationLink.isActive; }); } } catch (e) { - showNotification(kError, kWhite, "Une erreur est survenue", context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null); } }, child: Icon(Icons.star, color: kError, size: 25), @@ -806,16 +807,16 @@ class _AppConfigurationLinkScreenState extends State var applicationLink = await updateApplicationLink(appContext, link); if(applicationLink != null) { if(newValue) { - showNotification(kSuccess, kWhite, "Configuration activée avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configActivatedSuccess, context, null); } else { - showNotification(kSuccess, kWhite, "Configuration désactivée avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeactivatedSuccess, context, null); } setState(() { link.isActive = applicationLink.isActive; }); } } catch (e) { - showNotification(kError, kWhite, "Une erreur est survenue", context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null); } }, ), @@ -828,19 +829,19 @@ class _AppConfigurationLinkScreenState extends State child: InkWell( onTap: () async { showConfirmationDialog( - "Êtes-vous sûr de vouloir retirer cette configuration de l'application ?", + AppLocalizations.of(context)!.configRemoveConfirm, () {}, () async { try { var result = await deleteConfigurationToApp(appContext, link, _applicationInstanceDTO); - showNotification(kSuccess, kWhite, "La configuration a été retirée de l'application avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null); setState(() { // for refresh ui }); } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors du retrait de la configuration', context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null); } }, context @@ -949,19 +950,19 @@ class _AppConfigurationLinkScreenState extends State child: InkWell( onTap: () async { showConfirmationDialog( - "Êtes-vous sûr de vouloir retirer cette configuration de l'application ?", + AppLocalizations.of(context)!.configRemoveConfirm, () {}, () async { try { var result = await deleteConfigurationToApp(appContext, appConfigurationLink, _applicationInstanceDTO); - showNotification(kSuccess, kWhite, "La configuration a été retirée de l'application avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null); setState(() { // for refresh ui }); } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors du retrait de la configuration', context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null); } }, context @@ -979,14 +980,14 @@ class _AppConfigurationLinkScreenState extends State ),*/ ], ), - ): Center(child: Text("No data")), + ): Center(child: Text(AppLocalizations.of(context)!.noData)), ), ], ), ), ); } else if (snapshot.connectionState == ConnectionState.none) { - return Text("No data"); + return Text(AppLocalizations.of(context)!.noData); } else { return Center( child: Container( diff --git a/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart b/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart index b11f147..b186f5b 100644 --- a/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart @@ -7,6 +7,7 @@ import 'package:manager_app/Components/single_select_container.dart'; import 'package:manager_app/Components/message_notification.dart'; import 'package:provider/provider.dart'; import 'package:manager_app/app_context.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'showNewOrUpdateEventAgenda.dart'; @@ -94,12 +95,12 @@ class _AgendaConfigState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text("Évènements", + Text(AppLocalizations.of(context)!.agendaEventsLabel, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ElevatedButton.icon( icon: const Icon(Icons.add), - label: const Text("Ajouter un évènement"), + label: Text(AppLocalizations.of(context)!.addEvent), onPressed: agendaDTO.id == null ? null : () { @@ -115,13 +116,13 @@ class _AgendaConfigState extends State { if (created != null && mounted) { setState(() => events.add(created)); showNotification(kSuccess, kWhite, - 'Évènement créé avec succès', context, null); + AppLocalizations.of(context)!.agendaEventCreatedSuccess, context, null); } } catch (e) { showNotification( kError, kWhite, - 'Erreur lors de la création de l\'évènement', + AppLocalizations.of(context)!.agendaEventCreateError, context, null); rethrow; @@ -145,8 +146,8 @@ class _AgendaConfigState extends State { height: 600, padding: const EdgeInsets.symmetric(vertical: 8), child: events.isEmpty - ? const Center( - child: Text("Aucun évènement", + ? Center( + child: Text(AppLocalizations.of(context)!.noEvents, style: TextStyle(fontStyle: FontStyle.italic))) : ListView.builder( itemCount: events.length, @@ -170,14 +171,14 @@ class _AgendaConfigState extends State { (event.label != null && event.label!.isNotEmpty) ? (event.label!.firstWhere( (t) => t.language == 'FR', - orElse: () => event.label![0])).value ?? "Évènement $index" - : "Évènement $index", + orElse: () => event.label![0])).value ?? "${AppLocalizations.of(context)!.agendaEventFallback} $index" + : "${AppLocalizations.of(context)!.agendaEventFallback} $index", textStyle: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Padding( padding: const EdgeInsets.only(top: 4), child: - Text(event.address?.address ?? "Pas d'adresse"), + Text(event.address?.address ?? AppLocalizations.of(context)!.noAddress), ), trailing: Row( mainAxisSize: MainAxisSize.min, @@ -201,7 +202,7 @@ class _AgendaConfigState extends State { showNotification( kSuccess, kWhite, - 'Évènement mis à jour avec succès', + AppLocalizations.of(context)!.agendaEventUpdatedSuccess, context, null); } @@ -209,7 +210,7 @@ class _AgendaConfigState extends State { showNotification( kError, kWhite, - 'Erreur lors de la mise à jour de l\'évènement', + AppLocalizations.of(context)!.agendaEventUpdateError, context, null); rethrow; @@ -232,7 +233,7 @@ class _AgendaConfigState extends State { showNotification( kSuccess, kWhite, - 'Évènement supprimé avec succès', + AppLocalizations.of(context)!.agendaEventDeletedSuccess, context, null); } @@ -240,7 +241,7 @@ class _AgendaConfigState extends State { showNotification( kError, kWhite, - 'Erreur lors de la suppression de l\'évènement', + AppLocalizations.of(context)!.agendaEventDeleteError, context, null); } @@ -279,7 +280,7 @@ class _AgendaConfigState extends State { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ CheckInputContainer( - label: "En ligne :", + label: AppLocalizations.of(context)!.onlineLabel, isChecked: agendaDTO.isOnlineAgenda ?? true, onChanged: (value) { setState(() { @@ -289,7 +290,7 @@ class _AgendaConfigState extends State { }, ), CheckInputContainer( - label: "Vue carte :", + label: AppLocalizations.of(context)!.mapViewLabel, isChecked: agendaDTO.agendaMapProvider != null, onChanged: (value) { setState(() { @@ -304,7 +305,7 @@ class _AgendaConfigState extends State { ), if (agendaDTO.agendaMapProvider != null) SingleSelectContainer( - label: "Service carte :", + label: AppLocalizations.of(context)!.mapServiceLabel, color: Colors.black, initialValue: mapProviderIn, inputValues: const ["Google", "MapBox"], @@ -324,9 +325,9 @@ class _AgendaConfigState extends State { ), if (agendaDTO.isOnlineAgenda == true) MultiStringInputContainer( - label: "Fichiers json :", + label: AppLocalizations.of(context)!.jsonFilesLabel, resourceTypes: const [ResourceType.Json, ResourceType.JsonUrl], - modalLabel: "JSON", + modalLabel: AppLocalizations.of(context)!.jsonLabel, color: kPrimaryColor, initialValue: agendaDTO.resourceIds ?? [], isTitle: false, diff --git a/lib/Screens/Configurations/Section/SubSection/Agenda/showNewOrUpdateEventAgenda.dart b/lib/Screens/Configurations/Section/SubSection/Agenda/showNewOrUpdateEventAgenda.dart index 846e08b..236334a 100644 --- a/lib/Screens/Configurations/Section/SubSection/Agenda/showNewOrUpdateEventAgenda.dart +++ b/lib/Screens/Configurations/Section/SubSection/Agenda/showNewOrUpdateEventAgenda.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/string_input_container.dart'; @@ -51,7 +52,7 @@ void showNewOrUpdateEventAgenda( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - event == null ? "Nouvel Évènement" : "Modifier l'Évènement", + event == null ? AppLocalizations.of(context)!.newAgendaEventTitle : AppLocalizations.of(context)!.editAgendaEventTitle, style: TextStyle( color: kPrimaryColor, fontSize: 20, @@ -70,7 +71,7 @@ void showNewOrUpdateEventAgenda( width: halfWidth, child: MultiStringInputContainer( label: "Titre :", - modalLabel: "Titre de l'évènement", + modalLabel: AppLocalizations.of(context)!.eventTitleLabel, initialValue: workingEvent.label ?? [], onGetResult: (val) => setState(() => workingEvent.label = val), @@ -84,7 +85,7 @@ void showNewOrUpdateEventAgenda( width: halfWidth, child: MultiStringInputContainer( label: "Description :", - modalLabel: "Description de l'évènement", + modalLabel: AppLocalizations.of(context)!.eventDescriptionLabel, initialValue: workingEvent.description ?? [], onGetResult: (val) => setState( () => workingEvent.description = val), @@ -104,7 +105,7 @@ void showNewOrUpdateEventAgenda( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Date de début :"), + Text(AppLocalizations.of(context)!.startDateColonLabel), SizedBox(height: 4), OutlinedButton.icon( icon: Icon(Icons.calendar_today, size: 16), @@ -172,7 +173,7 @@ void showNewOrUpdateEventAgenda( SizedBox( width: halfWidth, child: ResourceInputContainer( - label: "Vidéo :", + label: AppLocalizations.of(context)!.videoResourceLabel, initialValue: workingEvent.videoResourceId, inResourceTypes: const [ResourceType.Video, ResourceType.VideoUrl], onChanged: (res) => setState(() { @@ -199,7 +200,7 @@ void showNewOrUpdateEventAgenda( SizedBox( width: halfWidth, child: StringInputContainer( - label: "Lien vidéo direct :", + label: AppLocalizations.of(context)!.directVideoLinkLabel, initialValue: workingEvent.videoLink ?? "", onChanged: (val) => setState(() => workingEvent.videoLink = val.isEmpty ? null : val), ), @@ -223,7 +224,7 @@ void showNewOrUpdateEventAgenda( SizedBox( width: thirdWidth, child: StringInputContainer( - label: "Téléphone :", + label: AppLocalizations.of(context)!.phoneLabel, initialValue: workingEvent.phone ?? "", onChanged: (val) => setState(() => workingEvent.phone = val), @@ -242,7 +243,7 @@ void showNewOrUpdateEventAgenda( ], ), Divider(height: 24), - Text("Localisation / Géométrie", + Text(AppLocalizations.of(context)!.locationGeometryLabel, style: TextStyle(fontWeight: FontWeight.bold)), SizedBox(height: 8), StringInputContainer( diff --git a/lib/Screens/Configurations/Section/SubSection/Article/article_config.dart b/lib/Screens/Configurations/Section/SubSection/Article/article_config.dart index ba99aa8..4fffeea 100644 --- a/lib/Screens/Configurations/Section/SubSection/Article/article_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Article/article_config.dart @@ -8,6 +8,7 @@ import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; import 'dart:convert'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:provider/provider.dart'; @@ -79,7 +80,7 @@ class _ArticleConfigState extends State { Column( children: [ MultiStringInputContainer( - label: "Contenu affiché :", + label: AppLocalizations.of(context)!.displayedContentLabel, modalLabel: "Contenu", color: kPrimaryColor, isHTML: true, diff --git a/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart b/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart index 3856129..2adc772 100644 --- a/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart @@ -6,6 +6,7 @@ import 'package:intl/intl.dart'; import 'showNewOrUpdateProgrammeBlock.dart'; import 'package:provider/provider.dart'; import 'package:manager_app/app_context.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart'; @@ -98,11 +99,11 @@ class _EventConfigState extends State { children: [ Expanded( child: ListTile( - title: Text("Date de début"), + title: Text(AppLocalizations.of(context)!.startDateLabel), subtitle: Text(eventDTO.startDate != null ? DateFormat('dd/MM/yyyy HH:mm') .format(eventDTO.startDate!.toLocal()) - : "Non définie"), + : AppLocalizations.of(context)!.notDefined), trailing: Icon(Icons.calendar_today), onTap: () async { DateTime initialDate = eventDTO.startDate?.toLocal() ?? DateTime.now(); @@ -158,10 +159,10 @@ class _EventConfigState extends State { ), Expanded( child: ListTile( - title: Text("Date de fin"), + title: Text(AppLocalizations.of(context)!.endDateLabel), subtitle: Text(eventDTO.endDate != null ? DateFormat('dd/MM/yyyy HH:mm').format(eventDTO.endDate!.toLocal()) - : "Non définie"), + : AppLocalizations.of(context)!.notDefined), trailing: Icon(Icons.calendar_today), onTap: () async { DateTime initialDate = eventDTO.endDate?.toLocal() ?? @@ -231,11 +232,11 @@ class _EventConfigState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("Programme", + Text(AppLocalizations.of(context)!.programmeLabel, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ElevatedButton.icon( icon: Icon(Icons.add), - label: Text("Ajouter un bloc"), + label: Text(AppLocalizations.of(context)!.addBlock), onPressed: () { final appContext = Provider.of(context, listen: false); @@ -277,7 +278,7 @@ class _EventConfigState extends State { showNotification( kSuccess, kWhite, - 'Bloc de programme créé avec succès', + AppLocalizations.of(context)!.programmeBlockCreatedSuccess, context, null); } @@ -285,7 +286,7 @@ class _EventConfigState extends State { showNotification( kError, kWhite, - 'Erreur lors de la création du bloc', + AppLocalizations.of(context)!.programmeBlockCreateError, context, null); } @@ -302,7 +303,7 @@ class _EventConfigState extends State { height: 600, child: (eventDTO.programme == null || eventDTO.programme!.isEmpty) ? Center( - child: Text("Aucun bloc de programme défini", + child: Text(AppLocalizations.of(context)!.noBlocks, style: TextStyle(fontStyle: FontStyle.italic))) : ListView.builder( itemCount: eventDTO.programme!.length, @@ -318,9 +319,9 @@ class _EventConfigState extends State { (t) => t.language == 'FR', orElse: () => block.title![0]) .value ?? - "Bloc ${index + 1}", + "${AppLocalizations.of(context)!.blockFallback} ${index + 1}", ) - : Text("Bloc ${index + 1}"), + : Text("${AppLocalizations.of(context)!.blockFallback} ${index + 1}"), subtitle: Text( "${block.startTime != null ? DateFormat('HH:mm').format(block.startTime!.toLocal()) : '??'} - ${block.endTime != null ? DateFormat('HH:mm').format(block.endTime!.toLocal()) : '??'}"), trailing: Row( @@ -368,7 +369,7 @@ class _EventConfigState extends State { showNotification( kSuccess, kWhite, - 'Bloc mis à jour avec succès', + AppLocalizations.of(context)!.programmeBlockUpdatedSuccess, context, null); } @@ -376,7 +377,7 @@ class _EventConfigState extends State { showNotification( kError, kWhite, - 'Erreur lors de la mise à jour du bloc', + AppLocalizations.of(context)!.programmeBlockUpdateError, context, null); } @@ -407,14 +408,14 @@ class _EventConfigState extends State { showNotification( kSuccess, kWhite, - 'Bloc supprimé avec succès', + AppLocalizations.of(context)!.programmeBlockDeletedSuccess, context, null); } catch (e) { showNotification( kError, kWhite, - 'Erreur lors de la suppression du bloc', + AppLocalizations.of(context)!.programmeBlockDeleteError, context, null); } @@ -457,10 +458,10 @@ class _EventConfigState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("Carte de base", + Text(AppLocalizations.of(context)!.baseMapLabel, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), DropDownInputContainer( - label: "Carte :", + label: AppLocalizations.of(context)!.mapLabel, values: mapItems, initialValue: currentValue, onChange: (val) { @@ -493,11 +494,11 @@ class _EventConfigState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text("Annotations globales", + Text(AppLocalizations.of(context)!.globalAnnotationsLabel, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ElevatedButton.icon( icon: const Icon(Icons.add), - label: const Text("Ajouter une annotation"), + label: Text(AppLocalizations.of(context)!.addAnnotation), onPressed: eventDTO.id == null ? null : () { @@ -518,11 +519,11 @@ class _EventConfigState extends State { ]; }); showNotification(kSuccess, kWhite, - 'Annotation créée avec succès', context, null); + AppLocalizations.of(context)!.annotationCreatedSuccess, context, null); } } catch (e) { showNotification(kError, kWhite, - 'Erreur lors de la création de l\'annotation', + AppLocalizations.of(context)!.annotationCreateError, context, null); } }); @@ -540,9 +541,9 @@ class _EventConfigState extends State { ), const SizedBox(height: 8), if (annotations.isEmpty) - const Padding( + Padding( padding: EdgeInsets.only(top: 8), - child: Text("Aucune annotation globale", + child: Text(AppLocalizations.of(context)!.noAnnotations, style: TextStyle(fontStyle: FontStyle.italic)), ) else @@ -552,8 +553,8 @@ class _EventConfigState extends State { final labelText = ann.label != null && ann.label!.isNotEmpty ? (ann.label!.firstWhere((t) => t.language == 'FR', orElse: () => ann.label![0]) - .value ?? 'Annotation ${idx + 1}') - : 'Annotation ${idx + 1}'; + .value ?? '${AppLocalizations.of(context)!.annotationFallback} ${idx + 1}') + : '${AppLocalizations.of(context)!.annotationFallback} ${idx + 1}'; return Card( margin: const EdgeInsets.symmetric(horizontal: 0, vertical: 4), @@ -578,11 +579,11 @@ class _EventConfigState extends State { eventDTO.globalMapAnnotations = updated; }); showNotification(kSuccess, kWhite, - 'Annotation supprimée', context, null); + AppLocalizations.of(context)!.annotationDeletedSuccess, context, null); } } catch (e) { showNotification(kError, kWhite, - 'Erreur lors de la suppression', context, null); + AppLocalizations.of(context)!.annotationDeleteError, context, null); } }, ), diff --git a/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateMapAnnotation.dart b/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateMapAnnotation.dart index a0755f3..6c4ae8c 100644 --- a/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateMapAnnotation.dart +++ b/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateMapAnnotation.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/string_input_container.dart'; @@ -102,7 +103,7 @@ void showNewOrUpdateMapAnnotation( SizedBox( width: halfWidth, child: DropDownInputContainer( - label: "Géométrie :", + label: AppLocalizations.of(context)!.geometryLabel, values: geometryTypeLabels, initialValue: geometryTypeToLabel(working.geometryType), onChange: (val) => setState(() => @@ -127,7 +128,7 @@ void showNewOrUpdateMapAnnotation( SizedBox( width: halfWidth, child: StringInputContainer( - label: "Icône (material) :", + label: AppLocalizations.of(context)!.materialIconLabel, initialValue: working.icon ?? '', onChanged: (val) => setState(() => working.icon = val.isEmpty ? null : val), diff --git a/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateProgrammeBlock.dart b/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateProgrammeBlock.dart index 6b47b90..15279c0 100644 --- a/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateProgrammeBlock.dart +++ b/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateProgrammeBlock.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/confirmation_dialog.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; @@ -112,12 +113,12 @@ void showNewOrUpdateProgrammeBlock( child: ListTile( leading: Icon(Icons.schedule, color: kPrimaryColor), - title: Text("Heure de début"), + title: Text(AppLocalizations.of(context)!.startTimeLabel), subtitle: Text( workingBlock.startTime != null ? DateFormat('HH:mm') .format(workingBlock.startTime!.toLocal()) - : "Non définie", + : AppLocalizations.of(context)!.notDefined, style: TextStyle( color: kPrimaryColor, fontWeight: FontWeight.bold), @@ -161,12 +162,12 @@ void showNewOrUpdateProgrammeBlock( child: ListTile( leading: Icon(Icons.schedule_outlined, color: kPrimaryColor), - title: Text("Heure de fin"), + title: Text(AppLocalizations.of(context)!.endTimeLabel), subtitle: Text( workingBlock.endTime != null ? DateFormat('HH:mm') .format(workingBlock.endTime!.toLocal()) - : "Non définie", + : AppLocalizations.of(context)!.notDefined, style: TextStyle( color: kPrimaryColor, fontWeight: FontWeight.bold), @@ -212,7 +213,7 @@ void showNewOrUpdateProgrammeBlock( Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("Annotations", + Text(AppLocalizations.of(context)!.annotationsLabel, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 15)), @@ -242,7 +243,7 @@ void showNewOrUpdateProgrammeBlock( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( - "Aucune annotation configurée.", + AppLocalizations.of(context)!.noAnnotationConfigured, style: TextStyle( fontStyle: FontStyle.italic, color: Colors.grey[600]), diff --git a/lib/Screens/Configurations/Section/SubSection/Game/game_config.dart b/lib/Screens/Configurations/Section/SubSection/Game/game_config.dart index 5945140..ddc253c 100644 --- a/lib/Screens/Configurations/Section/SubSection/Game/game_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Game/game_config.dart @@ -6,6 +6,7 @@ import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; class GameConfig extends StatefulWidget { final String? color; @@ -148,8 +149,8 @@ class _GameConfigState extends State { ), Flexible( child: MultiStringInputAndResourceContainer( - label: "Message départ :", - modalLabel: "Message départ", + label: AppLocalizations.of(context)!.startMessageLabel, + modalLabel: AppLocalizations.of(context)!.startMessageModalLabel, fontSize: 20, color: kPrimaryColor, initialValue: gameDTO.messageDebut ?? [], diff --git a/lib/Screens/Configurations/Section/SubSection/Map/category_input_container.dart b/lib/Screens/Configurations/Section/SubSection/Map/category_input_container.dart index 65c236c..14282f6 100644 --- a/lib/Screens/Configurations/Section/SubSection/Map/category_input_container.dart +++ b/lib/Screens/Configurations/Section/SubSection/Map/category_input_container.dart @@ -4,6 +4,7 @@ import 'package:manager_api_new/api.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/category_list.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; class CategoryInputContainer extends StatefulWidget { final Color color; @@ -51,7 +52,7 @@ class _CategoryInputContainerState extends State { child: InkWell( onTap: () { List newValues = []; - showCreateOrUpdateCategories("Catégories", middleCategories, newValues, (value) { + showCreateOrUpdateCategories(AppLocalizations.of(context)!.categoriesTitle, middleCategories, newValues, (value) { setState(() { widget.onChanged(value); middleCategories = value; diff --git a/lib/Screens/Configurations/Section/SubSection/Map/category_list.dart b/lib/Screens/Configurations/Section/SubSection/Map/category_list.dart index 3079986..289b24b 100644 --- a/lib/Screens/Configurations/Section/SubSection/Map/category_list.dart +++ b/lib/Screens/Configurations/Section/SubSection/Map/category_list.dart @@ -4,6 +4,7 @@ import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/new_update_categorie.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; @@ -126,7 +127,7 @@ class _CategoryListState extends State { onTap: () async { CategorieDTO newCategory = CategorieDTO(order: null); - var result = await showNewOrUpdateCategory(newCategory, appContext, context, "Création catégorie", currentIndex); + var result = await showNewOrUpdateCategory(newCategory, appContext, context, AppLocalizations.of(context)!.createCategoryTitle, currentIndex); if (result != null) { setState(() { @@ -203,7 +204,7 @@ class _CategoryListState extends State { message: "Modifier", child: InkWell( onTap: () async { - var result = await showNewOrUpdateCategory(category, appContext, context, "Modification catégorie", currentIndex); + var result = await showNewOrUpdateCategory(category, appContext, context, AppLocalizations.of(context)!.editCategoryTitle, currentIndex); if (result != null) { setState(() { diff --git a/lib/Screens/Configurations/Section/SubSection/Map/geopoint_image_list.dart b/lib/Screens/Configurations/Section/SubSection/Map/geopoint_image_list.dart index e7bbf1b..c6bc3cc 100644 --- a/lib/Screens/Configurations/Section/SubSection/Map/geopoint_image_list.dart +++ b/lib/Screens/Configurations/Section/SubSection/Map/geopoint_image_list.dart @@ -3,6 +3,7 @@ import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/listVi import 'package:manager_app/Screens/Resources/select_resource_modal.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; @@ -89,7 +90,7 @@ class _GeoPointContentListState extends State { child: InkWell( onTap: () async { var result = await showSelectResourceModal( - "Sélectionner une ressource", + AppLocalizations.of(context)!.selectResource, 1, [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio], context, diff --git a/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart b/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart index 1fec7af..82c960f 100644 --- a/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart @@ -20,6 +20,7 @@ import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/showNe import 'package:manager_app/app_context.dart'; import 'package:manager_app/client.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart'; @@ -137,8 +138,8 @@ class _MapConfigState extends State { unselectedLabelColor: Colors.grey, indicatorColor: kPrimaryColor, tabs: [ - Tab(icon: Icon(Icons.map), text: "Points d'Intérêt"), - Tab(icon: Icon(Icons.route), text: "Parcours"), + Tab(icon: Icon(Icons.map), text: AppLocalizations.of(context)!.pointsOfInterestLabel), + Tab(icon: Icon(Icons.route), text: AppLocalizations.of(context)!.pathsLabel), ], ), Container( @@ -293,7 +294,7 @@ class _MapConfigState extends State { top: 10, left: 10, child: Text( - "Points géographiques", + AppLocalizations.of(context)!.geopointsLabel, style: TextStyle(fontSize: 15), ), ), @@ -351,7 +352,7 @@ class _MapConfigState extends State { constraints: BoxConstraints(minHeight: 85), child: StringInputContainer( - label: "Recherche:", + label: AppLocalizations.of(context)!.searchLabel, isSmall: true, fontSize: 15, fontSizeText: 15, @@ -379,7 +380,7 @@ class _MapConfigState extends State { showNotification( kSuccess, kWhite, - 'Le point géographique a été créé avec succès', + AppLocalizations.of(context)!.geopointCreatedSuccess, context, null); setState(() { @@ -390,7 +391,7 @@ class _MapConfigState extends State { showNotification( kError, kWhite, - 'Une erreur est survenue lors de la création du point géographique', + AppLocalizations.of(context)!.geopointCreateError, context, null); } @@ -428,7 +429,7 @@ class _MapConfigState extends State { } else { return Center( child: Text( - "Une erreur est survenue lors de la récupération des points géographiques"), + AppLocalizations.of(context)!.geopointsLoadError), ); } } @@ -507,7 +508,7 @@ class _MapConfigState extends State { showNotification( kSuccess, kWhite, - 'Le point géographique a été mis à jour avec succès', + AppLocalizations.of(context)!.geopointUpdatedSuccess, context, null); setState(() { @@ -518,7 +519,7 @@ class _MapConfigState extends State { showNotification( kError, kWhite, - 'Une erreur est survenue lors de la mise à jour du point géographique', + AppLocalizations.of(context)!.geopointUpdateError, context, null); } @@ -547,7 +548,7 @@ class _MapConfigState extends State { child: InkWell( onTap: () async { showConfirmationDialog( - "Êtes-vous sûr de vouloir supprimer ce point géographique ?", + AppLocalizations.of(context)!.geopointDeleteConfirm, () {}, () async { try { var pointToRemove = pointsToShow[index]; @@ -558,7 +559,7 @@ class _MapConfigState extends State { showNotification( kSuccess, kWhite, - 'Le point géographique a été supprimé avec succès', + AppLocalizations.of(context)!.geopointDeletedSuccess, context, null); // refresh UI @@ -569,7 +570,7 @@ class _MapConfigState extends State { showNotification( kError, kWhite, - 'Une erreur est survenue lors de la suppression du point géographique', + AppLocalizations.of(context)!.geopointDeleteError, context, null); } @@ -609,7 +610,7 @@ class _MapConfigState extends State { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ SingleSelectContainer( - label: "Service :", + label: AppLocalizations.of(context)!.serviceLabel, color: Colors.black, initialValue: mapProviderIn, inputValues: map_providers, @@ -625,7 +626,7 @@ class _MapConfigState extends State { widget.onChanged(mapDTO); }), GeolocInputContainer( - label: "Point de centrage :", + label: AppLocalizations.of(context)!.centerPointLabel, initialValue: mapDTO.centerLatitude != null && mapDTO.centerLongitude != null ? LatLong(double.parse(mapDTO.centerLatitude!), @@ -642,7 +643,7 @@ class _MapConfigState extends State { }, isSmall: true), ResourceInputContainer( - label: "Icône :", + label: AppLocalizations.of(context)!.iconLabel, initialValue: mapDTO.iconResourceId, color: kPrimaryColor, imageFit: BoxFit.contain, @@ -664,7 +665,7 @@ class _MapConfigState extends State { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ CheckInputContainer( - label: "Vue liste :", + label: AppLocalizations.of(context)!.listViewLabel, isChecked: mapDTO.isListViewEnabled ?? false, onChanged: (value) { setState(() { @@ -675,7 +676,7 @@ class _MapConfigState extends State { ), if (mapDTO.mapProvider == MapProvider.Google) DropDownInputContainer( - label: "Type :", + label: AppLocalizations.of(context)!.typeLabel, values: map_types, initialValue: mapType, onChange: (String? value) { @@ -685,7 +686,7 @@ class _MapConfigState extends State { ), if (mapDTO.mapProvider == MapProvider.MapBox) DropDownInputContainer( - label: "Type :", + label: AppLocalizations.of(context)!.typeLabel, values: map_types_mapBox, initialValue: mapTypeMapBox, onChange: (String? value) { @@ -694,7 +695,7 @@ class _MapConfigState extends State { }, ), SliderInputContainer( - label: "Zoom :", + label: AppLocalizations.of(context)!.zoomLabel, initialValue: mapDTO.zoom != null ? mapDTO.zoom!.toDouble() : 18, color: kPrimaryColor, @@ -708,7 +709,7 @@ class _MapConfigState extends State { Container( height: 70, child: CategoryInputContainer( - label: "Catégories :", + label: AppLocalizations.of(context)!.categoriesLabel, initialValue: mapDTO.categories != null ? mapDTO.categories! : [], color: kPrimaryColor, diff --git a/lib/Screens/Configurations/Section/SubSection/Map/new_update_categorie.dart b/lib/Screens/Configurations/Section/SubSection/Map/new_update_categorie.dart index 05ab9e0..efffa52 100644 --- a/lib/Screens/Configurations/Section/SubSection/Map/new_update_categorie.dart +++ b/lib/Screens/Configurations/Section/SubSection/Map/new_update_categorie.dart @@ -7,6 +7,7 @@ import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; Future showNewOrUpdateCategory(CategorieDTO? inputCategorieDTO, AppContext appContext, BuildContext context, String text, int currentIndex) async { @@ -60,7 +61,7 @@ Future showNewOrUpdateCategory(CategorieDTO? inputCategorieDTO, A height: size.height * 0.2, constraints: BoxConstraints(minHeight: 50, maxHeight: 80), child: MultiStringInputContainer( - label: "Nom affiché :", + label: AppLocalizations.of(context)!.displayedNameLabel, modalLabel: text, fontSize: 20, color: kPrimaryColor, @@ -79,7 +80,7 @@ Future showNewOrUpdateCategory(CategorieDTO? inputCategorieDTO, A height: size.height * 0.2, constraints: BoxConstraints(minHeight: 50, maxHeight: 80), child: ResourceInputContainer( - label: "Icône catégorie :", + label: AppLocalizations.of(context)!.categoryIconLabel, initialValue: categorieDTO.resourceDTO?.id, color: kPrimaryColor, onChanged: (ResourceDTO resource) { @@ -124,7 +125,7 @@ Future showNewOrUpdateCategory(CategorieDTO? inputCategorieDTO, A width: inputCategorieDTO != null ? 220: 150, height: 70, child: RoundedButton( - text: inputCategorieDTO != null ? "Sauvegarder" : "Créer", + text: inputCategorieDTO != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, icon: Icons.check, color: kPrimaryColor, textColor: kWhite, diff --git a/lib/Screens/Configurations/Section/SubSection/Map/showNewOrUpdateGeoPoint.dart b/lib/Screens/Configurations/Section/SubSection/Map/showNewOrUpdateGeoPoint.dart index 3c600b2..91ce6bd 100644 --- a/lib/Screens/Configurations/Section/SubSection/Map/showNewOrUpdateGeoPoint.dart +++ b/lib/Screens/Configurations/Section/SubSection/Map/showNewOrUpdateGeoPoint.dart @@ -9,6 +9,7 @@ import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/geopoint_image_list.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO, @@ -67,7 +68,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO, children: [ // Titre du dialog Text( - "Point géographique / Zone", + AppLocalizations.of(context)!.geopointZoneTitle, style: TextStyle( fontSize: 20, fontWeight: FontWeight.w500, @@ -82,7 +83,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO, children: [ // Géométrie GeometryInputContainer( - label: "Géométrie (Point/Ligne/Zone) :", + label: AppLocalizations.of(context)!.geometryTypeLabel, initialGeometry: geoPointDTO.geometry, initialColor: geoPointDTO.polyColor, onSave: (geometry, color) { @@ -176,7 +177,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO, width: thirdWidth, child: MultiStringInputContainer( label: "Tel :", - modalLabel: "Téléphone", + modalLabel: AppLocalizations.of(context)!.phoneModalLabel, fontSize: 16, isHTML: true, color: kPrimaryColor, @@ -255,7 +256,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO, SizedBox( width: thirdWidth, child: DropDownInputContainerCategories( - label: "Catégorie :", + label: AppLocalizations.of(context)!.categoryLabel, categories: mapDTO.categories!, initialValue: geoPointDTO.categorieId, onChange: (CategorieDTO? value) { @@ -326,7 +327,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO, height: 46, child: RoundedButton( text: - geoPointDTO.id != null ? "Sauvegarder" : "Créer", + geoPointDTO.id != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, icon: Icons.check, color: kPrimaryColor, textColor: kWhite, diff --git a/lib/Screens/Configurations/Section/SubSection/Menu/listView_card_subSection.dart b/lib/Screens/Configurations/Section/SubSection/Menu/listView_card_subSection.dart index 4edea4b..9f7774f 100644 --- a/lib/Screens/Configurations/Section/SubSection/Menu/listView_card_subSection.dart +++ b/lib/Screens/Configurations/Section/SubSection/Menu/listView_card_subSection.dart @@ -1,6 +1,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:manager_app/Components/confirmation_dialog.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/fetch_section_icon.dart'; import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Models/managerContext.dart'; @@ -63,17 +64,18 @@ class _ListViewCardSubSection extends State { showEditSubSection( widget.listItems[widget.index], (Object value) async { + final l = AppLocalizations.of(context)!; try { var test = updateSectionDetail(widget.listItems[widget.index], value); // update sub section await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionUpdate(test); - showNotification(kSuccess, kWhite, "La sous section a été mis à jour avec succès", context, null); + showNotification(kSuccess, kWhite, l.subSectionUpdatedSuccess, context, null); setState(() { //widget.listItems[widget.index] = value; widget.onChanged(widget.listItems); // For resfreh ui }); } catch(e) { - showNotification(kError, kWhite, "Une erreur est survenue lors de la mise à jour de la sous section", context, null); + showNotification(kError, kWhite, l.subSectionUpdateError, context, null); } }, widget.appContext, @@ -92,19 +94,20 @@ class _ListViewCardSubSection extends State { ), InkWell( onTap: () async { + final l = AppLocalizations.of(context)!; showConfirmationDialog( - "Êtes-vous sûr de vouloir supprimer cette sous section ?", + l.sectionDeleteConfirm, () {}, () async { try { var sectionToDelete = widget.listItems[widget.index]; ManagerAppContext managerAppContext = appContext.getContext(); await managerAppContext.clientAPI!.sectionApi!.sectionDelete(sectionToDelete.id!); - showNotification(kSuccess, kWhite, 'La sous section a été supprimée avec succès', context, null); + showNotification(kSuccess, kWhite, l.subSectionDeletedSuccess, context, null); // refresh UI widget.onChanged(widget.listItems); } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors de la suppression de la sous section', context, null); + showNotification(kError, kWhite, l.subSectionDeleteError, context, null); } }, context diff --git a/lib/Screens/Configurations/Section/SubSection/Menu/menu_config.dart b/lib/Screens/Configurations/Section/SubSection/Menu/menu_config.dart index cba54d1..c7001ec 100644 --- a/lib/Screens/Configurations/Section/SubSection/Menu/menu_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Menu/menu_config.dart @@ -2,6 +2,7 @@ import 'dart:js_interop'; import 'package:flutter/material.dart'; import 'package:manager_app/Components/common_loader.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Menu/listView_card_subSection.dart'; @@ -63,13 +64,13 @@ class _MenuConfigState extends State { item.order = newIndex; await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionUpdate(item); - showNotification(kSuccess, kWhite, "L'ordre des sous sections a été mis à jour avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.subSectionOrderUpdatedSuccess, context, null); setState(() { // refresh ui print("Refresh UI"); }); } catch(e) { - showNotification(kError, kWhite, "Une erreur est survenue lors de la mise à jour de l'ordre des sous sections", context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.subSectionOrderUpdateError, context, null); } /*setState( @@ -143,7 +144,7 @@ class _MenuConfigState extends State { ); } else { return Center(child: Text( - "Une erreur est survenue lors de la récupération des sous sections")); + AppLocalizations.of(context)!.errorOccurred)); } } } @@ -168,14 +169,14 @@ class _MenuConfigState extends State { newSubsection.isBeacon = false; newSubsection.isActive = true; await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionCreate(newSubsection); - showNotification(kSuccess, kWhite, 'La sous section a été créée avec succès', context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.subSectionCreatedSuccess, context, null); setState(() { // refresh ui print("Refresh UI"); }); } } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors de la création de la sous section', context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.subSectionCreateError, context, null); } }, diff --git a/lib/Screens/Configurations/Section/SubSection/Menu/showEditSubSection.dart b/lib/Screens/Configurations/Section/SubSection/Menu/showEditSubSection.dart index d8aa2c5..1d7eddc 100644 --- a/lib/Screens/Configurations/Section/SubSection/Menu/showEditSubSection.dart +++ b/lib/Screens/Configurations/Section/SubSection/Menu/showEditSubSection.dart @@ -15,6 +15,7 @@ import 'package:manager_app/Screens/Configurations/Section/SubSection/Weather/we import 'package:manager_app/Screens/Configurations/Section/SubSection/WebOrVideo/web_config.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import 'menu_config.dart'; @@ -77,7 +78,7 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext Container( constraints: BoxConstraints(minHeight: 50, maxHeight: 80), child: MultiStringInputContainer( - label: "Titre affiché:", + label: AppLocalizations.of(context)!.displayTitleLabel, modalLabel: "Titre", fontSize: 20, isHTML: true, @@ -95,7 +96,7 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext Container( constraints: BoxConstraints(minHeight: 50, maxHeight: 80), child: MultiStringInputContainer( - label: "Description affichée:", + label: AppLocalizations.of(context)!.displayedDescriptionLabel, modalLabel: "Description", fontSize: 20, isHTML: true, @@ -129,7 +130,7 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext appContext, (updatedData) { updatedRawSubSectionData = updatedData; - }), + }, context), ), ), ], @@ -183,7 +184,7 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext ); } -getSpecificData(SectionDTO subSectionDTO, Object? rawSectionData, Object sectionDetailDTO, AppContext appContext, Function(Object) onChanged) { +getSpecificData(SectionDTO subSectionDTO, Object? rawSectionData, Object sectionDetailDTO, AppContext appContext, Function(Object) onChanged, [BuildContext? context]) { switch(subSectionDTO.type) { case SectionType.Map: MapDTO mapDTO = MapDTO.fromJson(rawSectionData)!; @@ -207,7 +208,7 @@ getSpecificData(SectionDTO subSectionDTO, Object? rawSectionData, Object section VideoDTO videoDTO = VideoDTO.fromJson(rawSectionData)!; sectionDetailDTO = videoDTO; return VideoConfig( - label: "Url de la vidéo:", + label: context != null ? AppLocalizations.of(context)!.videoUrlLabel : "Url de la vidéo:", initialValue: videoDTO, onChanged: (VideoDTO updatedWebDTO) { onChanged(updatedWebDTO); diff --git a/lib/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart b/lib/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart index 3da71b0..4d967ba 100644 --- a/lib/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart +++ b/lib/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart @@ -8,6 +8,7 @@ import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; Future showNewOrUpdatePDFFile(OrderedTranslationAndResourceDTO? inputPdfFile, AppContext appContext, BuildContext context, String text) async { @@ -81,7 +82,7 @@ Future showNewOrUpdatePDFFile(OrderedTranslat height: size.height * 0.2, constraints: BoxConstraints(minHeight: 50, maxHeight: 80), child: ResourceInputContainer( - label: "Icône catégorie :", + label: AppLocalizations.of(context)!.categoryIconLabel, initialValue: categorieDTO.iconResourceId, color: kPrimaryColor, onChanged: (ResourceDTO resource) { @@ -130,7 +131,7 @@ Future showNewOrUpdatePDFFile(OrderedTranslat width: inputPdfFile != null ? 220: 150, height: 70, child: RoundedButton( - text: inputPdfFile != null ? "Sauvegarder" : "Créer", + text: inputPdfFile != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, icon: Icons.check, color: kPrimaryColor, textColor: kWhite, diff --git a/lib/Screens/Configurations/Section/SubSection/PDF/pdf_list.dart b/lib/Screens/Configurations/Section/SubSection/PDF/pdf_list.dart index 1b44ca5..0400132 100644 --- a/lib/Screens/Configurations/Section/SubSection/PDF/pdf_list.dart +++ b/lib/Screens/Configurations/Section/SubSection/PDF/pdf_list.dart @@ -5,6 +5,7 @@ import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/new_up import 'package:manager_app/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; @@ -95,7 +96,7 @@ class _PDFListState extends State { onTap: () async { OrderedTranslationAndResourceDTO newPdfFile = OrderedTranslationAndResourceDTO(order: null); - var result = await showNewOrUpdatePDFFile(newPdfFile, appContext, context, "Création PDF"); + var result = await showNewOrUpdatePDFFile(newPdfFile, appContext, context, AppLocalizations.of(context)!.createPdfTitle); if (result != null) { setState(() { diff --git a/lib/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart b/lib/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart index 6fe1129..5c17cef 100644 --- a/lib/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart @@ -5,6 +5,7 @@ import 'package:manager_app/constants.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart'; import 'package:provider/provider.dart'; import 'package:manager_app/app_context.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Components/message_notification.dart'; @@ -68,11 +69,11 @@ class _ParcoursConfigState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("Parcours Guidés", + Text(AppLocalizations.of(context)!.guidedPathsLabel, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ElevatedButton.icon( icon: Icon(Icons.add), - label: Text("Ajouter un parcours"), + label: Text(AppLocalizations.of(context)!.addPath), onPressed: () { final appContext = Provider.of(context, listen: false); @@ -111,13 +112,13 @@ class _ParcoursConfigState extends State { }); } showNotification(kSuccess, kWhite, - 'Parcours créé avec succès', context, null); + AppLocalizations.of(context)!.pathCreatedSuccess, context, null); } } catch (e) { showNotification( kError, kWhite, - 'Erreur lors de la création du parcours', + AppLocalizations.of(context)!.pathCreateError, context, null); rethrow; // Important so showNewOrUpdateGuidedPath knows it failed @@ -134,7 +135,7 @@ class _ParcoursConfigState extends State { Expanded( child: paths.isEmpty ? Center( - child: Text("Aucun parcours configuré", + child: Text(AppLocalizations.of(context)!.noPathConfigured, style: TextStyle(fontStyle: FontStyle.italic))) : ReorderableListView.builder( buildDefaultDragHandles: false, @@ -164,7 +165,7 @@ class _ParcoursConfigState extends State { showNotification( kError, kWhite, - 'Erreur lors de la mise à jour de l\'ordre', + AppLocalizations.of(context)!.pathOrderUpdateError, context, null); } @@ -185,10 +186,10 @@ class _ParcoursConfigState extends State { .firstWhere((t) => t.language == 'FR', orElse: () => path.title![0]) .value ?? - "Parcours sans titre", + AppLocalizations.of(context)!.pathNoTitle, ) - : Text("Parcours sans titre"), - subtitle: Text("${path.steps?.length ?? 0} étapes"), + : Text(AppLocalizations.of(context)!.pathNoTitle), + subtitle: Text(AppLocalizations.of(context)!.stepsCount(path.steps?.length ?? 0)), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -223,7 +224,7 @@ class _ParcoursConfigState extends State { showNotification( kSuccess, kWhite, - 'Parcours mis à jour avec succès', + AppLocalizations.of(context)!.pathUpdatedSuccess, context, null); } @@ -231,7 +232,7 @@ class _ParcoursConfigState extends State { showNotification( kError, kWhite, - 'Erreur lors de la mise à jour du parcours', + AppLocalizations.of(context)!.pathUpdateError, context, null); rethrow; @@ -265,14 +266,14 @@ class _ParcoursConfigState extends State { showNotification( kSuccess, kWhite, - 'Parcours supprimé avec succès', + AppLocalizations.of(context)!.pathDeletedSuccess, context, null); } catch (e) { showNotification( kError, kWhite, - 'Erreur lors de la suppression du parcours', + AppLocalizations.of(context)!.pathDeleteError, context, null); } diff --git a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart index fe2a41b..7cc89db 100644 --- a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart +++ b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/check_input_container.dart'; @@ -107,7 +108,7 @@ void showNewOrUpdateGuidedPath( SizedBox( width: thirdWidth, child: CheckInputContainer( - label: "Linéaire :", + label: AppLocalizations.of(context)!.linearLabel, isChecked: workingPath.isLinear ?? false, onChanged: (val) => setState( () => workingPath.isLinear = val), @@ -117,7 +118,7 @@ void showNewOrUpdateGuidedPath( SizedBox( width: thirdWidth, child: CheckInputContainer( - label: "Réussite requise :", + label: AppLocalizations.of(context)!.requiredSuccessLabel, isChecked: workingPath.requireSuccessToAdvance ?? false, @@ -144,7 +145,7 @@ void showNewOrUpdateGuidedPath( Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("Étapes du parcours", + Text(AppLocalizations.of(context)!.pathStepsLabel, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 15)), @@ -174,7 +175,7 @@ void showNewOrUpdateGuidedPath( Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( - "Aucun point/étape configuré.", + AppLocalizations.of(context)!.noStepConfigured, style: TextStyle( fontStyle: FontStyle.italic, color: Colors.grey[600]), @@ -198,8 +199,8 @@ void showNewOrUpdateGuidedPath( orElse: () => step.title![0]) .value ?? - "Étape $index" - : "Étape $index", + "${AppLocalizations.of(context)!.stepFallback} $index" + : "${AppLocalizations.of(context)!.stepFallback} $index", ), subtitle: isEscapeMode ? Text( diff --git a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart index 80aaab9..773b043 100644 --- a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart +++ b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart @@ -8,6 +8,7 @@ import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'showNewOrUpdateQuizQuestion.dart'; void showNewOrUpdateGuidedStep( @@ -53,7 +54,7 @@ void showNewOrUpdateGuidedStep( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - step == null ? "Nouvelle Étape" : "Modifier l'Étape", + step == null ? AppLocalizations.of(context)!.newStepTitle : AppLocalizations.of(context)!.editStepTitle, style: TextStyle( color: kPrimaryColor, fontSize: 20, @@ -72,7 +73,7 @@ void showNewOrUpdateGuidedStep( width: halfWidth, child: MultiStringInputContainer( label: "Titre :", - modalLabel: "Titre de l'étape", + modalLabel: AppLocalizations.of(context)!.stepTitleLabel, initialValue: workingStep.title ?? [], onGetResult: (val) => setState(() => workingStep.title = val), @@ -86,7 +87,7 @@ void showNewOrUpdateGuidedStep( width: halfWidth, child: MultiStringInputContainer( label: "Description :", - modalLabel: "Description de l'étape", + modalLabel: AppLocalizations.of(context)!.stepDescriptionLabel, initialValue: workingStep.description ?? [], onGetResult: (val) => setState( () => workingStep.description = val), @@ -100,7 +101,7 @@ void showNewOrUpdateGuidedStep( Divider(height: 24), // Géométrie — Directement avec GeometryDTO GeometryInputContainer( - label: "Emplacement de l'étape :", + label: AppLocalizations.of(context)!.stepLocationLabel, initialGeometry: workingStep.geometry, initialColor: null, onSave: (geometry, color) { @@ -118,7 +119,7 @@ void showNewOrUpdateGuidedStep( Expanded( child: SwitchListTile( dense: true, - title: Text("Cachée initialement"), + title: Text(AppLocalizations.of(context)!.initiallyHiddenLabel), value: workingStep.isHiddenInitially ?? false, onChanged: (val) => setState(() => workingStep.isHiddenInitially = val), activeThumbColor: kPrimaryColor, @@ -127,7 +128,7 @@ void showNewOrUpdateGuidedStep( Expanded( child: SwitchListTile( dense: true, - title: Text("Verrouillée"), + title: Text(AppLocalizations.of(context)!.lockedLabel), value: workingStep.isStepLocked ?? false, onChanged: (val) => setState(() => workingStep.isStepLocked = val), activeThumbColor: kPrimaryColor, @@ -151,7 +152,7 @@ void showNewOrUpdateGuidedStep( SizedBox( width: halfWidth, child: StringInputContainer( - label: "Durée (secondes) :", + label: AppLocalizations.of(context)!.durationSecondsLabel, initialValue: workingStep.timerSeconds?.toString() ?? "", onChanged: (val) => setState(() => workingStep.timerSeconds = int.tryParse(val)), @@ -176,7 +177,7 @@ void showNewOrUpdateGuidedStep( ], SizedBox(height: 8), StringInputContainer( - label: "GeoPoint de déclenchement (ID) :", + label: AppLocalizations.of(context)!.triggerGeopointLabel, initialValue: workingStep.triggerGeoPointId?.toString() ?? "", onChanged: (val) => setState(() => workingStep.triggerGeoPointId = int.tryParse(val)), @@ -187,7 +188,7 @@ void showNewOrUpdateGuidedStep( Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("Questions / Défis", + Text(AppLocalizations.of(context)!.questionsChallengesLabel, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 15)), @@ -220,7 +221,7 @@ void showNewOrUpdateGuidedStep( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( - "Aucune question configurée.", + AppLocalizations.of(context)!.noQuestionsConfigured, style: TextStyle( fontStyle: FontStyle.italic, color: Colors.grey[600]), diff --git a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart index 69adcba..85148d8 100644 --- a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart +++ b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart'; @@ -104,8 +105,8 @@ void showNewOrUpdateQuizQuestion( children: [ // --- Intitulé (multi-langue via MultiStringInputContainer) --- MultiStringInputContainer( - label: "Question posée :", - modalLabel: "Intitulé de la question", + label: AppLocalizations.of(context)!.questionAskedLabel, + modalLabel: AppLocalizations.of(context)!.questionTitleLabel, initialValue: workingLabel, onGetResult: (val) => setState(() { workingLabel = val; @@ -146,7 +147,7 @@ void showNewOrUpdateQuizQuestion( if (workingQuestion.validationQuestionType == QuestionType.number0) ...[ Divider(height: 24), - Text("Réponse attendue :", + Text(AppLocalizations.of(context)!.expectedAnswerLabel, style: TextStyle(fontWeight: FontWeight.bold)), SizedBox(height: 8), Builder(builder: (_) { @@ -156,7 +157,7 @@ void showNewOrUpdateQuizQuestion( workingQuestion.responses[0].label); return MultiStringInputContainer( label: "", - modalLabel: "Réponse attendue", + modalLabel: AppLocalizations.of(context)!.expectedAnswerModalLabel, initialValue: respLabel, onGetResult: (val) => setState(() { workingQuestion.responses[0].label = @@ -170,7 +171,7 @@ void showNewOrUpdateQuizQuestion( }), SizedBox(height: 4), Text( - "La validation se fait par comparaison (insensible à la casse).", + AppLocalizations.of(context)!.validationNote, style: TextStyle( fontSize: 12, fontStyle: FontStyle.italic, @@ -187,7 +188,7 @@ void showNewOrUpdateQuizQuestion( Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("Réponses possibles :", + Text(AppLocalizations.of(context)!.possibleAnswersLabel, style: TextStyle(fontWeight: FontWeight.bold)), TextButton.icon( @@ -208,7 +209,7 @@ void showNewOrUpdateQuizQuestion( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( - "Aucune réponse définie. Ajoutez-en au moins une.", + AppLocalizations.of(context)!.noAnswerDefined, style: TextStyle( fontStyle: FontStyle.italic, color: Colors.grey[600]), @@ -233,8 +234,8 @@ void showNewOrUpdateQuizQuestion( // Checkbox bonne réponse Tooltip( message: resp.isGood == true - ? "Bonne réponse ✓" - : "Mauvaise réponse", + ? AppLocalizations.of(context)!.correctAnswer + : AppLocalizations.of(context)!.wrongAnswer, child: Checkbox( value: resp.isGood ?? false, activeColor: kSuccess, @@ -245,8 +246,8 @@ void showNewOrUpdateQuizQuestion( // Traductions (via MultiStringInputContainer) Expanded( child: MultiStringInputContainer( - label: "Réponse ${i + 1} :", - modalLabel: "Réponse ${i + 1}", + label: "${AppLocalizations.of(context)!.answerLabel} ${i + 1} :", + modalLabel: "${AppLocalizations.of(context)!.answerLabel} ${i + 1}", initialValue: respLabel, onGetResult: (val) => setState( () => resp.label = @@ -273,7 +274,7 @@ void showNewOrUpdateQuizQuestion( ), SizedBox(height: 4), Text( - "✓ = bonne réponse. Plusieurs peuvent être correctes.", + AppLocalizations.of(context)!.answerNote, style: TextStyle( fontSize: 12, fontStyle: FontStyle.italic, diff --git a/lib/Screens/Configurations/Section/SubSection/Quizz/new_update_question_quizz.dart b/lib/Screens/Configurations/Section/SubSection/Quizz/new_update_question_quizz.dart index 0557772..25af3e9 100644 --- a/lib/Screens/Configurations/Section/SubSection/Quizz/new_update_question_quizz.dart +++ b/lib/Screens/Configurations/Section/SubSection/Quizz/new_update_question_quizz.dart @@ -7,6 +7,7 @@ import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Quizz/quizz_answer_list.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; Future showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO, AppContext appContext, BuildContext context, String text) async { @@ -54,7 +55,7 @@ Future showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO, mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ResourceInputContainer( - label: "Image fond d'écran :", + label: AppLocalizations.of(context)!.backgroundImageLabel, initialValue: questionDTO.imageBackgroundResourceId, color: kPrimaryColor, onChanged: (ResourceDTO resource) { @@ -75,8 +76,8 @@ Future showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO, height: size.height * 0.15, constraints: BoxConstraints(minHeight: 50, maxHeight: 80), child: MultiStringInputAndResourceContainer( - label: "Question :", - modalLabel: "Question", + label: AppLocalizations.of(context)!.questionInputLabel, + modalLabel: AppLocalizations.of(context)!.questionLabel, fontSize: 20, color: kPrimaryColor, resourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio], @@ -133,7 +134,7 @@ Future showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO, width: 175, height: 70, child: RoundedButton( - text: "Annuler", + text: AppLocalizations.of(context)!.cancel, icon: Icons.undo, color: kSecond, press: () { @@ -149,7 +150,7 @@ Future showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO, width: inputQuestionDTO != null ? 220: 150, height: 70, child: RoundedButton( - text: inputQuestionDTO != null ? "Sauvegarder" : "Créer", + text: inputQuestionDTO != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, icon: Icons.check, color: kPrimaryColor, textColor: kWhite, @@ -157,7 +158,7 @@ Future showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO, if(!questionDTO.label!.any((label) => label.value == null || label.value!.trim() == "")) { Navigator.pop(dialogContext, questionDTO); } else { - showNotification(kPrimaryColor, kWhite, "La traduction n'est pas complète", context, null); + showNotification(kPrimaryColor, kWhite, AppLocalizations.of(context)!.translationIncomplete, context, null); } }, fontSize: 20, diff --git a/lib/Screens/Configurations/Section/SubSection/Quizz/quizz_answer_list.dart b/lib/Screens/Configurations/Section/SubSection/Quizz/quizz_answer_list.dart index b2c7d07..4853952 100644 --- a/lib/Screens/Configurations/Section/SubSection/Quizz/quizz_answer_list.dart +++ b/lib/Screens/Configurations/Section/SubSection/Quizz/quizz_answer_list.dart @@ -5,6 +5,7 @@ import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:manager_app/Components/multi_string_input_html_modal.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; @@ -92,7 +93,7 @@ class _QuizzResponseListState extends State { top: 10, left: 10, child: Text( - "Réponses", + AppLocalizations.of(context)!.answersLabel, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), ), ), @@ -117,7 +118,7 @@ class _QuizzResponseListState extends State { } }); - showMultiStringInputAndResourceHTML("Réponse", "Créer la réponse", true, initials, newValues, (value) { + showMultiStringInputAndResourceHTML(AppLocalizations.of(context)!.answerLabel, AppLocalizations.of(context)!.createAnswerTitle, true, initials, newValues, (value) { if(value != null && value.isNotEmpty) { setState(() { newResponse.label = value; @@ -184,7 +185,7 @@ class _QuizzResponseListState extends State { child: Row( children: [ Tooltip( - message: "Si coché, la réponse est valide", + message: AppLocalizations.of(context)!.answerValidNote, child: Padding( padding: const EdgeInsets.all(8.0), child: Checkbox( @@ -217,7 +218,7 @@ class _QuizzResponseListState extends State { } }); - showMultiStringInputAndResourceHTML("Réponse", "Modifier la réponse", true, initials, newValues, (value) { + showMultiStringInputAndResourceHTML(AppLocalizations.of(context)!.answerLabel, AppLocalizations.of(context)!.editAnswerTitle, true, initials, newValues, (value) { setState(() { if (value != null && response.label! != value) { response.label = value; diff --git a/lib/Screens/Configurations/Section/SubSection/Quizz/quizz_config.dart b/lib/Screens/Configurations/Section/SubSection/Quizz/quizz_config.dart index 67a84f5..dc78297 100644 --- a/lib/Screens/Configurations/Section/SubSection/Quizz/quizz_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Quizz/quizz_config.dart @@ -8,6 +8,7 @@ import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/client.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/Components/common_loader.dart'; import 'dart:convert'; @@ -62,13 +63,13 @@ class _QuizzConfigState extends State { item.order = newIndex; try { await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizUpdate(item); - showNotification(kSuccess, kWhite, "L'ordre des questions a été mis à jour avec succès", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.questionOrderUpdatedSuccess, context, null); setState(() { // refresh ui print("Refresh UI"); }); } catch(e) { - showNotification(kError, kWhite, "Une erreur est survenue lors de la mise à jour de l'ordre des questions", context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.questionOrderUpdateError, context, null); } } @@ -86,12 +87,12 @@ class _QuizzConfigState extends State { Container( height: 50, child: RoundedButton( - text: "Mauvais score", + text: AppLocalizations.of(context)!.quizBadScore, color: kPrimaryColor, textColor: kWhite, icon: Icons.message, press: () async { - updateScoreQuizMessage(context, appContext, quizzDTO.badLevel, "Message pour un mauvais score", 0); + updateScoreQuizMessage(context, appContext, quizzDTO.badLevel, AppLocalizations.of(context)!.quizBadScoreMsg, 0); }, fontSize: 20, horizontal: 10, @@ -102,12 +103,12 @@ class _QuizzConfigState extends State { Container( height: 50, child: RoundedButton( - text: "Moyen score", + text: AppLocalizations.of(context)!.quizMediumScore, color: kPrimaryColor, textColor: kWhite, icon: Icons.message, press: () async { - updateScoreQuizMessage(context, appContext, quizzDTO.mediumLevel, "Message pour un score moyen", 1); + updateScoreQuizMessage(context, appContext, quizzDTO.mediumLevel, AppLocalizations.of(context)!.quizMediumScoreMsg, 1); }, fontSize: 20, horizontal: 10, @@ -118,12 +119,12 @@ class _QuizzConfigState extends State { Container( height: 50, child: RoundedButton( - text: "Bon score", + text: AppLocalizations.of(context)!.quizGoodScore, color: kPrimaryColor, textColor: kWhite, icon: Icons.message, press: () async { - updateScoreQuizMessage(context, appContext, quizzDTO.goodLevel, "Message pour un bon score", 2); + updateScoreQuizMessage(context, appContext, quizzDTO.goodLevel, AppLocalizations.of(context)!.quizGoodScoreMsg, 2); }, fontSize: 20, horizontal: 10, @@ -134,12 +135,12 @@ class _QuizzConfigState extends State { Container( height: 50, child: RoundedButton( - text: "Excellent score", + text: AppLocalizations.of(context)!.quizExcellentScore, color: kPrimaryColor, textColor: kWhite, icon: Icons.message, press: () async { - updateScoreQuizMessage(context, appContext, quizzDTO.greatLevel, "Message pour un excellent score", 3); + updateScoreQuizMessage(context, appContext, quizzDTO.greatLevel, AppLocalizations.of(context)!.quizExcellentScoreMsg, 3); }, fontSize: 20, horizontal: 10, @@ -204,7 +205,7 @@ class _QuizzConfigState extends State { top: 10, left: 10, child: Text( - "Questions", + AppLocalizations.of(context)!.questionsLabel, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), ), @@ -215,20 +216,20 @@ class _QuizzConfigState extends State { child: InkWell( onTap: () async { QuestionDTO? result = await showNewOrUpdateQuestionQuizz( - null, appContext, context, "Question"); + null, appContext, context, AppLocalizations.of(context)!.questionLabel); try { if(result != null) { await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizCreate(quizzDTO.id!, result); - showNotification(kSuccess, kWhite, 'La question a été créée avec succès', context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.questionCreatedSuccess, context, null); setState(() { // refresh ui print("Refresh UI"); }); } } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors de la création de la question', context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.questionCreateError, context, null); } /*setState(() { @@ -267,7 +268,7 @@ class _QuizzConfigState extends State { ); } else { return Center(child: Text( - "Une erreur est survenue lors de la récupération des questions")); + AppLocalizations.of(context)!.questionsLoadError)); } } } @@ -314,28 +315,28 @@ class _QuizzConfigState extends State { child: Row( children: [ Tooltip( - message: "Modifier", + message: AppLocalizations.of(context)!.edit, child: InkWell( onTap: () async { QuestionDTO? result = await showNewOrUpdateQuestionQuizz( question, appContext, context, - "Modifier la question" + AppLocalizations.of(context)!.editQuestion ); try { if(result != null) { await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizUpdate(result); - showNotification(kSuccess, kWhite, 'La question a été mis à jour avec succès', context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.questionUpdatedSuccess, context, null); setState(() { // refresh ui print("Refresh UI"); }); } } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors de la mise à jour de la question', context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.questionUpdateError, context, null); } /*setState(() { @@ -354,24 +355,24 @@ class _QuizzConfigState extends State { ), ), Tooltip( - message: "Supprimer", + message: AppLocalizations.of(context)!.delete, child: InkWell( onTap: () { showConfirmationDialog( - "Êtes-vous sûr de vouloir supprimer cette question ?", + AppLocalizations.of(context)!.questionDeleteConfirm, () {}, () async { try { var questionToRemove = questions[index]; (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizDeleteWithHttpInfo(questionToRemove.id!); - showNotification(kSuccess, kWhite, 'La question a été supprimée avec succès', context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.questionDeletedSuccess, context, null); // refresh UI setState(() { print("Refresh UI"); }); } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors de la suppression de la question', context, null); + showNotification(kError, kWhite, AppLocalizations.of(context)!.questionDeleteError, context, null); } }, context diff --git a/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart b/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart index 3947ec5..ed6e984 100644 --- a/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart +++ b/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart @@ -5,6 +5,7 @@ import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; Future showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, AppContext appContext, BuildContext context, bool showTitle, bool showDescription) async { @@ -75,7 +76,7 @@ Future showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap child: Column( children: [ MultiStringInputContainer( - label: "Titre affiché:", + label: AppLocalizations.of(context)!.displayTitleLabel, modalLabel: "Titre", fontSize: 20, isHTML: true, @@ -90,7 +91,7 @@ Future showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap isTitle: true ), MultiStringInputContainer( - label: "Description affichée:", + label: AppLocalizations.of(context)!.displayedDescriptionLabel, modalLabel: "Description", fontSize: 20, isHTML: true, @@ -140,7 +141,7 @@ Future showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap width: inputContentDTO != null ? 220: 150, height: 70, child: RoundedButton( - text: inputContentDTO != null ? "Sauvegarder" : "Créer", + text: inputContentDTO != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, icon: Icons.check, color: kPrimaryColor, textColor: kWhite, diff --git a/lib/Screens/Configurations/Section/section_detail_screen.dart b/lib/Screens/Configurations/Section/section_detail_screen.dart index 78b3718..3604342 100644 --- a/lib/Screens/Configurations/Section/section_detail_screen.dart +++ b/lib/Screens/Configurations/Section/section_detail_screen.dart @@ -10,6 +10,7 @@ import 'package:manager_app/Components/fetch_section_icon.dart'; import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/message_notification.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/number_input_container.dart'; import 'package:manager_app/Components/rounded_button.dart'; @@ -120,10 +121,10 @@ class _SectionDetailScreenState extends State { } else { return Center( child: Text( - "Une erreur est survenue lors de la récupération de la section")); + AppLocalizations.of(context)!.sectionLoadError)); } } else if (snapshot.connectionState == ConnectionState.none) { - return Text("No data"); + return Text(AppLocalizations.of(context)!.noData); } else { return Center( child: Container( @@ -235,7 +236,7 @@ class _SectionDetailScreenState extends State { showNotification( kSuccess, kWhite, - 'Ce QR code a été copié dans le presse papier', + AppLocalizations.of(context)!.qrCodeCopied, context, null); }, @@ -263,7 +264,7 @@ class _SectionDetailScreenState extends State { ], ), CheckInputContainer( - label: "Beacon :", + label: AppLocalizations.of(context)!.beaconLabel, isChecked: sectionDTO.isBeacon!, onChanged: (value) { setState(() { @@ -274,7 +275,7 @@ class _SectionDetailScreenState extends State { ), if (sectionDTO.isBeacon!) NumberInputContainer( - label: "Identifiant Beacon :", + label: AppLocalizations.of(context)!.beaconIdLabel, initialValue: sectionDTO.beaconId != null ? sectionDTO.beaconId! : 0, @@ -287,7 +288,7 @@ class _SectionDetailScreenState extends State { showNotification( Colors.orange, kWhite, - 'Cela doit être un chiffre', + AppLocalizations.of(context)!.beaconMustBeNumber, context, null); } @@ -302,7 +303,7 @@ class _SectionDetailScreenState extends State { SizedBox( height: 100, child: StringInputContainer( - label: "Identifiant :", + label: AppLocalizations.of(context)!.identifierLabel, initialValue: sectionDTO.label, onChanged: (String value) { sectionDTO.label = value; @@ -312,8 +313,8 @@ class _SectionDetailScreenState extends State { SizedBox( height: 100, child: MultiStringInputContainer( - label: "Titre affiché:", - modalLabel: "Titre", + label: AppLocalizations.of(context)!.displayTitleLabel, + modalLabel: AppLocalizations.of(context)!.messageTitle, color: kPrimaryColor, initialValue: sectionDTO.title!, onGetResult: (value) { @@ -351,7 +352,7 @@ class _SectionDetailScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ ResourceInputContainer( - label: "Image :", + label: AppLocalizations.of(context)!.imageLabel, initialValue: sectionDTO.imageId, color: kPrimaryColor, onChanged: (ResourceDTO resource) { @@ -400,7 +401,7 @@ class _SectionDetailScreenState extends State { Padding( padding: const EdgeInsets.all(10.0), child: RoundedButton( - text: "Annuler", + text: AppLocalizations.of(context)!.cancel, icon: Icons.undo, color: Colors.grey, textColor: Colors.white, @@ -413,7 +414,7 @@ class _SectionDetailScreenState extends State { if (canEdit) Padding( padding: const EdgeInsets.all(8.0), child: RoundedButton( - text: "Supprimer", + text: AppLocalizations.of(context)!.delete, icon: Icons.delete, color: kError, textColor: Colors.white, @@ -426,7 +427,7 @@ class _SectionDetailScreenState extends State { if (canEdit) Padding( padding: const EdgeInsets.all(8.0), child: RoundedButton( - text: "Sauvegarder", + text: AppLocalizations.of(context)!.save, icon: Icons.done, color: kSuccess, textColor: Colors.white, @@ -456,7 +457,7 @@ class _SectionDetailScreenState extends State { Future delete(AppContext appContext) async { showConfirmationDialog( - "Êtes-vous sûr de vouloir supprimer cette section ?", () {}, () async { + AppLocalizations.of(context)!.sectionDeleteConfirm, () {}, () async { ManagerAppContext managerAppContext = appContext.getContext(); await managerAppContext.clientAPI!.sectionApi! .sectionDelete(sectionDTO.id!); @@ -482,12 +483,12 @@ class _SectionDetailScreenState extends State { showNotification( kSuccess, kWhite, - 'Les traductions de la section ont été sauvegardées avec succès', + AppLocalizations.of(context)!.sectionTranslationSaved, context, null); } else { showNotification(kSuccess, kWhite, - 'La section a été sauvegardée avec succès', context, null); + AppLocalizations.of(context)!.sectionSavedSuccess, context, null); } } @@ -509,7 +510,7 @@ class _SectionDetailScreenState extends State { ); case SectionType.Video: return VideoConfig( - label: "Url de la vidéo:", + label: AppLocalizations.of(context)!.videoUrlLabel, initialValue: sectionDetailDTO as VideoDTO, onChanged: (VideoDTO updatedWebDTO) { sectionDetailDTO = updatedWebDTO; @@ -517,7 +518,7 @@ class _SectionDetailScreenState extends State { ); case SectionType.Web: return WebConfig( - label: "Url du site web:", + label: AppLocalizations.of(context)!.webUrlLabel, initialValue: sectionDetailDTO as WebDTO, onChanged: (WebDTO updatedWebDTO) { sectionDetailDTO = updatedWebDTO; diff --git a/lib/Screens/Configurations/configuration_detail_screen.dart b/lib/Screens/Configurations/configuration_detail_screen.dart index d8858dc..4260015 100644 --- a/lib/Screens/Configurations/configuration_detail_screen.dart +++ b/lib/Screens/Configurations/configuration_detail_screen.dart @@ -11,6 +11,7 @@ import 'package:manager_app/Components/number_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/message_notification.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/multi_select_dropdown_language_container.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/rounded_button.dart'; @@ -50,7 +51,7 @@ class _ConfigurationDetailScreenState extends State { if (snapshot.connectionState == ConnectionState.done) { return bodyConfiguration(snapshot.data, size, appContext, context); } else if (snapshot.connectionState == ConnectionState.none) { - return Text("No data"); + return Text(AppLocalizations.of(context)!.noData); } else { return Center( child: Container( @@ -100,11 +101,11 @@ class _ConfigurationDetailScreenState extends State { anchorElement.click(); } else { File test = await FileHelper().storeConfiguration(export); - showNotification(kSuccess, kWhite, "L'export de la configuration a réussi, le document se trouve à cet endroit : " + test.path, context, 3000); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configExportSuccess(test.path), context, 3000); } } catch(e) { log(e.toString()); - showNotification(kPrimaryColor, kWhite, "L'export de la configuration a échoué", context, null); + showNotification(kPrimaryColor, kWhite, AppLocalizations.of(context)!.configExportFailed, context, null); } }, child: Padding( @@ -167,7 +168,7 @@ class _ConfigurationDetailScreenState extends State { SizedBox( height: 70, child: StringInputContainer( - label: "Identifiant :", + label: AppLocalizations.of(context)!.identifierLabel, fontSize: 20, fontSizeText: 20, initialValue: configurationDTO.label, @@ -182,7 +183,7 @@ class _ConfigurationDetailScreenState extends State { crossAxisAlignment: WrapCrossAlignment.center, children: [ MultiSelectDropdownLanguageContainer( - label: "Langues :", + label: AppLocalizations.of(context)!.languagesLabel, initialValue: configurationDTO.languages != null ? configurationDTO.languages! : [], values: languages, isMultiple: true, @@ -195,7 +196,7 @@ class _ConfigurationDetailScreenState extends State { ), CheckInputContainer( icon: Icons.signal_wifi_off, - label: "Hors ligne :", + label: "Hors ligne :", // no ARB key for this one yet, leave as-is fontSize: 20, isChecked: configurationDTO.isOffline, onChanged: (value) { @@ -211,7 +212,7 @@ class _ConfigurationDetailScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ ResourceInputContainer( - label: "Image fond d'écran :", + label: AppLocalizations.of(context)!.mainImageLabel, fontSize: 20, initialValue: configurationDTO.imageId, color: kPrimaryColor, @@ -226,7 +227,7 @@ class _ConfigurationDetailScreenState extends State { }, ), ResourceInputContainer( - label: "Image loader :", + label: AppLocalizations.of(context)!.loaderLabel, fontSize: 20, initialValue: configurationDTO.loaderImageId, color: kPrimaryColor, @@ -355,7 +356,7 @@ class _ConfigurationDetailScreenState extends State { Padding( padding: const EdgeInsets.all(5.0), child: RoundedButton( - text: "Annuler", + text: AppLocalizations.of(context)!.cancel, icon: Icons.undo, color: Colors.grey, textColor: Colors.white, @@ -368,7 +369,7 @@ class _ConfigurationDetailScreenState extends State { if (canEdit) Padding( padding: const EdgeInsets.all(5.0), child: RoundedButton( - text: "Supprimer", + text: AppLocalizations.of(context)!.delete, icon: Icons.delete, color: kError, textColor: Colors.white, @@ -381,7 +382,7 @@ class _ConfigurationDetailScreenState extends State { if (canEdit) Padding( padding: const EdgeInsets.all(5.0), child: RoundedButton( - text: "Sauvegarder", + text: AppLocalizations.of(context)!.save, icon: Icons.done, color: kSuccess, textColor: Colors.white, @@ -432,14 +433,14 @@ class _ConfigurationDetailScreenState extends State { Future delete(ConfigurationDTO configurationDTO, AppContext appContext) async { showConfirmationDialog( - "Êtes-vous sûr de vouloir supprimer cette visite ?", + AppLocalizations.of(context)!.configDeleteConfirm, () {}, () async { ManagerAppContext managerAppContext = appContext.getContext(); await managerAppContext.clientAPI!.configurationApi!.configurationDelete(configurationDTO.id!); managerAppContext.selectedConfiguration = null; appContext.setContext(managerAppContext); - showNotification(kSuccess, kWhite, 'La configuration a été supprimée avec succès', context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeletedSuccess, context, null); }, context ); @@ -451,7 +452,7 @@ class _ConfigurationDetailScreenState extends State { managerAppContext.selectedConfiguration = configuration; appContext.setContext(managerAppContext); - showNotification(kSuccess, kWhite, 'La configuration a été sauvegardée avec succès', context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configSavedSuccess, context, null); } Future getConfiguration(ConfigurationDetailScreen widget, Client client) async { diff --git a/lib/Screens/Configurations/configurations_screen.dart b/lib/Screens/Configurations/configurations_screen.dart index 655bd3a..dfe9d43 100644 --- a/lib/Screens/Configurations/configurations_screen.dart +++ b/lib/Screens/Configurations/configurations_screen.dart @@ -1,6 +1,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:manager_app/Components/common_loader.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Configurations/configuration_detail_screen.dart'; @@ -46,7 +47,7 @@ class _ConfigurationsScreenState extends State { if (managerAppContext.canEdit) tempOutput.add(ConfigurationDTO(id: null)); return bodyGrid(tempOutput, size, appContext, context); } else if (snapshot.connectionState == ConnectionState.none) { - return Text("No data"); + return Text(AppLocalizations.of(context)!.noData); } else { return Center( child: Container( @@ -96,7 +97,7 @@ class _ConfigurationsScreenState extends State { managerAppContext.selectedConfiguration = null; await appContext.setContext(managerAppContext); - showNotification(kSuccess, kWhite, 'La configuration a été créée avec succès', context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configCreatedSuccess, context, null); } } } else { diff --git a/lib/Screens/Configurations/new_configuration_popup.dart b/lib/Screens/Configurations/new_configuration_popup.dart index 0bb2b26..03f3b5a 100644 --- a/lib/Screens/Configurations/new_configuration_popup.dart +++ b/lib/Screens/Configurations/new_configuration_popup.dart @@ -1,6 +1,7 @@ //import 'package:filepicker_windows/filepicker_windows.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/string_input_container.dart'; @@ -11,6 +12,7 @@ import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; Future showNewConfiguration(AppContext appContext, ValueChanged isImport, BuildContext context, BuildContext mainContext) { + final l = AppLocalizations.of(mainContext)!; ConfigurationDTO configurationDTO = new ConfigurationDTO(); Size size = MediaQuery.of(mainContext).size; configurationDTO.label = ""; @@ -28,12 +30,12 @@ Future showNewConfiguration(AppContext appContext, ValueChang crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - Center(child: Text("Nouvelle configuration", style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400))), + Center(child: Text(l.newConfiguration, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400))), Center( child: SizedBox( height: 100, child: StringInputContainer( - label: "Nom :", + label: l.configNameLabel, initialValue: configurationDTO.label, onChanged: (value) { configurationDTO.label = value; @@ -41,12 +43,12 @@ Future showNewConfiguration(AppContext appContext, ValueChang ), ), ), - Text("ou"), + Text(l.orText), Column( children: [ Padding( padding: const EdgeInsets.only(bottom: 5.0), - child: Text("Importer", style: TextStyle(fontSize: 25, fontWeight: FontWeight.w300)), + child: Text(l.importLabel, style: TextStyle(fontSize: 25, fontWeight: FontWeight.w300)), ), InkWell( onTap: () async { @@ -85,7 +87,7 @@ Future showNewConfiguration(AppContext appContext, ValueChang width: 175, height: 70, child: RoundedButton( - text: "Annuler", + text: l.cancel, icon: Icons.undo, color: kSecond, press: () { @@ -101,7 +103,7 @@ Future showNewConfiguration(AppContext appContext, ValueChang width: 150, height: 70, child: RoundedButton( - text: "Créer", + text: l.create, icon: Icons.check, color: kPrimaryColor, textColor: kWhite, @@ -110,7 +112,7 @@ Future showNewConfiguration(AppContext appContext, ValueChang //create(configurationDTO, appContext, context); Navigator.of(context).pop(configurationDTO); } else { - showNotification(Colors.orange, kWhite, 'Veuillez spécifier un nom pour la nouvelle visite', context, null); + showNotification(Colors.orange, kWhite, l.configNameRequired, context, null); } }, fontSize: 20, diff --git a/lib/Screens/Configurations/new_section_popup.dart b/lib/Screens/Configurations/new_section_popup.dart index 5a2c313..ba0730a 100644 --- a/lib/Screens/Configurations/new_section_popup.dart +++ b/lib/Screens/Configurations/new_section_popup.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:manager_app/Components/dropDown_input_container.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/string_input_container.dart'; @@ -10,6 +11,7 @@ import 'package:manager_api_new/api.dart'; Future showNewSection(String configurationId, AppContext appContext, BuildContext contextBuild, bool isSubSection) { + final l = AppLocalizations.of(contextBuild)!; SectionDTO sectionDTO = new SectionDTO(); sectionDTO.label = ""; sectionDTO.configurationId = configurationId; @@ -18,7 +20,6 @@ Future showNewSection(String configurationId, sectionDTO.parentId = isSubSection ? (appContext.getContext() as ManagerAppContext).selectedSection!.id : null; - Size size = MediaQuery.of(contextBuild).size; sectionDTO.type = SectionType.Map; var section = showDialog( @@ -35,8 +36,8 @@ Future showNewSection(String configurationId, children: [ Text( isSubSection - ? "Nouvelle sous section" - : "Nouvelle section", + ? l.newSubSection + : l.newSection, style: new TextStyle( fontSize: 25, fontWeight: FontWeight.w400)), Column( @@ -44,7 +45,7 @@ Future showNewSection(String configurationId, SizedBox( height: 100, child: StringInputContainer( - label: "Nom :", + label: l.sectionNameLabel, initialValue: sectionDTO.label, onChanged: (value) { sectionDTO.label = value; @@ -52,7 +53,7 @@ Future showNewSection(String configurationId, ), ), DropDownInputContainer( - label: "Type:", + label: l.sectionTypeLabel, values: isSubSection ? section_types .where( @@ -92,7 +93,7 @@ Future showNewSection(String configurationId, width: 175, height: 70, child: RoundedButton( - text: "Annuler", + text: l.cancel, icon: Icons.undo, color: kSecond, press: () { @@ -108,7 +109,7 @@ Future showNewSection(String configurationId, width: 150, height: 70, child: RoundedButton( - text: "Créer", + text: l.create, icon: Icons.check, color: kPrimaryColor, textColor: kWhite, @@ -122,7 +123,7 @@ Future showNewSection(String configurationId, showNotification( Colors.orange, kWhite, - 'Veuillez spécifier un nom pour la nouvelle section', + l.sectionNameRequired, context, null); } @@ -156,7 +157,7 @@ void create(SectionDTO sectionDTO, AppContext appContext, BuildContext context, } managerAppContext.selectedConfiguration.sectionIds.add(newSection.id);*/ appContext.setContext(managerAppContext); - showNotification(kSuccess, kWhite, 'La section a été créée avec succès !', + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.sectionCreatedSuccess, context, null); } else { sendSubSection!(newSection); diff --git a/lib/Screens/Kiosk_devices/change_device_info_modal.dart b/lib/Screens/Kiosk_devices/change_device_info_modal.dart index c6ed70d..d9b7b3a 100644 --- a/lib/Screens/Kiosk_devices/change_device_info_modal.dart +++ b/lib/Screens/Kiosk_devices/change_device_info_modal.dart @@ -9,6 +9,7 @@ import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import '../../Components/resource_input_container.dart'; @@ -73,7 +74,7 @@ showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appC ], ); } else { - return Text("Aucune configuration trouvée"); + return Text(AppLocalizations.of(context)!.noConfigFound); } } else if (snapshot.connectionState == ConnectionState.none) { @@ -104,7 +105,7 @@ showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appC }, ), ColorPickerInputContainer( - label: "Couleur fond d'écran :", + label: AppLocalizations.of(context)!.backgroundColorLabel, fontSize: 20, color: appConfiguration.secondaryColor, onChanged: (value) { diff --git a/lib/Screens/Kiosk_devices/device_element.dart b/lib/Screens/Kiosk_devices/device_element.dart index 4599702..b0b72dd 100644 --- a/lib/Screens/Kiosk_devices/device_element.dart +++ b/lib/Screens/Kiosk_devices/device_element.dart @@ -6,6 +6,7 @@ import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Kiosk_devices/change_device_info_modal.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; @@ -85,7 +86,7 @@ class _DeviceElementState extends State { icon: Icon(Icons.edit), onPressed: () { showChangeInfo( - "Mettre à jour la tablette", + AppLocalizations.of(context)!.updateTabletBtn, widget.deviceDTO, widget.appConfigurationLinkDTO, (DeviceDTO outputDevice, AppConfigurationLinkDTO outputAppConfig) async { @@ -120,7 +121,7 @@ class _DeviceElementState extends State { AppConfigurationLinkDTO? result = await managerAppContext.clientAPI!.applicationInstanceApi!.applicationInstanceUpdateApplicationLink(appConfigurationToUpdate); //print(device); - showNotification(kSuccess, kWhite, "Le kiosk a été mis à jour", context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.kioskUpdatedSuccess, context, null); return device; } diff --git a/lib/Screens/Kiosk_devices/kiosk_screen.dart b/lib/Screens/Kiosk_devices/kiosk_screen.dart index 0b9d566..17be9e1 100644 --- a/lib/Screens/Kiosk_devices/kiosk_screen.dart +++ b/lib/Screens/Kiosk_devices/kiosk_screen.dart @@ -1,5 +1,6 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart'; @@ -35,7 +36,7 @@ class _KioskScreenState extends State { child: Align( alignment: AlignmentDirectional.centerStart, child: AutoSizeText( - "Code pin: " + managerAppContext.pinCode.toString(), + AppLocalizations.of(context)!.pinCode(managerAppContext.pinCode.toString()), style: TextStyle(fontSize: 25.0, fontWeight: FontWeight.w300), maxLines: 2, maxFontSize: 25.0, @@ -49,7 +50,7 @@ class _KioskScreenState extends State { alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.all(8.0), - child: Text("Tablettes"), + child: Text(AppLocalizations.of(context)!.tablets), ) ), FutureBuilder( @@ -179,7 +180,7 @@ class _KioskScreenState extends State { icon: Icon(Icons.edit), onPressed: () { showSelectConfigModal( - "Sélectionner une configuration", + AppLocalizations.of(context)!.selectConfiguration, (String configurationId) { device.configurationId = configurationId; @@ -201,7 +202,7 @@ class _KioskScreenState extends State { ) : InkWell( onTap: () { showSelectConfigModal( - "Sélectionner une configuration", + AppLocalizations.of(context)!.selectConfiguration, (String configurationId) { device.configurationId = configurationId; @@ -226,7 +227,7 @@ class _KioskScreenState extends State { padding: const EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 15), child: Center( child: AutoSizeText( - "Sélectionner", + AppLocalizations.of(context)!.choose, style: TextStyle(color: kWhite), maxLines: 1, ) diff --git a/lib/Screens/Main/main_screen.dart b/lib/Screens/Main/main_screen.dart index 18cedcf..e3755a4 100644 --- a/lib/Screens/Main/main_screen.dart +++ b/lib/Screens/Main/main_screen.dart @@ -16,7 +16,9 @@ import 'package:manager_app/Screens/Statistics/statistics_screen.dart'; import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart'; import 'package:manager_app/Screens/Notifications/notifications_screen.dart'; import 'package:manager_app/Screens/Users/users_screen.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/app_context.dart'; +import 'package:manager_app/Components/quota_bars_widget.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_app/main.dart'; import 'package:manager_api_new/api.dart'; @@ -81,6 +83,100 @@ class _MainScreenState extends State { selectedElement = initElementToShow(context, widget.view, currentPosition.value!, menu, widget.instance); } + Future _showAssignPlanDialog(BuildContext context, ManagerAppContext managerCtx, Instance inst, {void Function()? onSaved}) async { + List? plans; + InstanceDTO? instanceDetail; + try { + plans = await managerCtx.clientAPI!.subscriptionPlanApi!.subscriptionPlanGet(); + instanceDetail = await managerCtx.clientAPI!.instanceApi!.instanceGetDetail(inst.id); + } catch (_) {} + + if (!context.mounted) return; + + final planList = plans ?? []; + String? selectedPlanId = instanceDetail?.subscriptionPlanId; + + showDialog( + context: context, + builder: (dialogContext) => StatefulBuilder( + builder: (dialogContext, setDialogState) => AlertDialog( + title: Text('Plan — ${inst.name}'), + content: SizedBox( + width: 360, + child: planList.isEmpty + ? Text(AppLocalizations.of(context)!.noPlansAvailable) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + RadioListTile( + title: Text(AppLocalizations.of(context)!.noPlan), + value: null, + groupValue: selectedPlanId, + onChanged: (v) => setDialogState(() => selectedPlanId = v), + ), + ...planList.map((plan) => RadioListTile( + title: Text(plan.name), + subtitle: Text( + '${_formatQuotaBytes(plan.storageQuotaBytes, unlimitedLabel: AppLocalizations.of(dialogContext)!.unlimitedStorage)} · ${plan.aiRequestsPerMonth == 0 ? AppLocalizations.of(dialogContext)!.unlimitedAI : AppLocalizations.of(dialogContext)!.aiRequestsPerMonth(plan.aiRequestsPerMonth ?? 0)}', + style: const TextStyle(fontSize: 11), + ), + value: plan.id, + groupValue: selectedPlanId, + onChanged: (v) => setDialogState(() => selectedPlanId = v), + )), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext, rootNavigator: true).pop(), + child: Text(AppLocalizations.of(dialogContext)!.cancel), + ), + TextButton( + onPressed: () async { + Navigator.of(dialogContext, rootNavigator: true).pop(); + try { + await managerCtx.clientAPI!.instanceApi!.instanceUpdateinstance( + InstanceDTO( + id: instanceDetail?.id, + name: instanceDetail?.name, + pinCode: instanceDetail?.pinCode, + isPushNotification: instanceDetail?.isPushNotification, + isStatistic: instanceDetail?.isStatistic, + isMobile: instanceDetail?.isMobile, + isTablet: instanceDetail?.isTablet, + isWeb: instanceDetail?.isWeb, + isVR: instanceDetail?.isVR, + isAssistant: instanceDetail?.isAssistant, + aiRequestsThisMonth: instanceDetail?.aiRequestsThisMonth, + aiUsageMonthKey: instanceDetail?.aiUsageMonthKey, + subscriptionPlanId: selectedPlanId ?? '', + ), + ); + onSaved?.call(); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.planUpdated))); + } + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.errorMessage(e.toString())))); + } + } + }, + child: Text(AppLocalizations.of(dialogContext)!.save), + ), + ], + ), + ), + ); + } + + static String _formatQuotaBytes(int? bytes, {String? unlimitedLabel}) { + if (bytes == null || bytes == 0) return unlimitedLabel ?? 'Stockage illimité'; + if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(0)} MB'; + return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB'; + } + Future _showSwitchInstanceDialog(BuildContext context, ManagerAppContext managerCtx) async { List? instances; try { @@ -94,11 +190,11 @@ class _MainScreenState extends State { showDialog( context: context, builder: (_) => AlertDialog( - title: const Text('Changer d\'instance'), + title: Text(AppLocalizations.of(context)!.switchInstance), content: SizedBox( width: 400, child: instanceList.isEmpty - ? const Text('Aucune instance trouvée') + ? Text(AppLocalizations.of(context)!.noInstanceFound) : ListView.builder( shrinkWrap: true, itemCount: instanceList.length, @@ -117,7 +213,19 @@ class _MainScreenState extends State { fontWeight: isCurrent ? FontWeight.bold : FontWeight.normal, ), ), - trailing: isCurrent ? const Icon(Icons.check, color: Colors.green) : null, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isCurrent) const Icon(Icons.check, color: Colors.green), + IconButton( + icon: const Icon(Icons.edit_outlined, size: 18), + tooltip: AppLocalizations.of(context)!.configurePlan, + onPressed: () => _showAssignPlanDialog(context, managerCtx, inst, onSaved: () { + Navigator.of(context, rootNavigator: true).pop(); + }), + ), + ], + ), onTap: isCurrent ? null : () async { @@ -141,12 +249,26 @@ class _MainScreenState extends State { ), ), actions: [ - TextButton(onPressed: () => Navigator.of(context, rootNavigator: true).pop(), child: const Text('Fermer')), + TextButton(onPressed: () => Navigator.of(context, rootNavigator: true).pop(), child: Text(AppLocalizations.of(context)!.close)), ], ), ); } + static String _localizedSectionName(BuildContext context, String type) { + final l = AppLocalizations.of(context)!; + switch (type) { + case 'devices': return l.menuApplications; + case 'configurations': return l.menuConfigurations; + case 'resources': return l.menuResources; + case 'statistics': return l.menuStatistics; + case 'notifications': return l.menuNotifications; + case 'users': return l.menuUsers; + case 'apikeys': return l.menuApiKeys; + default: return type; + } + } + static IconData _sectionIcon(String type) { switch (type) { case 'devices': return Icons.apps; @@ -228,7 +350,7 @@ class _MainScreenState extends State { color: currentPath.contains(section.type) ? kPrimaryColor : kBodyTextColor, size: 22, ), - title: Text(section.name, style: TextStyle(color: currentPath.contains(section.type) ? kPrimaryColor : kBodyTextColor, fontSize: 22, fontWeight: currentPath.contains(section.type) ? FontWeight.w500 : FontWeight.w100)), + title: Text(_localizedSectionName(context, section.type), style: TextStyle(color: currentPath.contains(section.type) ? kPrimaryColor : kBodyTextColor, fontSize: 22, fontWeight: currentPath.contains(section.type) ? FontWeight.w500 : FontWeight.w100)), selected: currentPosition == section.menuId, onTap: () { WidgetsBinding.instance.addPostFrameCallback((_) => @@ -252,7 +374,7 @@ class _MainScreenState extends State { ), iconColor: isAppActive ? kPrimaryColor : kBodyTextColor, collapsedIconColor: isAppActive ? kPrimaryColor : kBodyTextColor, - title: Text(section.name, style: TextStyle(color: isAppActive ? kPrimaryColor : kBodyTextColor, fontSize: 22, fontWeight: isAppActive ? FontWeight.w500 : FontWeight.w100)), + title: Text(_localizedSectionName(context, section.type), style: TextStyle(color: isAppActive ? kPrimaryColor : kBodyTextColor, fontSize: 22, fontWeight: isAppActive ? FontWeight.w500 : FontWeight.w100)), children: section.subMenu.map((subSection) { return Container( decoration: currentPath.contains(subSection.type) @@ -276,7 +398,7 @@ class _MainScreenState extends State { subSection.type == "vr" ? Icon(Icons.panorama_photosphere, color: currentPath.contains(subSection.type)? kPrimaryColor : kBodyTextColor, size: 20) : SizedBox(), Padding( padding: const EdgeInsets.all(8.0), - child: Text(subSection.name, style: TextStyle(color: currentPath.contains(subSection.type) ? kPrimaryColor : kBodyTextColor, fontSize: 18)), + child: Text(_localizedSectionName(context, subSection.type), style: TextStyle(color: currentPath.contains(subSection.type) ? kPrimaryColor : kBodyTextColor, fontSize: 18)), ) ], ), @@ -302,11 +424,26 @@ class _MainScreenState extends State { ), ), ), - // Footer: Email + Switch instance (SuperAdmin) + Logout + // Footer: Quota + Language + Email + Switch instance (SuperAdmin) + Logout Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ + const QuotaBarsWidget(), + DropdownButton( + value: managerAppContext.locale, + underline: const SizedBox(), + isDense: true, + items: const [ + DropdownMenuItem(value: Locale('fr'), child: Text('🇫🇷 FR')), + DropdownMenuItem(value: Locale('en'), child: Text('🇬🇧 EN')), + DropdownMenuItem(value: Locale('nl'), child: Text('🇧🇪 NL')), + ], + onChanged: (locale) { + if (locale != null) appContext.setLocale(locale); + }, + ), + const SizedBox(height: 4), AutoSizeText( (appContext.getContext() as ManagerAppContext).email ?? "", style: TextStyle(color: kBodyTextColor, fontSize: 16, fontWeight: FontWeight.w300, fontFamily: "Helvetica"), @@ -315,7 +452,7 @@ class _MainScreenState extends State { if ((appContext.getContext() as ManagerAppContext).role == UserRole.SuperAdmin) IconButton( icon: Icon(Icons.swap_horiz, color: kPrimaryColor), - tooltip: 'Changer d\'instance', + tooltip: AppLocalizations.of(context)!.tooltipSwitchInstance, onPressed: () => _showSwitchInstanceDialog(context, appContext.getContext() as ManagerAppContext), ), IconButton( diff --git a/lib/Screens/Notifications/notifications_screen.dart b/lib/Screens/Notifications/notifications_screen.dart index ba885ad..97527e9 100644 --- a/lib/Screens/Notifications/notifications_screen.dart +++ b/lib/Screens/Notifications/notifications_screen.dart @@ -1,8 +1,10 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; +import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; @@ -17,17 +19,15 @@ class NotificationsScreen extends StatefulWidget { class _NotificationsScreenState extends State { List _notifications = []; bool _loading = true; - String? _statusFilter; // null = toutes + String? _statusFilter; int _page = 0; static const _pageSize = 15; - // ─── KPI counts ─────────────────────────────────────────── int get _totalCount => _notifications.length; int get _sentCount => _notifications.where((n) => n.status == 'Sent').length; int get _scheduledCount => _notifications.where((n) => n.status == 'Scheduled').length; int get _failedCount => _notifications.where((n) => n.status == 'Failed').length; - // ─── Filtre + pagination ─────────────────────────────────── List get _filtered => _statusFilter == null ? _notifications : _notifications.where((n) => n.status == _statusFilter).toList(); @@ -43,7 +43,6 @@ class _NotificationsScreenState extends State { }); } - // ─── API helpers ────────────────────────────────────────── Future _load(ManagerAppContext ctx) async { try { final response = await ctx.clientAPI!.notificationApi!.notificationGetWithHttpInfo(); @@ -78,8 +77,8 @@ class _NotificationsScreenState extends State { return response.statusCode == 202; } - // ─── Dialogs ────────────────────────────────────────────── void _showSendModal(BuildContext context, ManagerAppContext ctx) { + final l = AppLocalizations.of(context)!; final titleCtrl = TextEditingController(); final bodyCtrl = TextEditingController(); bool isScheduled = false; @@ -90,18 +89,18 @@ class _NotificationsScreenState extends State { context: context, builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) { return AlertDialog( - title: const Text('Nouveau message'), + title: Text(l.newMessage), content: SizedBox( width: 480, child: Column(mainAxisSize: MainAxisSize.min, children: [ TextField( controller: titleCtrl, - decoration: const InputDecoration(labelText: 'Titre'), + decoration: InputDecoration(labelText: l.messageTitle), ), const SizedBox(height: 8), TextField( controller: bodyCtrl, - decoration: const InputDecoration(labelText: 'Message'), + decoration: InputDecoration(labelText: l.messageBody), maxLines: 3, ), const SizedBox(height: 16), @@ -111,7 +110,7 @@ class _NotificationsScreenState extends State { value: isScheduled, onChanged: (v) => setLocal(() => isScheduled = v ?? false), ), - const Text('Planifier'), + Text(l.schedule), ], ), if (isScheduled) ...[ @@ -122,7 +121,7 @@ class _NotificationsScreenState extends State { icon: const Icon(Icons.calendar_today, size: 16), label: Text(scheduledDate != null ? '${scheduledDate!.day.toString().padLeft(2, '0')}/${scheduledDate!.month.toString().padLeft(2, '0')}/${scheduledDate!.year}' - : 'Date'), + : l.date), onPressed: () async { final d = await showDatePicker( context: ctx2, @@ -140,7 +139,7 @@ class _NotificationsScreenState extends State { icon: const Icon(Icons.access_time, size: 16), label: Text(scheduledTime != null ? scheduledTime!.format(ctx2) - : 'Heure'), + : l.time), onPressed: () async { final t = await showTimePicker( context: ctx2, @@ -157,7 +156,7 @@ class _NotificationsScreenState extends State { actions: [ TextButton( onPressed: () => Navigator.pop(ctx2), - child: const Text('Annuler'), + child: Text(l.cancel), ), ElevatedButton( onPressed: titleCtrl.text.isEmpty ? null : () async { @@ -179,7 +178,7 @@ class _NotificationsScreenState extends State { await _load(ctx); } }, - child: const Text('Envoyer'), + child: Text(l.send), ), ], ); @@ -188,13 +187,14 @@ class _NotificationsScreenState extends State { } void _confirmCancel(BuildContext context, ManagerAppContext ctx, PushNotificationDTO notif) { + final l = AppLocalizations.of(context)!; showDialog( context: context, builder: (_) => AlertDialog( - title: const Text('Annuler la notification'), - content: Text('Annuler « ${notif.title} » ?'), + title: Text(l.cancelNotification), + content: Text(l.cancelNotificationConfirm(notif.title ?? '')), actions: [ - TextButton(onPressed: () => Navigator.pop(context), child: const Text('Non')), + TextButton(onPressed: () => Navigator.pop(context), child: Text(l.no)), ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () async { @@ -202,14 +202,13 @@ class _NotificationsScreenState extends State { final ok = await _cancel(ctx, notif.id!); if (ok && context.mounted) await _load(ctx); }, - child: const Text('Annuler', style: TextStyle(color: Colors.white)), + child: Text(l.cancel, style: const TextStyle(color: Colors.white)), ), ], ), ); } - // ─── Helpers ────────────────────────────────────────────── static String _formatDate(DateTime? dt) { if (dt == null) return '—'; final d = dt.toLocal(); @@ -245,7 +244,6 @@ class _NotificationsScreenState extends State { ); } - // ─── Build ──────────────────────────────────────────────── @override void initState() { super.initState(); @@ -257,32 +255,30 @@ class _NotificationsScreenState extends State { @override Widget build(BuildContext context) { + final l = AppLocalizations.of(context)!; final appContext = Provider.of(context); final ctx = appContext.getContext() as ManagerAppContext; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // ── Header ── Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Notifications', + Text(l.notificationsTitle, style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)), ElevatedButton.icon( onPressed: () => _showSendModal(context, ctx), icon: const Icon(Icons.add), - label: const Text('Nouveau message'), + label: Text(l.newMessage), ), ], ), const SizedBox(height: 16), - - // ── KPIs ── Row( children: [ _KpiCard( - label: 'Toutes', + label: l.allNotifications, count: _totalCount, color: Colors.blueGrey, selected: _statusFilter == null, @@ -290,7 +286,7 @@ class _NotificationsScreenState extends State { ), const SizedBox(width: 12), _KpiCard( - label: 'Envoyées', + label: l.sentNotifications, count: _sentCount, color: Colors.green, selected: _statusFilter == 'Sent', @@ -298,7 +294,7 @@ class _NotificationsScreenState extends State { ), const SizedBox(width: 12), _KpiCard( - label: 'Planifiées', + label: l.scheduledNotifications, count: _scheduledCount, color: Colors.blue, selected: _statusFilter == 'Scheduled', @@ -306,7 +302,7 @@ class _NotificationsScreenState extends State { ), const SizedBox(width: 12), _KpiCard( - label: 'Échouées', + label: l.failedNotifications, count: _failedCount, color: Colors.red, selected: _statusFilter == 'Failed', @@ -315,14 +311,12 @@ class _NotificationsScreenState extends State { ], ), const SizedBox(height: 16), - - // ── List ── if (_loading) - const Center(child: CircularProgressIndicator()) + const CommonLoader() else if (_notifications.isEmpty) - const Center(child: Text('Aucune notification envoyée')) + Center(child: Text(l.noNotifications)) else if (_filtered.isEmpty) - const Center(child: Text('Aucune notification pour ce filtre')) + Center(child: Text(l.noNotificationsForFilter)) else Expanded( child: Card( @@ -344,12 +338,12 @@ class _NotificationsScreenState extends State { columnSpacing: 24, headingRowColor: WidgetStateProperty.all(Colors.grey.shade50), dividerThickness: 1, - columns: const [ - DataColumn(label: Text('Titre', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Topic', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Date', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Statut', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('')), + columns: [ + DataColumn(label: Text(l.messageTitle, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.topic, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.date, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.status, style: const TextStyle(fontWeight: FontWeight.w600))), + const DataColumn(label: Text('')), ], rows: _paginated.map((n) { final isScheduled = n.status == 'Scheduled'; @@ -362,7 +356,7 @@ class _NotificationsScreenState extends State { DataCell(isScheduled ? IconButton( icon: const Icon(Icons.delete_outline, color: Colors.red, size: 20), - tooltip: 'Annuler', + tooltip: l.tooltipCancelNotification, onPressed: () => _confirmCancel(context, ctx, n), ) : const SizedBox()), @@ -372,7 +366,6 @@ class _NotificationsScreenState extends State { ), ), ), - // ── Pagination ── Container( decoration: BoxDecoration( border: Border(top: BorderSide(color: Colors.grey.shade200)), @@ -392,7 +385,7 @@ class _NotificationsScreenState extends State { ), const SizedBox(width: 16), Text( - '${_filtered.length} résultat${_filtered.length > 1 ? 's' : ''}', + l.resultsCount(_filtered.length), style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], diff --git a/lib/Screens/Policy/web_view.dart b/lib/Screens/Policy/web_view.dart index bb34ef6..9509162 100644 --- a/lib/Screens/Policy/web_view.dart +++ b/lib/Screens/Policy/web_view.dart @@ -3,6 +3,7 @@ import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; class WebView extends StatefulWidget { WebView({required this.htmlText}); @@ -63,5 +64,5 @@ class _WebViewWidget extends State { ) : //WebViewWidget(controller: controller!) Text("Texte"): - Center(child: Text("La page internet ne peut pas être affichée, l'url est incorrecte ou vide")); + Center(child: Text(AppLocalizations.of(context)!.webViewError)); } //_webView \ No newline at end of file diff --git a/lib/Screens/Resources/get_element_for_resource.dart b/lib/Screens/Resources/get_element_for_resource.dart index 46aed19..8695558 100644 --- a/lib/Screens/Resources/get_element_for_resource.dart +++ b/lib/Screens/Resources/get_element_for_resource.dart @@ -1,6 +1,7 @@ import 'package:manager_app/Components/audio_player.dart'; import 'package:manager_app/Components/video_viewer.dart'; +import 'package:manager_app/Components/video_viewer_youtube.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; @@ -87,14 +88,11 @@ getElementForResource(dynamic resourceDTO, AppContext appContext) { } case ResourceType.VideoUrl: - return SelectableText(resourceDTO.url!); - - /*case ResourceType.VideoUrl: if(resourceDTO.url == null) { return Center(child: Text("Error loading video")); } else { return VideoViewerYoutube(videoUrl: resourceDTO.url!); - }*/ // TODO + } case ResourceType.Pdf: return Text("Fichier pdf - aucune visualisation possible"); diff --git a/lib/Screens/Resources/new_resource_popup.dart b/lib/Screens/Resources/new_resource_popup.dart index 2565347..b39272b 100644 --- a/lib/Screens/Resources/new_resource_popup.dart +++ b/lib/Screens/Resources/new_resource_popup.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; dynamic showNewResource(AppContext appContext, BuildContext context) async { @@ -27,7 +28,7 @@ dynamic showNewResource(AppContext appContext, BuildContext context) async { child: SingleChildScrollView( child: Column( children: [ - Text("Nouvelle ressource", style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), + Text(AppLocalizations.of(context)!.newResource, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), /*Column( children: [ StringInputContainer( @@ -70,7 +71,7 @@ dynamic showNewResource(AppContext appContext, BuildContext context) async { width: 175, height: 70, child: RoundedButton( - text: "Annuler", + text: AppLocalizations.of(context)!.cancel, icon: Icons.undo, color: kSecond, press: () { @@ -86,7 +87,7 @@ dynamic showNewResource(AppContext appContext, BuildContext context) async { width: 150, height: 70, child: RoundedButton( - text: "Créer", + text: AppLocalizations.of(context)!.create, icon: Icons.check, color: kPrimaryColor, textColor: kWhite, @@ -96,13 +97,13 @@ dynamic showNewResource(AppContext appContext, BuildContext context) async { if(resourceDetailDTO.url != null || filesToSendWeb != null) { // TODO clarify resourceDetailDTO.data != null Navigator.pop(context, [resourceDetailDTO, filesToSend, filesToSendWeb]); } else { - showNotification(Colors.orange, kWhite, 'Aucun fichier n\'a été chargé', context, null); + showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.resourceNoFileLoaded, context, null); } } else { if (resourceDetailDTO.url != null || filesToSendWeb!.length > 0 || filesToSend!.length > 0) { // TODO clarify resourceDetailDTO.data != null Navigator.pop(context, [resourceDetailDTO, filesToSend, filesToSendWeb]); } else { - showNotification(Colors.orange, kWhite, 'Aucun fichier n\'a été chargé', context, null); + showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.resourceNoFileLoaded, context, null); } } /*} else { diff --git a/lib/Screens/Resources/resources_screen.dart b/lib/Screens/Resources/resources_screen.dart index a3d26be..21ee6ab 100644 --- a/lib/Screens/Resources/resources_screen.dart +++ b/lib/Screens/Resources/resources_screen.dart @@ -1,10 +1,13 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:firebase_storage/firebase_storage.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/message_notification.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Resources/new_resource_popup.dart'; import 'package:manager_app/Screens/Resources/resource_body_grid.dart'; @@ -103,7 +106,7 @@ class _ResourcesScreenState extends State { var resourceUpdated = managerAppContext.clientAPI!.resourceApi!.resourceUpdate(result); setState(() { // refresh ui - showNotification(kSuccess, kWhite, 'La ressource a été mise à jour avec succès', context, null); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.resourceUpdatedSuccess, context, null); }); } catch (e) { print("Error during updating resource"); @@ -117,10 +120,10 @@ class _ResourcesScreenState extends State { } });//bodyGrid(tempOutput, size, appContext); } else { - return Text("No data"); + return Text(AppLocalizations.of(context)!.noData); } } else if (snapshot.connectionState == ConnectionState.none) { - return Text("No data"); + return Text(AppLocalizations.of(context)!.noData); } else { return Center( child: Container( @@ -149,6 +152,24 @@ Future?> create(ResourceDTO resourceDTO, List? files, L int index = 0; if(filesWeb != null) { + // Vérification quota stockage avant upload + if ((managerAppContext.instanceDTO?.storageQuotaBytes ?? 0) > 0) { + try { + final quotaUri = Uri.parse('${managerAppContext.host}/api/Instance/${managerAppContext.instanceId}/quota'); + final quotaResponse = await http.get(quotaUri, headers: {'Authorization': 'Bearer ${managerAppContext.accessToken}'}); + if (quotaResponse.statusCode == 200) { + final quotaData = jsonDecode(quotaResponse.body); + final storageUsed = (quotaData['storageUsedBytes'] as num).toInt(); + final storageQuota = (quotaData['storageQuotaBytes'] as num).toInt(); + final incomingBytes = filesWeb.fold(0, (sum, f) => sum + (f.size)); + if (storageUsed + incomingBytes > storageQuota) { + showNotification(kError, kWhite, 'Quota de stockage dépassé', context, null); + return null; + } + } + } catch (_) {} + } + for (PlatformFile platformFile in filesWeb) { var mimeType = ""; ResourceDTO resourceDTO = new ResourceDTO(label: platformFile.name); @@ -200,8 +221,9 @@ Future?> create(ResourceDTO resourceDTO, List? files, L UploadTask uploadTask = ref.putData(platformFile.bytes!, metadata); uploadTask.then((res) { res.ref.getDownloadURL().then((urlRessource) { - showNotification(Colors.green, kWhite, 'La ressource a été créée avec succès', context, null); + showNotification(Colors.green, kWhite, AppLocalizations.of(context)!.resourceCreatedSuccess, context, null); newResource.url = urlRessource; + newResource.sizeBytes = platformFile.size; (appContext.getContext() as ManagerAppContext).clientAPI!.resourceApi!.resourceUpdate(newResource); createdResources.add(newResource); index++; diff --git a/lib/Screens/Resources/show_resource_popup.dart b/lib/Screens/Resources/show_resource_popup.dart index db89cf0..1556c59 100644 --- a/lib/Screens/Resources/show_resource_popup.dart +++ b/lib/Screens/Resources/show_resource_popup.dart @@ -7,6 +7,7 @@ import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_api_new/api.dart'; import 'dart:html' as html; @@ -110,7 +111,7 @@ Future showResource(ResourceDTO resourceDTO, AppContext appContext width: 220, height: 70, child: RoundedButton( - text: "Télécharger", + text: AppLocalizations.of(context)!.download, icon: Icons.download, color: kPrimaryColor, press: () { @@ -134,7 +135,7 @@ Future showResource(ResourceDTO resourceDTO, AppContext appContext width: 220, height: 70, child: RoundedButton( - text: "Sauvegarder", + text: AppLocalizations.of(context)!.save, icon: Icons.check, color: kPrimaryColor, press: () { @@ -157,7 +158,7 @@ Future showResource(ResourceDTO resourceDTO, AppContext appContext Future delete(ResourceDTO resourceDTO, AppContext appContext, context) async { showConfirmationDialog( - "Êtes-vous sûr de vouloir supprimer cette ressource ?", + AppLocalizations.of(context)!.resourceDeleteConfirm, () {}, () async { await (appContext.getContext() as ManagerAppContext).clientAPI!.resourceApi!.resourceDelete(resourceDTO.id!); diff --git a/lib/Screens/Statistics/statistics_screen.dart b/lib/Screens/Statistics/statistics_screen.dart index ccb4ba3..dc926fc 100644 --- a/lib/Screens/Statistics/statistics_screen.dart +++ b/lib/Screens/Statistics/statistics_screen.dart @@ -1,7 +1,9 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; +import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; @@ -55,20 +57,20 @@ class _StatisticsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildHeader(), + _buildHeader(context), const SizedBox(height: 16), Expanded( child: FutureBuilder( future: _future, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); + return const CommonLoader(); } if (snapshot.hasError || snapshot.data == null) { - return Center(child: Text('Impossible de charger les statistiques', style: TextStyle(color: kBodyTextColor))); + return Center(child: Text(AppLocalizations.of(context)!.statsLoadError, style: TextStyle(color: kBodyTextColor))); } final stats = snapshot.data!; - return _buildDashboard(stats); + return _buildDashboard(context, stats); }, ), ), @@ -77,23 +79,33 @@ class _StatisticsScreenState extends State { ); } - Widget _buildHeader() { + bool get _hasAdvancedStats => + _managerAppContext.instanceDTO?.hasAdvancedStats ?? false; + + int get _statsHistoryDays => + _managerAppContext.instanceDTO?.statsHistoryDays ?? 30; + + Widget _buildHeader(BuildContext context) { + final l = AppLocalizations.of(context)!; + final historyDays = _statsHistoryDays; + final maxDays = historyDays == 0 ? 999 : historyDays; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Statistiques', style: TextStyle(fontSize: 26, fontWeight: FontWeight.w600, color: kPrimaryColor)), + Text(l.statisticsTitle, style: TextStyle(fontSize: 26, fontWeight: FontWeight.w600, color: kPrimaryColor)), SegmentedButton( style: SegmentedButton.styleFrom( selectedBackgroundColor: kPrimaryColor, selectedForegroundColor: kWhite, ), - segments: const [ - ButtonSegment(value: 7, label: Text('7j')), - ButtonSegment(value: 30, label: Text('30j')), - ButtonSegment(value: 90, label: Text('90j')), + segments: [ + const ButtonSegment(value: 7, label: Text('7j')), + const ButtonSegment(value: 30, label: Text('30j')), + ButtonSegment(value: 90, label: Text('90j'), enabled: maxDays >= 90), ], selected: {_selectedDays}, onSelectionChanged: (s) { @@ -108,7 +120,7 @@ class _StatisticsScreenState extends State { spacing: 8, children: [ ChoiceChip( - label: const Text('Tous'), + label: Text(l.statsAll), selected: _selectedAppType == null, selectedColor: kPrimaryColor, labelStyle: TextStyle(color: _selectedAppType == null ? kWhite : kBodyTextColor), @@ -127,7 +139,8 @@ class _StatisticsScreenState extends State { ); } - Widget _buildDashboard(StatsSummaryDTO stats) { + Widget _buildDashboard(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; if (stats.totalSessions == 0) { return Center( child: Column( @@ -136,13 +149,13 @@ class _StatisticsScreenState extends State { Icon(Icons.bar_chart_outlined, size: 56, color: kBodyTextColor.withValues(alpha: 0.4)), const SizedBox(height: 16), Text( - 'Pas encore de données pour cette période', + l.statsNoData, style: TextStyle(fontSize: 15, color: kBodyTextColor), ), if (_selectedAppType != null) ...[ const SizedBox(height: 8), Text( - 'Aucun event reçu pour le type "${_selectedAppType!.name}"', + l.statsNoDataForType(_selectedAppType!.name), style: TextStyle(fontSize: 13, color: kBodyTextColor.withValues(alpha: 0.6)), ), ], @@ -155,23 +168,20 @@ class _StatisticsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // KPI cards - _buildKpiRow(stats), + _buildKpiRow(context, stats), const SizedBox(height: 20), - // Line chart - _buildLineChart(stats), + _buildLineChart(context, stats), const SizedBox(height: 20), - // Bar chart + Donut charts - _buildChartsRow(stats), + _buildChartsRow(context, stats), const SizedBox(height: 20), - // Bottom tables - _buildTablesRow(stats), + _buildTablesRow(context, stats), ], ), ); } - Widget _buildKpiRow(StatsSummaryDTO stats) { + Widget _buildKpiRow(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; final topAppType = stats.appTypeDistribution.isNotEmpty ? stats.appTypeDistribution.entries.reduce((a, b) => a.value > b.value ? a : b) : null; @@ -181,13 +191,13 @@ class _StatisticsScreenState extends State { return Row( children: [ - _kpiCard('Sessions', '${stats.totalSessions}', Icons.people_outline), + _kpiCard(l.statsSessions, '${stats.totalSessions}', Icons.people_outline), const SizedBox(width: 12), - _kpiCard('Durée moy.', _formatDuration(stats.avgVisitDurationSeconds), Icons.timer_outlined), + _kpiCard(l.statsAvgDuration, _formatDuration(stats.avgVisitDurationSeconds), Icons.timer_outlined), const SizedBox(width: 12), - _kpiCard('App top', topAppType?.key ?? '—', Icons.phone_iphone), + _kpiCard(l.statsTopApp, topAppType?.key ?? '—', Icons.phone_iphone), const SizedBox(width: 12), - _kpiCard('Langue top', topLang?.key.toUpperCase() ?? '—', Icons.language), + _kpiCard(l.statsTopLang, topLang?.key.toUpperCase() ?? '—', Icons.language), ], ); } @@ -215,7 +225,8 @@ class _StatisticsScreenState extends State { ); } - Widget _buildLineChart(StatsSummaryDTO stats) { + Widget _buildLineChart(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; if (stats.visitsByDay.isEmpty) return const SizedBox(); final spots = []; @@ -229,7 +240,9 @@ class _StatisticsScreenState extends State { tabletSpots.add(FlSpot(i.toDouble(), d.tablet.toDouble())); } - final isFiltered = _selectedAppType != null; + final mobileHasData = mobileSpots.any((s) => s.y > 0); + final tabletHasData = tabletSpots.any((s) => s.y > 0); + final showBreakdown = _selectedAppType == null && (mobileHasData || tabletHasData); return Card( elevation: 0, @@ -240,17 +253,17 @@ class _StatisticsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Visites par jour', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)), + Text(l.statsVisitsByDay, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)), const SizedBox(height: 4), - if (!isFiltered) Row(children: [ - _legendDot(kPrimaryColor), const SizedBox(width: 4), Text('Mobile', style: TextStyle(fontSize: 12, color: kBodyTextColor)), - const SizedBox(width: 12), - _legendDot(kSecond), const SizedBox(width: 4), Text('Tablet', style: TextStyle(fontSize: 12, color: kBodyTextColor)), + if (showBreakdown) Row(children: [ + if (mobileHasData) ...[_legendDot(kPrimaryColor), const SizedBox(width: 4), Text('Mobile', style: TextStyle(fontSize: 12, color: kBodyTextColor)), const SizedBox(width: 12)], + if (tabletHasData) ...[_legendDot(kSecond), const SizedBox(width: 4), Text('Tablet', style: TextStyle(fontSize: 12, color: kBodyTextColor))], ]), const SizedBox(height: 16), SizedBox( height: 200, child: LineChart(LineChartData( + minY: 0, gridData: FlGridData(show: true, drawVerticalLine: false), titlesData: FlTitlesData( leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: 32)), @@ -268,9 +281,12 @@ class _StatisticsScreenState extends State { )), ), borderData: FlBorderData(show: false), - lineBarsData: isFiltered - ? [_lineBar(spots, kPrimaryColor)] - : [_lineBar(mobileSpots, kPrimaryColor), _lineBar(tabletSpots, kSecond)], + lineBarsData: showBreakdown + ? [ + if (mobileHasData) _lineBar(mobileSpots, kPrimaryColor), + if (tabletHasData) _lineBar(tabletSpots, kSecond), + ] + : [_lineBar(spots, kPrimaryColor)], )), ), ], @@ -289,28 +305,30 @@ class _StatisticsScreenState extends State { isCurved: true, color: color, barWidth: 2, - dotData: FlDotData(show: false), - belowBarData: BarAreaData(show: true, color: color.withOpacity(0.1)), + dotData: FlDotData(show: spots.length <= 1), + belowBarData: BarAreaData(show: true, color: color.withValues(alpha: 0.1)), ); } - Widget _buildChartsRow(StatsSummaryDTO stats) { + Widget _buildChartsRow(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; final showAppTypeDonut = _selectedAppType == null; return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded(flex: 2, child: _buildTopSectionsChart(stats)), + Expanded(flex: 2, child: _buildTopSectionsChart(context, stats)), const SizedBox(width: 12), if (showAppTypeDonut) ...[ - Expanded(child: _buildDonut(stats.appTypeDistribution, 'Apps')), + Expanded(child: _buildDonut(stats.appTypeDistribution, l.statsApps)), const SizedBox(width: 12), ], - Expanded(child: _buildDonut(stats.languageDistribution, 'Langues')), + Expanded(child: _buildDonut(stats.languageDistribution, l.statsLanguages)), ], ); } - Widget _buildTopSectionsChart(StatsSummaryDTO stats) { + Widget _buildTopSectionsChart(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; if (stats.topSections.isEmpty) return const SizedBox(); final sections = stats.topSections.take(8).toList(); final maxViews = sections.map((s) => s.views).reduce((a, b) => a > b ? a : b); @@ -324,7 +342,7 @@ class _StatisticsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Top sections', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)), + Text(l.statsTopSections, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)), const SizedBox(height: 16), SizedBox( height: 240, @@ -341,7 +359,8 @@ class _StatisticsScreenState extends State { getTitlesWidget: (value, meta) { final idx = value.toInt(); if (idx < 0 || idx >= sections.length) return const SizedBox(); - final title = sections[idx].sectionTitle ?? sections[idx].sectionId ?? ''; + final raw = sections[idx].sectionTitle ?? sections[idx].sectionId ?? ''; + final title = raw.replaceAll(RegExp(r'<[^>]*>'), '').trim(); return Padding( padding: const EdgeInsets.only(top: 4), child: Text( @@ -430,15 +449,19 @@ class _StatisticsScreenState extends State { ); } - Widget _buildTablesRow(StatsSummaryDTO stats) { + Widget _buildTablesRow(BuildContext context, StatsSummaryDTO stats) { + if (!_hasAdvancedStats) { + return _buildPremiumLockedCard(context); + } + final tables = [ - if (stats.topPois.isNotEmpty) Expanded(child: _buildPoiTable(stats)), - if (stats.topAgendaEvents.isNotEmpty) Expanded(child: _buildAgendaTable(stats)), - if (stats.quizStats.isNotEmpty) Expanded(child: _buildQuizTable(stats)), - if (stats.gameStats.isNotEmpty) Expanded(child: _buildGameTable(stats)), - if (stats.topArticles.isNotEmpty) Expanded(child: _buildArticleTable(stats)), - if (stats.topMenuItems.isNotEmpty) Expanded(child: _buildMenuTable(stats)), - if (stats.qrScans.totalScans > 0) Expanded(child: _buildQrCard(stats)), + if (stats.topPois.isNotEmpty) Expanded(child: _buildPoiTable(context, stats)), + if (stats.topAgendaEvents.isNotEmpty) Expanded(child: _buildAgendaTable(context, stats)), + if (stats.quizStats.isNotEmpty) Expanded(child: _buildQuizTable(context, stats)), + if (stats.gameStats.isNotEmpty) Expanded(child: _buildGameTable(context, stats)), + if (stats.topArticles.isNotEmpty) Expanded(child: _buildArticleTable(context, stats)), + if (stats.topMenuItems.isNotEmpty) Expanded(child: _buildMenuTable(context, stats)), + if (stats.qrScans.totalScans > 0) Expanded(child: _buildQrCard(context, stats)), ]; if (tables.isEmpty) return const SizedBox(); final spaced = []; @@ -449,51 +472,88 @@ class _StatisticsScreenState extends State { return Row(crossAxisAlignment: CrossAxisAlignment.start, children: spaced); } - Widget _buildPoiTable(StatsSummaryDTO stats) { - return _tableCard('Top POI', ['POI', 'Taps'], stats.topPois.map((p) => [ + Widget _buildPremiumLockedCard(BuildContext context) { + return Card( + elevation: 0, + color: kWhite, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(24), + child: Row( + children: [ + Icon(Icons.lock_outline, color: kBodyTextColor.withValues(alpha: 0.5), size: 28), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Statistiques avancées', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: kPrimaryColor)), + const SizedBox(height: 4), + Text( + 'Les stats détaillées (POI, quiz, jeux, articles, QR…) sont disponibles avec le plan Premium.', + style: TextStyle(fontSize: 13, color: kBodyTextColor), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildPoiTable(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; + return _tableCard(l.statsTopPOI, [l.statsPOI, l.statsTaps], stats.topPois.map((p) => [ p.title ?? p.geoPointId?.toString() ?? '—', '${p.taps}', ]).toList()); } - Widget _buildAgendaTable(StatsSummaryDTO stats) { - return _tableCard('Top événements agenda', ['Événement', 'Taps'], stats.topAgendaEvents.map((e) => [ + Widget _buildAgendaTable(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; + return _tableCard(l.statsTopAgenda, [l.statsEvent, l.statsTaps], stats.topAgendaEvents.map((e) => [ e.eventTitle ?? e.eventId ?? '—', '${e.taps}', ]).toList()); } - Widget _buildQuizTable(StatsSummaryDTO stats) { - return _tableCard('Quiz', ['Section', 'Score moy', 'Complétions'], stats.quizStats.map((q) => [ + Widget _buildQuizTable(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; + return _tableCard(l.statsQuiz, [l.statsSection, l.statsAvgScore, l.statsCompletions], stats.quizStats.map((q) => [ q.sectionTitle ?? q.sectionId ?? '—', '${q.avgScore.toStringAsFixed(1)} / ${q.totalQuestions}', '${q.completions}', ]).toList()); } - Widget _buildGameTable(StatsSummaryDTO stats) { - return _tableCard('Jeux', ['Type', 'Complétions', 'Durée moy.'], stats.gameStats.map((g) => [ + Widget _buildGameTable(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; + return _tableCard(l.statsGames, [l.statsGameType, l.statsCompletions, l.statsAvgDuration], stats.gameStats.map((g) => [ g.gameType ?? '—', '${g.completions}', _formatDuration(g.avgDurationSeconds), ]).toList()); } - Widget _buildArticleTable(StatsSummaryDTO stats) { - return _tableCard('Articles lus', ['Section', 'Lectures'], stats.topArticles.map((a) => [ + Widget _buildArticleTable(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; + return _tableCard(l.statsArticles, [l.statsSection, l.statsReadings], stats.topArticles.map((a) => [ a.sectionId ?? '—', '${a.reads}', ]).toList()); } - Widget _buildMenuTable(StatsSummaryDTO stats) { - return _tableCard('Menu', ['Item', 'Taps'], stats.topMenuItems.map((m) => [ + Widget _buildMenuTable(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; + return _tableCard(l.statsMenuTitle, [l.statsMenuItem, l.statsTaps], stats.topMenuItems.map((m) => [ m.menuItemTitle ?? m.targetSectionId ?? '—', '${m.taps}', ]).toList()); } - Widget _buildQrCard(StatsSummaryDTO stats) { + Widget _buildQrCard(BuildContext context, StatsSummaryDTO stats) { + final l = AppLocalizations.of(context)!; final qr = stats.qrScans; return Card( elevation: 0, @@ -504,13 +564,13 @@ class _StatisticsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('QR Scans', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)), + Text(l.statsQrScans, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)), const SizedBox(height: 12), - _qrRow(Icons.qr_code_scanner, 'Total', '${qr.totalScans}', kPrimaryColor), + _qrRow(Icons.qr_code_scanner, l.statsTotal, '${qr.totalScans}', kPrimaryColor), const SizedBox(height: 8), - _qrRow(Icons.check_circle_outline, 'Valides', '${qr.validScans}', Colors.green.shade600), + _qrRow(Icons.check_circle_outline, l.statsValid, '${qr.validScans}', Colors.green.shade600), const SizedBox(height: 8), - _qrRow(Icons.cancel_outlined, 'Invalides', '${qr.invalidScans}', Colors.red.shade400), + _qrRow(Icons.cancel_outlined, l.statsInvalid, '${qr.invalidScans}', Colors.red.shade400), ], ), ), diff --git a/lib/Screens/Users/users_screen.dart b/lib/Screens/Users/users_screen.dart index c5e2c1c..9d0f26a 100644 --- a/lib/Screens/Users/users_screen.dart +++ b/lib/Screens/Users/users_screen.dart @@ -1,8 +1,10 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; +import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/constants.dart'; import 'package:provider/provider.dart'; @@ -26,7 +28,6 @@ class _UsersScreenState extends State { return '—'; } - // Convertit une valeur de rôle (String ou int) en int pour les comparaisons static int _roleToInt(dynamic v) { if (v is int) return v; if (v is String) { @@ -36,7 +37,6 @@ class _UsersScreenState extends State { return 3; } - // Roles the caller is allowed to assign (can't assign higher than own role) List _allowedRoles(int callerRoleValue) => [0, 1, 2, 3].where((r) => r >= callerRoleValue).toList(); @@ -88,29 +88,30 @@ class _UsersScreenState extends State { } void _showCreateDialog(BuildContext context, ManagerAppContext ctx) { + final l = AppLocalizations.of(context)!; final callerRole = _roleToInt(ctx.role?.value); final emailCtrl = TextEditingController(); final firstCtrl = TextEditingController(); final lastCtrl = TextEditingController(); final passCtrl = TextEditingController(); - int selectedRole = callerRole; // default: same as caller + int selectedRole = callerRole; showDialog( context: context, builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) { return AlertDialog( - title: const Text('Créer un utilisateur'), + title: Text(l.createUserTitle), content: SingleChildScrollView( child: Column(mainAxisSize: MainAxisSize.min, children: [ - TextField(controller: emailCtrl, decoration: const InputDecoration(labelText: 'Email')), - TextField(controller: firstCtrl, decoration: const InputDecoration(labelText: 'Prénom')), - TextField(controller: lastCtrl, decoration: const InputDecoration(labelText: 'Nom')), - TextField(controller: passCtrl, obscureText: true, decoration: const InputDecoration(labelText: 'Mot de passe')), + TextField(controller: emailCtrl, decoration: InputDecoration(labelText: l.email)), + TextField(controller: firstCtrl, decoration: InputDecoration(labelText: l.firstName)), + TextField(controller: lastCtrl, decoration: InputDecoration(labelText: l.lastName)), + TextField(controller: passCtrl, obscureText: true, decoration: InputDecoration(labelText: l.password)), const SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Rôle', style: TextStyle(fontSize: 12, color: Colors.grey)), + Text(l.role, style: const TextStyle(fontSize: 12, color: Colors.grey)), DropdownButton( value: selectedRole, isExpanded: true, @@ -124,14 +125,14 @@ class _UsersScreenState extends State { ]), ), actions: [ - TextButton(onPressed: () => Navigator.pop(ctx2), child: const Text('Annuler')), + TextButton(onPressed: () => Navigator.pop(ctx2), child: Text(l.cancel)), ElevatedButton( onPressed: () async { Navigator.pop(ctx2); await _createUser(ctx, emailCtrl.text, firstCtrl.text, lastCtrl.text, passCtrl.text, selectedRole); }, - child: const Text('Créer'), + child: Text(l.create), ), ], ); @@ -140,6 +141,7 @@ class _UsersScreenState extends State { } void _showEditDialog(BuildContext context, ManagerAppContext ctx, Map user) { + final l = AppLocalizations.of(context)!; final callerRole = _roleToInt(ctx.role?.value); final firstCtrl = TextEditingController(text: user['firstName'] as String? ?? ''); final lastCtrl = TextEditingController(text: user['lastName'] as String? ?? ''); @@ -149,16 +151,16 @@ class _UsersScreenState extends State { context: context, builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) { return AlertDialog( - title: const Text('Modifier l\'utilisateur'), + title: Text(l.editUserTitle), content: SingleChildScrollView( child: Column(mainAxisSize: MainAxisSize.min, children: [ - TextField(controller: firstCtrl, decoration: const InputDecoration(labelText: 'Prénom')), - TextField(controller: lastCtrl, decoration: const InputDecoration(labelText: 'Nom')), + TextField(controller: firstCtrl, decoration: InputDecoration(labelText: l.firstName)), + TextField(controller: lastCtrl, decoration: InputDecoration(labelText: l.lastName)), const SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Rôle', style: TextStyle(fontSize: 12, color: Colors.grey)), + Text(l.role, style: const TextStyle(fontSize: 12, color: Colors.grey)), DropdownButton( value: selectedRole, isExpanded: true, @@ -172,14 +174,14 @@ class _UsersScreenState extends State { ]), ), actions: [ - TextButton(onPressed: () => Navigator.pop(ctx2), child: const Text('Annuler')), + TextButton(onPressed: () => Navigator.pop(ctx2), child: Text(l.cancel)), ElevatedButton( onPressed: () async { Navigator.pop(ctx2); await _updateUser(ctx, user['id'] as String, firstCtrl.text, lastCtrl.text, selectedRole); }, - child: const Text('Enregistrer'), + child: Text(l.save), ), ], ); @@ -188,20 +190,21 @@ class _UsersScreenState extends State { } void _confirmDelete(BuildContext context, ManagerAppContext ctx, Map user) { + final l = AppLocalizations.of(context)!; showDialog( context: context, builder: (_) => AlertDialog( - title: const Text('Supprimer l\'utilisateur'), - content: Text('Supprimer ${user['email']} ?'), + title: Text(l.deleteUserTitle), + content: Text(l.deleteUserConfirm(user['email'] as String? ?? '')), actions: [ - TextButton(onPressed: () => Navigator.pop(context), child: const Text('Annuler')), + TextButton(onPressed: () => Navigator.pop(context), child: Text(l.cancel)), ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () async { Navigator.pop(context); await _deleteUser(ctx, user['id'] as String); }, - child: const Text('Supprimer', style: TextStyle(color: Colors.white)), + child: Text(l.delete, style: const TextStyle(color: Colors.white)), ), ], ), @@ -228,6 +231,7 @@ class _UsersScreenState extends State { @override Widget build(BuildContext context) { + final l = AppLocalizations.of(context)!; final appContext = Provider.of(context); final managerCtx = appContext.getContext() as ManagerAppContext; @@ -237,19 +241,19 @@ class _UsersScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Utilisateurs', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)), + Text(l.usersTitle, style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)), ElevatedButton.icon( onPressed: () => _showCreateDialog(context, managerCtx), icon: const Icon(Icons.add), - label: const Text('Créer un utilisateur'), + label: Text(l.createUserBtn), ), ], ), const SizedBox(height: 16), if (_loading) - const Center(child: CircularProgressIndicator()) + const CommonLoader() else if (_users.isEmpty) - const Center(child: Text('Aucun utilisateur')) + Center(child: Text(l.noUsers)) else Expanded( child: Card( @@ -268,12 +272,12 @@ class _UsersScreenState extends State { columnSpacing: 24, headingRowColor: WidgetStateProperty.all(Colors.grey.shade50), dividerThickness: 1, - columns: const [ - DataColumn(label: Text('Email', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Prénom', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Nom', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Rôle', style: TextStyle(fontWeight: FontWeight.w600))), - DataColumn(label: Text('Actions', style: TextStyle(fontWeight: FontWeight.w600))), + columns: [ + DataColumn(label: Text(l.email, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.firstName, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.lastName, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.role, style: const TextStyle(fontWeight: FontWeight.w600))), + DataColumn(label: Text(l.actions, style: const TextStyle(fontWeight: FontWeight.w600))), ], rows: _users.map((user) { final roleColor = _roleColor(user['role']); @@ -294,12 +298,12 @@ class _UsersScreenState extends State { DataCell(Row(children: [ IconButton( icon: Icon(Icons.edit, color: kPrimaryColor, size: 20), - tooltip: 'Modifier', + tooltip: l.tooltipEdit, onPressed: () => _showEditDialog(context, managerCtx, user), ), IconButton( icon: const Icon(Icons.delete_outline, color: Colors.red, size: 20), - tooltip: 'Supprimer', + tooltip: l.tooltipDelete, onPressed: () => _confirmDelete(context, managerCtx, user), ), ])), diff --git a/lib/Screens/login_screen.dart b/lib/Screens/login_screen.dart index 75819e0..d40b43d 100644 --- a/lib/Screens/login_screen.dart +++ b/lib/Screens/login_screen.dart @@ -10,6 +10,7 @@ import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_input_field.dart'; import 'package:manager_app/Components/rounded_password_field.dart'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Helpers/FileHelper.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/session.dart'; @@ -88,7 +89,7 @@ class _LoginScreenState extends State { userRole = token.role; showNotification( - kSuccess, kWhite, 'Connexion réussie', context, null); + kSuccess, kWhite, AppLocalizations.of(context)!.loginSuccess, context, null); if (isRememberMe) { if (!localStorage.containsKey("remember")) { @@ -171,7 +172,7 @@ class _LoginScreenState extends State { (Route route) => false // For pushAndRemoveUntil );*/ } else { - showNotification(Colors.orange, kWhite, 'Un problème est survenu lors de la connexion', context, null); + showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.loginError, context, null); setState(() { isLoading = false; }); @@ -181,7 +182,7 @@ class _LoginScreenState extends State { print("error auth"); print(e); if(fromClick) { - showNotification(Colors.orange, kWhite, 'Un problème est survenu lors de la connexion', context, null); + showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.loginError, context, null); setState(() { isLoading = false; }); @@ -393,13 +394,13 @@ class _LoginScreenState extends State { }, ), ), - Text("Se souvenir de moi", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),), + Text(AppLocalizations.of(context)!.rememberMe, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),), ], ), ), SizedBox(height: size.height * 0.015), !isLoading ? RoundedButton( - text: "SE CONNECTER", + text: AppLocalizations.of(context)!.connect, fontSize: 16, vertical: 15, horizontal: 30, diff --git a/lib/Services/ai_translate_service.dart b/lib/Services/ai_translate_service.dart new file mode 100644 index 0000000..5478656 --- /dev/null +++ b/lib/Services/ai_translate_service.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; + +class AiTranslateService { + static Future> translate({ + required String host, + required String accessToken, + required String instanceId, + required String text, + required String sourceLang, + required List targetLangs, + }) async { + final uri = Uri.parse('$host/api/Ai/translate?instanceId=$instanceId'); + final response = await http.post( + uri, + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $accessToken', + }, + body: jsonEncode({ + 'text': text, + 'sourceLang': sourceLang, + 'targetLangs': targetLangs, + }), + ); + + if (response.statusCode != 200) { + throw Exception('Erreur traduction IA : ${response.statusCode}'); + } + + final data = jsonDecode(response.body); + final translations = data['translations'] as Map; + return translations.map((k, v) => MapEntry(k, v.toString())); + } +} diff --git a/lib/app_context.dart b/lib/app_context.dart index 6f5b303..a103ae3 100644 --- a/lib/app_context.dart +++ b/lib/app_context.dart @@ -19,4 +19,9 @@ class AppContext with ChangeNotifier { notifyListeners(); } + + void setLocale(Locale locale) { + _managerContext?.locale = locale; + notifyListeners(); + } } diff --git a/lib/client.dart b/lib/client.dart index 87c4022..d81c563 100644 --- a/lib/client.dart +++ b/lib/client.dart @@ -50,6 +50,9 @@ class Client { NotificationApi? _notificationApi; NotificationApi? get notificationApi => _notificationApi; + SubscriptionPlanApi? _subscriptionPlanApi; + SubscriptionPlanApi? get subscriptionPlanApi => _subscriptionPlanApi; + Client(String path) { _apiClient = ApiClient(basePath: path); //basePath: "https://192.168.31.140"); @@ -70,5 +73,6 @@ class Client { _statsApi = StatsApi(_apiClient); _apiKeyApi = ApiKeyApi(_apiClient); _notificationApi = NotificationApi(_apiClient); + _subscriptionPlanApi = SubscriptionPlanApi(_apiClient); } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 0000000..1904094 --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,461 @@ +{ + "@@locale": "en", + + "cancel": "Cancel", + "save": "Save", + "create": "Create", + "delete": "Delete", + "close": "Close", + "edit": "Edit", + "actions": "Actions", + "status": "Status", + "no": "No", + "name": "Name", + "type": "Type", + "date": "Date", + + "loginSuccess": "Login successful", + "loginError": "An error occurred during login", + "rememberMe": "Remember me", + "connect": "LOG IN", + + "menuApplications": "Applications", + "menuConfigurations": "Configurations", + "menuResources": "Resources", + "menuStatistics": "Statistics", + "menuNotifications": "Notifications", + "menuUsers": "Users", + "menuApiKeys": "API Keys", + + "noPlansAvailable": "No plans available. Create a plan first.", + "noPlan": "No plan (unlimited)", + "unlimitedStorage": "Unlimited storage", + "unlimitedAI": "Unlimited AI", + "aiRequestsPerMonth": "{count} AI req/month", + "@aiRequestsPerMonth": { + "placeholders": { + "count": { "type": "int" } + } + }, + "storageLabel": "Storage", + "aiThisMonthLabel": "AI this month", + "unlimited": "Unlimited", + "requestsAbbr": "req", + "planUpdated": "Plan updated", + "errorMessage": "Error: {error}", + "@errorMessage": { + "placeholders": { + "error": { "type": "String" } + } + }, + "switchInstance": "Switch instance", + "noInstanceFound": "No instance found", + "configurePlan": "Configure plan", + "tooltipSwitchInstance": "Switch instance", + + "usersTitle": "Users", + "createUserBtn": "Create user", + "createUserTitle": "Create user", + "editUserTitle": "Edit user", + "deleteUserTitle": "Delete user", + "deleteUserConfirm": "Delete {email}?", + "@deleteUserConfirm": { + "placeholders": { + "email": { "type": "String" } + } + }, + "noUsers": "No users", + "email": "Email", + "firstName": "First name", + "lastName": "Last name", + "password": "Password", + "role": "Role", + "tooltipEdit": "Edit", + "tooltipDelete": "Delete", + + "notificationsTitle": "Notifications", + "newMessage": "New message", + "messageTitle": "Title", + "messageBody": "Message", + "schedule": "Schedule", + "time": "Time", + "send": "Send", + "cancelNotification": "Cancel notification", + "cancelNotificationConfirm": "Cancel « {title} »?", + "@cancelNotificationConfirm": { + "placeholders": { + "title": { "type": "String" } + } + }, + "allNotifications": "All", + "sentNotifications": "Sent", + "scheduledNotifications": "Scheduled", + "failedNotifications": "Failed", + "noNotifications": "No notifications sent", + "noNotificationsForFilter": "No notifications for this filter", + "topic": "Topic", + "tooltipCancelNotification": "Cancel", + "resultsCount": "{count, plural, one{{count} result} other{{count} results}}", + "@resultsCount": { + "placeholders": { + "count": { "type": "int" } + } + }, + + "apiKeysTitle": "API Keys", + "createApiKey": "Create API key", + "createKeyBtn": "Create key", + "appType": "Application type", + "noExpiration": "No expiration", + "expiresOn": "Expires on {date}", + "@expiresOn": { + "placeholders": { + "date": { "type": "String" } + } + }, + "choose": "Choose", + "removeExpiration": "Remove expiration", + "apiKeyCreatedTitle": "API key created", + "copyKeyWarning": "Copy this key now — it won't be shown again.", + "copy": "Copy", + "copiedKey": "I've copied the key", + "revokeApiKeyTitle": "Revoke API key", + "revokeApiKeyConfirm": "Revoke « {name} »? Apps using this key will lose access.", + "@revokeApiKeyConfirm": { + "placeholders": { + "name": { "type": "String" } + } + }, + "revoke": "Revoke", + "noApiKeys": "No API keys", + "createdOn": "Created on", + "expiration": "Expiration", + "activeKey": "Active", + "revokedKey": "Revoked", + "tooltipRevoke": "Revoke", + + "statisticsTitle": "Statistics", + "statsLoadError": "Unable to load statistics", + "statsNoData": "No data yet for this period", + "statsNoDataForType": "No events received for type \"{type}\"", + "@statsNoDataForType": { + "placeholders": { + "type": { "type": "String" } + } + }, + "statsSessions": "Sessions", + "statsAvgDuration": "Avg. duration", + "statsTopApp": "Top app", + "statsTopLang": "Top language", + "statsVisitsByDay": "Visits per day", + "statsAll": "All", + "statsTopSections": "Top sections", + "statsApps": "Apps", + "statsLanguages": "Languages", + "statsTopPOI": "Top POI", + "statsPOI": "POI", + "statsTaps": "Taps", + "statsTopAgenda": "Top agenda events", + "statsEvent": "Event", + "statsQuiz": "Quiz", + "statsSection": "Section", + "statsAvgScore": "Avg. score", + "statsCompletions": "Completions", + "statsGames": "Games", + "statsGameType": "Type", + "statsArticles": "Articles read", + "statsReadings": "Reads", + "statsMenuTitle": "Menu", + "statsMenuItem": "Item", + "statsQrScans": "QR Scans", + "statsTotal": "Total", + "statsValid": "Valid", + "statsInvalid": "Invalid", + "statsViews": "Views", + + "noData": "No data", + "errorOccurred": "An error occurred", + "yes": "Yes", + + "newConfiguration": "New configuration", + "configNameLabel": "Name:", + "orText": "or", + "importLabel": "Import", + "configNameRequired": "Please specify a name for the new visit", + "configCreatedSuccess": "Configuration created successfully", + "configExportSuccess": "Configuration exported successfully, file is at: {path}", + "@configExportSuccess": { + "placeholders": { + "path": { "type": "String" } + } + }, + "configExportFailed": "Configuration export failed", + "configDeletedSuccess": "Configuration deleted successfully", + "configSavedSuccess": "Configuration saved successfully", + "configDeleteConfirm": "Are you sure you want to delete this configuration?", + + "newSection": "New section", + "newSubSection": "New sub section", + "sectionNameLabel": "Name:", + "sectionTypeLabel": "Type:", + "sectionNameRequired": "Please specify a name for the new section", + "sectionCreatedSuccess": "Section created successfully!", + "sectionDeleteConfirm": "Are you sure you want to delete this section?", + "sectionSavedSuccess": "Section saved successfully", + "sectionTranslationSaved": "Section translations saved successfully", + "sectionLoadError": "An error occurred while loading the section", + "qrCodeCopied": "This QR code has been copied to the clipboard", + "beaconLabel": "Beacon:", + "beaconIdLabel": "Beacon ID:", + "beaconMustBeNumber": "This must be a number", + "identifierLabel": "Identifier:", + "displayTitleLabel": "Display title:", + "imageLabel": "Image:", + "backgroundImageLabel": "Background image:", + "questionInputLabel": "Question:", + "videoUrlLabel": "Video URL:", + "webUrlLabel": "Website URL:", + + "subSectionUpdatedSuccess": "Sub section updated successfully", + "subSectionUpdateError": "An error occurred while updating the sub section", + "subSectionDeletedSuccess": "Sub section deleted successfully", + "subSectionDeleteError": "An error occurred while deleting the sub section", + "subSectionOrderUpdatedSuccess": "Sub section order updated successfully", + "subSectionOrderUpdateError": "An error occurred while updating the sub section order", + "subSectionCreatedSuccess": "Sub section created successfully", + "subSectionCreateError": "An error occurred while creating the sub section", + + "questionsLabel": "Questions", + "questionLabel": "Question", + "editQuestion": "Edit question", + "questionDeleteConfirm": "Are you sure you want to delete this question?", + "questionsLoadError": "An error occurred while loading the questions", + "quizBadScore": "Bad score", + "quizMediumScore": "Medium score", + "quizGoodScore": "Good score", + "quizExcellentScore": "Excellent score", + "quizBadScoreMsg": "Message for a bad score", + "quizMediumScoreMsg": "Message for a medium score", + "quizGoodScoreMsg": "Message for a good score", + "quizExcellentScoreMsg": "Message for an excellent score", + "questionOrderUpdatedSuccess": "Question order updated successfully", + "questionOrderUpdateError": "An error occurred while updating the question order", + "questionCreatedSuccess": "Question created successfully", + "questionCreateError": "An error occurred while creating the question", + "questionUpdatedSuccess": "Question updated successfully", + "questionUpdateError": "An error occurred while updating the question", + "questionDeletedSuccess": "Question deleted successfully", + "questionDeleteError": "An error occurred while deleting the question", + "translationIncomplete": "Translation is incomplete", + + "geopointCreatedSuccess": "Point created successfully", + "geopointCreateError": "An error occurred while creating the point", + "geopointUpdatedSuccess": "Point updated successfully", + "geopointUpdateError": "An error occurred while updating the point", + "geopointDeletedSuccess": "Point deleted successfully", + "geopointDeleteError": "An error occurred while deleting the point", + "categoryCreatedSuccess": "Category created successfully", + "categoryCreateError": "An error occurred while creating the category", + "categoryUpdatedSuccess": "Category updated successfully", + "categoryUpdateError": "An error occurred while updating the category", + + "eventCreatedSuccess": "Event created successfully", + "eventCreateError": "An error occurred while creating the event", + "eventUpdatedSuccess": "Event updated successfully", + "eventUpdateError": "An error occurred while updating the event", + "eventDeletedSuccess": "Event deleted successfully", + "eventDeleteError": "An error occurred while deleting the event", + "annotationCreatedSuccess": "Annotation created successfully", + "annotationCreateError": "An error occurred while creating the annotation", + "annotationUpdatedSuccess": "Annotation updated successfully", + "annotationUpdateError": "An error occurred while updating the annotation", + "programmeBlockCreatedSuccess": "Block created successfully", + "programmeBlockCreateError": "An error occurred while creating the block", + "programmeBlockUpdatedSuccess": "Block updated successfully", + "programmeBlockUpdateError": "An error occurred while updating the block", + + "pathCreatedSuccess": "Path created successfully", + "pathCreateError": "An error occurred while creating the path", + "pathUpdatedSuccess": "Path updated successfully", + "pathUpdateError": "An error occurred while updating the path", + "stepCreatedSuccess": "Step created successfully", + "stepCreateError": "An error occurred while creating the step", + "stepUpdatedSuccess": "Step updated successfully", + "stepUpdateError": "An error occurred while updating the step", + + "agendaEventsLabel": "Events", + "agendaEventFallback": "Event", + "addEvent": "Add an event", + "noEvents": "No events", + "noAddress": "No address", + "onlineLabel": "Online:", + "mapViewLabel": "Map view:", + "mapServiceLabel": "Map service:", + "jsonFilesLabel": "JSON files:", + "jsonLabel": "JSON", + + "guidedPathsLabel": "Guided Paths", + "addPath": "Add a path", + "noPathConfigured": "No path configured", + "pathOrderUpdateError": "Error updating order", + "pathNoTitle": "Untitled path", + "pathDeletedSuccess": "Path deleted successfully", + "pathDeleteError": "Error deleting path", + "pathsLabel": "Paths", + + "pointsOfInterestLabel": "Points of Interest", + "geopointsLabel": "Geographic points", + "searchLabel": "Search:", + "geopointsLoadError": "Error loading geographic points", + "geopointDeleteConfirm": "Are you sure you want to delete this geographic point?", + "serviceLabel": "Service:", + "centerPointLabel": "Center point:", + "iconLabel": "Icon:", + "listViewLabel": "List view:", + "typeLabel": "Type:", + "zoomLabel": "Zoom:", + "categoriesLabel": "Categories:", + + "startDateLabel": "Start date", + "notDefined": "Not defined", + "endDateLabel": "End date", + "programmeLabel": "Programme", + "addBlock": "Add a block", + "blockFallback": "Block", + "noBlocks": "No programme blocks defined", + "programmeBlockDeletedSuccess": "Block deleted successfully", + "programmeBlockDeleteError": "Error deleting block", + "baseMapLabel": "Base map", + "mapLabel": "Map:", + "globalAnnotationsLabel": "Global annotations", + "addAnnotation": "Add an annotation", + "noAnnotations": "No global annotations", + "annotationFallback": "Annotation", + "annotationDeletedSuccess": "Annotation deleted", + "annotationDeleteError": "Error deleting", + + "agendaEventCreatedSuccess": "Event created successfully", + "agendaEventCreateError": "An error occurred while creating the event", + "agendaEventUpdatedSuccess": "Event updated successfully", + "agendaEventUpdateError": "An error occurred while updating the event", + "agendaEventDeletedSuccess": "Event deleted successfully", + "agendaEventDeleteError": "An error occurred while deleting the event", + + "newResource": "New resource", + "resourceCreatedSuccess": "Resource created successfully", + "resourceCreateError": "An error occurred while creating the resource", + "resourceUpdatedSuccess": "Resource updated successfully", + "resourceNoFileLoaded": "No file has been loaded", + "resourceNameRequired": "Please give the resource a name", + "resourceTooLarge": "Error: resource size must be under 1.5MB", + "resourceInvalidUrl": "The URL is invalid", + "resourceUrlRequired": "Please fill in the URL field", + + "generalInfo": "General information", + "mainImageLabel": "Main image:", + "loaderLabel": "Loader:", + "primaryColorLabel": "Primary color:", + "secondaryColorLabel": "Secondary color:", + "layoutLabel": "Layout:", + "layoutGrid": "Grid", + "languagesLabel": "Languages:", + "featuredEventLabel": "Featured event:", + "chooseEvent": "Choose an event", + "noneOption": "None", + "aiAssistantLabel": "AI assistant:", + "appUpdatedSuccess": "Mobile application updated", + "configActivatedSuccess": "Configuration activated successfully", + "configDeactivatedSuccess": "Configuration deactivated successfully", + "configRemoveConfirm": "Are you sure you want to remove this configuration from the application?", + "configRemovedSuccess": "Configuration removed from application successfully", + "configRemoveError": "An error occurred while removing the configuration", + "phoneConfigTitle": "Configurations per device", + "addConfig": "Add a configuration", + "selectConfigToAdd": "Select a configuration to add", + "noItems": "No items to display", + "add": "Add", + + "pinCode": "Pin code: {code}", + "@pinCode": { + "placeholders": { + "code": { "type": "String" } + } + }, + "tablets": "Tablets", + + "stepsCount": "{count} steps", + "@stepsCount": { + "placeholders": { + "count": { "type": "int" } + } + }, + "displayedDescriptionLabel": "Displayed description:", + "displayedContentLabel": "Displayed content:", + "displayedNameLabel": "Display name:", + "startTimeLabel": "Start time", + "noAnnotationConfigured": "No annotations configured.", + "newStepTitle": "New step", + "editStepTitle": "Edit step", + "stepTitleLabel": "Step title", + "stepDescriptionLabel": "Step description", + "stepLocationLabel": "Step location:", + "initiallyHiddenLabel": "Initially hidden", + "lockedLabel": "Locked", + "durationSecondsLabel": "Duration (seconds):", + "triggerGeopointLabel": "Trigger GeoPoint (ID):", + "questionsChallengesLabel": "Questions / Challenges", + "noQuestionsConfigured": "No questions configured.", + "newAgendaEventTitle": "New event", + "editAgendaEventTitle": "Edit event", + "eventTitleLabel": "Event title", + "eventDescriptionLabel": "Event description", + "startDateColonLabel": "Start date:", + "videoResourceLabel": "Video:", + "directVideoLinkLabel": "Direct video link:", + "phoneLabel": "Phone:", + "locationGeometryLabel": "Location / Geometry", + "geometryLabel": "Geometry:", + "materialIconLabel": "Icon (material):", + "linearLabel": "Linear:", + "requiredSuccessLabel": "Required success:", + "pathStepsLabel": "Path steps", + "noStepConfigured": "No point/step configured.", + "stepFallback": "Step", + "questionAskedLabel": "Question asked:", + "questionTitleLabel": "Question title", + "expectedAnswerLabel": "Expected answer:", + "expectedAnswerModalLabel": "Expected answer", + "validationNote": "Validation is done by comparison (case-insensitive).", + "possibleAnswersLabel": "Possible answers:", + "noAnswerDefined": "No answer defined. Add at least one.", + "correctAnswer": "Correct answer ✓", + "wrongAnswer": "Wrong answer", + "answerNote": "✓ = correct answer. Multiple can be correct.", + "geopointZoneTitle": "Geographic point / Zone", + "geometryTypeLabel": "Geometry (Point/Line/Zone):", + "phoneModalLabel": "Phone", + "categoryLabel": "Category:", + "startMessageLabel": "Start message:", + "startMessageModalLabel": "Start message", + "createCategoryTitle": "Create category", + "editCategoryTitle": "Edit category", + "categoryIconLabel": "Category icon:", + "selectResource": "Select a resource", + "createPdfTitle": "Create PDF", + "answersLabel": "Answers", + "answerLabel": "Answer", + "createAnswerTitle": "Create answer", + "editAnswerTitle": "Edit answer", + "answerValidNote": "If checked, the answer is valid", + "selectConfiguration": "Select a configuration", + "noConfigFound": "No configuration found", + "backgroundColorLabel": "Background color:", + "updateTabletBtn": "Update tablet", + "kioskUpdatedSuccess": "Kiosk updated", + "webViewError": "The web page cannot be displayed, the URL is incorrect or empty", + "download": "Download", + "resourceDeleteConfirm": "Are you sure you want to delete this resource?", + "categoriesTitle": "Categories", + "endTimeLabel": "End time", + "annotationsLabel": "Annotations" +} diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb new file mode 100644 index 0000000..fb36b46 --- /dev/null +++ b/lib/l10n/app_fr.arb @@ -0,0 +1,461 @@ +{ + "@@locale": "fr", + + "cancel": "Annuler", + "save": "Enregistrer", + "create": "Créer", + "delete": "Supprimer", + "close": "Fermer", + "edit": "Modifier", + "actions": "Actions", + "status": "Statut", + "no": "Non", + "name": "Nom", + "type": "Type", + "date": "Date", + + "loginSuccess": "Connexion réussie", + "loginError": "Un problème est survenu lors de la connexion", + "rememberMe": "Se souvenir de moi", + "connect": "SE CONNECTER", + + "menuApplications": "Applications", + "menuConfigurations": "Configurations", + "menuResources": "Ressources", + "menuStatistics": "Statistiques", + "menuNotifications": "Notifications", + "menuUsers": "Utilisateurs", + "menuApiKeys": "Clés API", + + "noPlansAvailable": "Aucun plan disponible. Créez d'abord un plan.", + "noPlan": "Aucun plan (illimité)", + "unlimitedStorage": "Stockage illimité", + "unlimitedAI": "IA illimitée", + "aiRequestsPerMonth": "{count} req IA/mois", + "@aiRequestsPerMonth": { + "placeholders": { + "count": { "type": "int" } + } + }, + "storageLabel": "Stockage", + "aiThisMonthLabel": "IA ce mois", + "unlimited": "Illimité", + "requestsAbbr": "req", + "planUpdated": "Plan mis à jour", + "errorMessage": "Erreur : {error}", + "@errorMessage": { + "placeholders": { + "error": { "type": "String" } + } + }, + "switchInstance": "Changer d'instance", + "noInstanceFound": "Aucune instance trouvée", + "configurePlan": "Configurer le plan", + "tooltipSwitchInstance": "Changer d'instance", + + "usersTitle": "Utilisateurs", + "createUserBtn": "Créer un utilisateur", + "createUserTitle": "Créer un utilisateur", + "editUserTitle": "Modifier l'utilisateur", + "deleteUserTitle": "Supprimer l'utilisateur", + "deleteUserConfirm": "Supprimer {email} ?", + "@deleteUserConfirm": { + "placeholders": { + "email": { "type": "String" } + } + }, + "noUsers": "Aucun utilisateur", + "email": "Email", + "firstName": "Prénom", + "lastName": "Nom", + "password": "Mot de passe", + "role": "Rôle", + "tooltipEdit": "Modifier", + "tooltipDelete": "Supprimer", + + "notificationsTitle": "Notifications", + "newMessage": "Nouveau message", + "messageTitle": "Titre", + "messageBody": "Message", + "schedule": "Planifier", + "time": "Heure", + "send": "Envoyer", + "cancelNotification": "Annuler la notification", + "cancelNotificationConfirm": "Annuler « {title} » ?", + "@cancelNotificationConfirm": { + "placeholders": { + "title": { "type": "String" } + } + }, + "allNotifications": "Toutes", + "sentNotifications": "Envoyées", + "scheduledNotifications": "Planifiées", + "failedNotifications": "Échouées", + "noNotifications": "Aucune notification envoyée", + "noNotificationsForFilter": "Aucune notification pour ce filtre", + "topic": "Topic", + "tooltipCancelNotification": "Annuler", + "resultsCount": "{count, plural, one{{count} résultat} other{{count} résultats}}", + "@resultsCount": { + "placeholders": { + "count": { "type": "int" } + } + }, + + "apiKeysTitle": "Clés API", + "createApiKey": "Créer une clé API", + "createKeyBtn": "Créer une clé", + "appType": "Type d'application", + "noExpiration": "Pas d'expiration", + "expiresOn": "Expire le {date}", + "@expiresOn": { + "placeholders": { + "date": { "type": "String" } + } + }, + "choose": "Choisir", + "removeExpiration": "Supprimer l'expiration", + "apiKeyCreatedTitle": "Clé API créée", + "copyKeyWarning": "Copiez cette clé maintenant — elle ne sera plus affichée.", + "copy": "Copier", + "copiedKey": "J'ai copié la clé", + "revokeApiKeyTitle": "Révoquer la clé API", + "revokeApiKeyConfirm": "Révoquer « {name} » ? Les apps utilisant cette clé perdront l'accès.", + "@revokeApiKeyConfirm": { + "placeholders": { + "name": { "type": "String" } + } + }, + "revoke": "Révoquer", + "noApiKeys": "Aucune clé API", + "createdOn": "Créée le", + "expiration": "Expiration", + "activeKey": "Active", + "revokedKey": "Révoquée", + "tooltipRevoke": "Révoquer", + + "statisticsTitle": "Statistiques", + "statsLoadError": "Impossible de charger les statistiques", + "statsNoData": "Pas encore de données pour cette période", + "statsNoDataForType": "Aucun event reçu pour le type \"{type}\"", + "@statsNoDataForType": { + "placeholders": { + "type": { "type": "String" } + } + }, + "statsSessions": "Sessions", + "statsAvgDuration": "Durée moy.", + "statsTopApp": "App top", + "statsTopLang": "Langue top", + "statsVisitsByDay": "Visites par jour", + "statsAll": "Tous", + "statsTopSections": "Top sections", + "statsApps": "Apps", + "statsLanguages": "Langues", + "statsTopPOI": "Top POI", + "statsPOI": "POI", + "statsTaps": "Taps", + "statsTopAgenda": "Top événements agenda", + "statsEvent": "Événement", + "statsQuiz": "Quiz", + "statsSection": "Section", + "statsAvgScore": "Score moy", + "statsCompletions": "Complétions", + "statsGames": "Jeux", + "statsGameType": "Type", + "statsArticles": "Articles lus", + "statsReadings": "Lectures", + "statsMenuTitle": "Menu", + "statsMenuItem": "Item", + "statsQrScans": "QR Scans", + "statsTotal": "Total", + "statsValid": "Valides", + "statsInvalid": "Invalides", + "statsViews": "Vues", + + "noData": "Aucune donnée", + "errorOccurred": "Une erreur est survenue", + "yes": "Oui", + + "newConfiguration": "Nouvelle configuration", + "configNameLabel": "Nom :", + "orText": "ou", + "importLabel": "Importer", + "configNameRequired": "Veuillez spécifier un nom pour la nouvelle visite", + "configCreatedSuccess": "La configuration a été créée avec succès", + "configExportSuccess": "L'export de la configuration a réussi, le document se trouve à cet endroit : {path}", + "@configExportSuccess": { + "placeholders": { + "path": { "type": "String" } + } + }, + "configExportFailed": "L'export de la configuration a échoué", + "configDeletedSuccess": "La configuration a été supprimée avec succès", + "configSavedSuccess": "La configuration a été sauvegardée avec succès", + "configDeleteConfirm": "Êtes-vous sûr de vouloir supprimer cette configuration ?", + + "newSection": "Nouvelle section", + "newSubSection": "Nouvelle sous section", + "sectionNameLabel": "Nom :", + "sectionTypeLabel": "Type:", + "sectionNameRequired": "Veuillez spécifier un nom pour la nouvelle section", + "sectionCreatedSuccess": "La section a été créée avec succès !", + "sectionDeleteConfirm": "Êtes-vous sûr de vouloir supprimer cette section ?", + "sectionSavedSuccess": "La section a été sauvegardée avec succès", + "sectionTranslationSaved": "Les traductions de la section ont été sauvegardées avec succès", + "sectionLoadError": "Une erreur est survenue lors de la récupération de la section", + "qrCodeCopied": "Ce QR code a été copié dans le presse papier", + "beaconLabel": "Beacon :", + "beaconIdLabel": "Identifiant Beacon :", + "beaconMustBeNumber": "Cela doit être un chiffre", + "identifierLabel": "Identifiant :", + "displayTitleLabel": "Titre affiché:", + "imageLabel": "Image :", + "backgroundImageLabel": "Image fond d'écran :", + "questionInputLabel": "Question :", + "videoUrlLabel": "Url de la vidéo:", + "webUrlLabel": "Url du site web:", + + "subSectionUpdatedSuccess": "La sous section a été mis à jour avec succès", + "subSectionUpdateError": "Une erreur est survenue lors de la mise à jour de la sous section", + "subSectionDeletedSuccess": "La sous section a été supprimée avec succès", + "subSectionDeleteError": "Une erreur est survenue lors de la suppression de la sous section", + "subSectionOrderUpdatedSuccess": "L'ordre des sous sections a été mis à jour avec succès", + "subSectionOrderUpdateError": "Une erreur est survenue lors de la mise à jour de l'ordre des sous sections", + "subSectionCreatedSuccess": "La sous section a été créée avec succès", + "subSectionCreateError": "Une erreur est survenue lors de la création de la sous section", + + "questionsLabel": "Questions", + "questionLabel": "Question", + "editQuestion": "Modifier la question", + "questionDeleteConfirm": "Êtes-vous sûr de vouloir supprimer cette question ?", + "questionsLoadError": "Une erreur est survenue lors de la récupération des questions", + "quizBadScore": "Mauvais score", + "quizMediumScore": "Moyen score", + "quizGoodScore": "Bon score", + "quizExcellentScore": "Excellent score", + "quizBadScoreMsg": "Message pour un mauvais score", + "quizMediumScoreMsg": "Message pour un score moyen", + "quizGoodScoreMsg": "Message pour un bon score", + "quizExcellentScoreMsg": "Message pour un excellent score", + "questionOrderUpdatedSuccess": "L'ordre des questions a été mis à jour avec succès", + "questionOrderUpdateError": "Une erreur est survenue lors de la mise à jour de l'ordre des questions", + "questionCreatedSuccess": "La question a été créée avec succès", + "questionCreateError": "Une erreur est survenue lors de la création de la question", + "questionUpdatedSuccess": "La question a été mis à jour avec succès", + "questionUpdateError": "Une erreur est survenue lors de la mise à jour de la question", + "questionDeletedSuccess": "La question a été supprimée avec succès", + "questionDeleteError": "Une erreur est survenue lors de la suppression de la question", + "translationIncomplete": "La traduction n'est pas complète", + + "geopointCreatedSuccess": "Le point a été créé avec succès", + "geopointCreateError": "Une erreur est survenue lors de la création du point", + "geopointUpdatedSuccess": "Le point a été mis à jour avec succès", + "geopointUpdateError": "Une erreur est survenue lors de la mise à jour du point", + "geopointDeletedSuccess": "Le point a été supprimé avec succès", + "geopointDeleteError": "Une erreur est survenue lors de la suppression du point", + "categoryCreatedSuccess": "La catégorie a été créée avec succès", + "categoryCreateError": "Une erreur est survenue lors de la création de la catégorie", + "categoryUpdatedSuccess": "La catégorie a été mise à jour avec succès", + "categoryUpdateError": "Une erreur est survenue lors de la mise à jour de la catégorie", + + "eventCreatedSuccess": "L'événement a été créé avec succès", + "eventCreateError": "Une erreur est survenue lors de la création de l'événement", + "eventUpdatedSuccess": "L'événement a été mis à jour avec succès", + "eventUpdateError": "Une erreur est survenue lors de la mise à jour de l'événement", + "eventDeletedSuccess": "L'événement a été supprimé avec succès", + "eventDeleteError": "Une erreur est survenue lors de la suppression de l'événement", + "annotationCreatedSuccess": "L'annotation a été créée avec succès", + "annotationCreateError": "Une erreur est survenue lors de la création de l'annotation", + "annotationUpdatedSuccess": "L'annotation a été mise à jour avec succès", + "annotationUpdateError": "Une erreur est survenue lors de la mise à jour de l'annotation", + "programmeBlockCreatedSuccess": "Le bloc a été créé avec succès", + "programmeBlockCreateError": "Une erreur est survenue lors de la création du bloc", + "programmeBlockUpdatedSuccess": "Le bloc a été mis à jour avec succès", + "programmeBlockUpdateError": "Une erreur est survenue lors de la mise à jour du bloc", + + "pathCreatedSuccess": "Le parcours a été créé avec succès", + "pathCreateError": "Une erreur est survenue lors de la création du parcours", + "pathUpdatedSuccess": "Le parcours a été mis à jour avec succès", + "pathUpdateError": "Une erreur est survenue lors de la mise à jour du parcours", + "stepCreatedSuccess": "L'étape a été créée avec succès", + "stepCreateError": "Une erreur est survenue lors de la création de l'étape", + "stepUpdatedSuccess": "L'étape a été mise à jour avec succès", + "stepUpdateError": "Une erreur est survenue lors de la mise à jour de l'étape", + + "agendaEventsLabel": "Évènements", + "agendaEventFallback": "Évènement", + "addEvent": "Ajouter un évènement", + "noEvents": "Aucun évènement", + "noAddress": "Pas d'adresse", + "onlineLabel": "En ligne :", + "mapViewLabel": "Vue carte :", + "mapServiceLabel": "Service carte :", + "jsonFilesLabel": "Fichiers json :", + "jsonLabel": "JSON", + + "guidedPathsLabel": "Parcours Guidés", + "addPath": "Ajouter un parcours", + "noPathConfigured": "Aucun parcours configuré", + "pathOrderUpdateError": "Erreur lors de la mise à jour de l'ordre", + "pathNoTitle": "Parcours sans titre", + "pathDeletedSuccess": "Parcours supprimé avec succès", + "pathDeleteError": "Erreur lors de la suppression du parcours", + "pathsLabel": "Parcours", + + "pointsOfInterestLabel": "Points d'Intérêt", + "geopointsLabel": "Points géographiques", + "searchLabel": "Recherche :", + "geopointsLoadError": "Une erreur est survenue lors de la récupération des points géographiques", + "geopointDeleteConfirm": "Êtes-vous sûr de vouloir supprimer ce point géographique ?", + "serviceLabel": "Service :", + "centerPointLabel": "Point de centrage :", + "iconLabel": "Icône :", + "listViewLabel": "Vue liste :", + "typeLabel": "Type :", + "zoomLabel": "Zoom :", + "categoriesLabel": "Catégories :", + + "startDateLabel": "Date de début", + "notDefined": "Non définie", + "endDateLabel": "Date de fin", + "programmeLabel": "Programme", + "addBlock": "Ajouter un bloc", + "blockFallback": "Bloc", + "noBlocks": "Aucun bloc de programme défini", + "programmeBlockDeletedSuccess": "Bloc supprimé avec succès", + "programmeBlockDeleteError": "Erreur lors de la suppression du bloc", + "baseMapLabel": "Carte de base", + "mapLabel": "Carte :", + "globalAnnotationsLabel": "Annotations globales", + "addAnnotation": "Ajouter une annotation", + "noAnnotations": "Aucune annotation globale", + "annotationFallback": "Annotation", + "annotationDeletedSuccess": "Annotation supprimée", + "annotationDeleteError": "Erreur lors de la suppression", + + "agendaEventCreatedSuccess": "L'événement a été créé avec succès", + "agendaEventCreateError": "Une erreur est survenue lors de la création de l'événement", + "agendaEventUpdatedSuccess": "L'événement a été mis à jour avec succès", + "agendaEventUpdateError": "Une erreur est survenue lors de la mise à jour de l'événement", + "agendaEventDeletedSuccess": "L'événement a été supprimé avec succès", + "agendaEventDeleteError": "Une erreur est survenue lors de la suppression de l'événement", + + "newResource": "Nouvelle ressource", + "resourceCreatedSuccess": "La ressource a été créée avec succès", + "resourceCreateError": "Une erreur est survenue lors de la création de la ressource", + "resourceUpdatedSuccess": "La ressource a été mise à jour avec succès", + "resourceNoFileLoaded": "Aucun fichier n'a été chargé", + "resourceNameRequired": "Veuillez donner un nom à la ressource", + "resourceTooLarge": "Erreur, attention, la taille de la ressource doit être inférieure à 1.5Mb", + "resourceInvalidUrl": "L'url est invalide", + "resourceUrlRequired": "Veuillez remplir le champ URL", + + "generalInfo": "Informations générales", + "mainImageLabel": "Image principale :", + "loaderLabel": "Loader :", + "primaryColorLabel": "Couleur principale :", + "secondaryColorLabel": "Couleur secondaire :", + "layoutLabel": "Affichage :", + "layoutGrid": "Grille", + "languagesLabel": "Langues :", + "featuredEventLabel": "Evènement à l'affiche :", + "chooseEvent": "Choisir un évènement", + "noneOption": "Aucun", + "aiAssistantLabel": "Assistant IA :", + "appUpdatedSuccess": "Application mobile mise à jour", + "configActivatedSuccess": "Configuration activée avec succès", + "configDeactivatedSuccess": "Configuration désactivée avec succès", + "configRemoveConfirm": "Êtes-vous sûr de vouloir retirer cette configuration de l'application ?", + "configRemovedSuccess": "La configuration a été retirée de l'application avec succès", + "configRemoveError": "Une erreur est survenue lors du retrait de la configuration", + "phoneConfigTitle": "Configurations par appareil", + "addConfig": "Ajouter une configuration", + "selectConfigToAdd": "Sélectionner une configuration à ajouter", + "noItems": "Aucun élément à afficher", + "add": "Ajouter", + + "pinCode": "Code pin: {code}", + "@pinCode": { + "placeholders": { + "code": { "type": "String" } + } + }, + "tablets": "Tablettes", + + "stepsCount": "{count} étapes", + "@stepsCount": { + "placeholders": { + "count": { "type": "int" } + } + }, + "displayedDescriptionLabel": "Description affichée:", + "displayedContentLabel": "Contenu affiché :", + "displayedNameLabel": "Nom affiché :", + "startTimeLabel": "Heure de début", + "noAnnotationConfigured": "Aucune annotation configurée.", + "newStepTitle": "Nouvelle Étape", + "editStepTitle": "Modifier l'Étape", + "stepTitleLabel": "Titre de l'étape", + "stepDescriptionLabel": "Description de l'étape", + "stepLocationLabel": "Emplacement de l'étape :", + "initiallyHiddenLabel": "Cachée initialement", + "lockedLabel": "Verrouillée", + "durationSecondsLabel": "Durée (secondes) :", + "triggerGeopointLabel": "GeoPoint de déclenchement (ID) :", + "questionsChallengesLabel": "Questions / Défis", + "noQuestionsConfigured": "Aucune question configurée.", + "newAgendaEventTitle": "Nouvel Évènement", + "editAgendaEventTitle": "Modifier l'Évènement", + "eventTitleLabel": "Titre de l'évènement", + "eventDescriptionLabel": "Description de l'évènement", + "startDateColonLabel": "Date de début :", + "videoResourceLabel": "Vidéo :", + "directVideoLinkLabel": "Lien vidéo direct :", + "phoneLabel": "Téléphone :", + "locationGeometryLabel": "Localisation / Géométrie", + "geometryLabel": "Géométrie :", + "materialIconLabel": "Icône (material) :", + "linearLabel": "Linéaire :", + "requiredSuccessLabel": "Réussite requise :", + "pathStepsLabel": "Étapes du parcours", + "noStepConfigured": "Aucun point/étape configuré.", + "stepFallback": "Étape", + "questionAskedLabel": "Question posée :", + "questionTitleLabel": "Intitulé de la question", + "expectedAnswerLabel": "Réponse attendue :", + "expectedAnswerModalLabel": "Réponse attendue", + "validationNote": "La validation se fait par comparaison (insensible à la casse).", + "possibleAnswersLabel": "Réponses possibles :", + "noAnswerDefined": "Aucune réponse définie. Ajoutez-en au moins une.", + "correctAnswer": "Bonne réponse ✓", + "wrongAnswer": "Mauvaise réponse", + "answerNote": "✓ = bonne réponse. Plusieurs peuvent être correctes.", + "geopointZoneTitle": "Point géographique / Zone", + "geometryTypeLabel": "Géométrie (Point/Ligne/Zone) :", + "phoneModalLabel": "Téléphone", + "categoryLabel": "Catégorie :", + "startMessageLabel": "Message départ :", + "startMessageModalLabel": "Message départ", + "createCategoryTitle": "Création catégorie", + "editCategoryTitle": "Modification catégorie", + "categoryIconLabel": "Icône catégorie :", + "selectResource": "Sélectionner une ressource", + "createPdfTitle": "Création PDF", + "answersLabel": "Réponses", + "answerLabel": "Réponse", + "createAnswerTitle": "Créer la réponse", + "editAnswerTitle": "Modifier la réponse", + "answerValidNote": "Si coché, la réponse est valide", + "selectConfiguration": "Sélectionner une configuration", + "noConfigFound": "Aucune configuration trouvée", + "backgroundColorLabel": "Couleur fond d'écran :", + "updateTabletBtn": "Mettre à jour la tablette", + "kioskUpdatedSuccess": "Le kiosk a été mis à jour", + "webViewError": "La page internet ne peut pas être affichée, l'url est incorrecte ou vide", + "download": "Télécharger", + "resourceDeleteConfirm": "Êtes-vous sûr de vouloir supprimer cette ressource ?", + "categoriesTitle": "Catégories", + "endTimeLabel": "Heure de fin", + "annotationsLabel": "Annotations" +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 0000000..bcc1f96 --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,2405 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; +import 'app_localizations_fr.dart'; +import 'app_localizations_nl.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en'), + Locale('fr'), + Locale('nl') + ]; + + /// No description provided for @cancel. + /// + /// In fr, this message translates to: + /// **'Annuler'** + String get cancel; + + /// No description provided for @save. + /// + /// In fr, this message translates to: + /// **'Enregistrer'** + String get save; + + /// No description provided for @create. + /// + /// In fr, this message translates to: + /// **'Créer'** + String get create; + + /// No description provided for @delete. + /// + /// In fr, this message translates to: + /// **'Supprimer'** + String get delete; + + /// No description provided for @close. + /// + /// In fr, this message translates to: + /// **'Fermer'** + String get close; + + /// No description provided for @edit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get edit; + + /// No description provided for @actions. + /// + /// In fr, this message translates to: + /// **'Actions'** + String get actions; + + /// No description provided for @status. + /// + /// In fr, this message translates to: + /// **'Statut'** + String get status; + + /// No description provided for @no. + /// + /// In fr, this message translates to: + /// **'Non'** + String get no; + + /// No description provided for @name. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get name; + + /// No description provided for @type. + /// + /// In fr, this message translates to: + /// **'Type'** + String get type; + + /// No description provided for @date. + /// + /// In fr, this message translates to: + /// **'Date'** + String get date; + + /// No description provided for @loginSuccess. + /// + /// In fr, this message translates to: + /// **'Connexion réussie'** + String get loginSuccess; + + /// No description provided for @loginError. + /// + /// In fr, this message translates to: + /// **'Un problème est survenu lors de la connexion'** + String get loginError; + + /// No description provided for @rememberMe. + /// + /// In fr, this message translates to: + /// **'Se souvenir de moi'** + String get rememberMe; + + /// No description provided for @connect. + /// + /// In fr, this message translates to: + /// **'SE CONNECTER'** + String get connect; + + /// No description provided for @menuApplications. + /// + /// In fr, this message translates to: + /// **'Applications'** + String get menuApplications; + + /// No description provided for @menuConfigurations. + /// + /// In fr, this message translates to: + /// **'Configurations'** + String get menuConfigurations; + + /// No description provided for @menuResources. + /// + /// In fr, this message translates to: + /// **'Ressources'** + String get menuResources; + + /// No description provided for @menuStatistics. + /// + /// In fr, this message translates to: + /// **'Statistiques'** + String get menuStatistics; + + /// No description provided for @menuNotifications. + /// + /// In fr, this message translates to: + /// **'Notifications'** + String get menuNotifications; + + /// No description provided for @menuUsers. + /// + /// In fr, this message translates to: + /// **'Utilisateurs'** + String get menuUsers; + + /// No description provided for @menuApiKeys. + /// + /// In fr, this message translates to: + /// **'Clés API'** + String get menuApiKeys; + + /// No description provided for @noPlansAvailable. + /// + /// In fr, this message translates to: + /// **'Aucun plan disponible. Créez d\'abord un plan.'** + String get noPlansAvailable; + + /// No description provided for @noPlan. + /// + /// In fr, this message translates to: + /// **'Aucun plan (illimité)'** + String get noPlan; + + /// No description provided for @unlimitedStorage. + /// + /// In fr, this message translates to: + /// **'Stockage illimité'** + String get unlimitedStorage; + + /// No description provided for @unlimitedAI. + /// + /// In fr, this message translates to: + /// **'IA illimitée'** + String get unlimitedAI; + + /// No description provided for @aiRequestsPerMonth. + /// + /// In fr, this message translates to: + /// **'{count} req IA/mois'** + String aiRequestsPerMonth(int count); + + /// No description provided for @storageLabel. + /// + /// In fr, this message translates to: + /// **'Stockage'** + String get storageLabel; + + /// No description provided for @aiThisMonthLabel. + /// + /// In fr, this message translates to: + /// **'IA ce mois'** + String get aiThisMonthLabel; + + /// No description provided for @unlimited. + /// + /// In fr, this message translates to: + /// **'Illimité'** + String get unlimited; + + /// No description provided for @requestsAbbr. + /// + /// In fr, this message translates to: + /// **'req'** + String get requestsAbbr; + + /// No description provided for @planUpdated. + /// + /// In fr, this message translates to: + /// **'Plan mis à jour'** + String get planUpdated; + + /// No description provided for @errorMessage. + /// + /// In fr, this message translates to: + /// **'Erreur : {error}'** + String errorMessage(String error); + + /// No description provided for @switchInstance. + /// + /// In fr, this message translates to: + /// **'Changer d\'instance'** + String get switchInstance; + + /// No description provided for @noInstanceFound. + /// + /// In fr, this message translates to: + /// **'Aucune instance trouvée'** + String get noInstanceFound; + + /// No description provided for @configurePlan. + /// + /// In fr, this message translates to: + /// **'Configurer le plan'** + String get configurePlan; + + /// No description provided for @tooltipSwitchInstance. + /// + /// In fr, this message translates to: + /// **'Changer d\'instance'** + String get tooltipSwitchInstance; + + /// No description provided for @usersTitle. + /// + /// In fr, this message translates to: + /// **'Utilisateurs'** + String get usersTitle; + + /// No description provided for @createUserBtn. + /// + /// In fr, this message translates to: + /// **'Créer un utilisateur'** + String get createUserBtn; + + /// No description provided for @createUserTitle. + /// + /// In fr, this message translates to: + /// **'Créer un utilisateur'** + String get createUserTitle; + + /// No description provided for @editUserTitle. + /// + /// In fr, this message translates to: + /// **'Modifier l\'utilisateur'** + String get editUserTitle; + + /// No description provided for @deleteUserTitle. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'utilisateur'** + String get deleteUserTitle; + + /// No description provided for @deleteUserConfirm. + /// + /// In fr, this message translates to: + /// **'Supprimer {email} ?'** + String deleteUserConfirm(String email); + + /// No description provided for @noUsers. + /// + /// In fr, this message translates to: + /// **'Aucun utilisateur'** + String get noUsers; + + /// No description provided for @email. + /// + /// In fr, this message translates to: + /// **'Email'** + String get email; + + /// No description provided for @firstName. + /// + /// In fr, this message translates to: + /// **'Prénom'** + String get firstName; + + /// No description provided for @lastName. + /// + /// In fr, this message translates to: + /// **'Nom'** + String get lastName; + + /// No description provided for @password. + /// + /// In fr, this message translates to: + /// **'Mot de passe'** + String get password; + + /// No description provided for @role. + /// + /// In fr, this message translates to: + /// **'Rôle'** + String get role; + + /// No description provided for @tooltipEdit. + /// + /// In fr, this message translates to: + /// **'Modifier'** + String get tooltipEdit; + + /// No description provided for @tooltipDelete. + /// + /// In fr, this message translates to: + /// **'Supprimer'** + String get tooltipDelete; + + /// No description provided for @notificationsTitle. + /// + /// In fr, this message translates to: + /// **'Notifications'** + String get notificationsTitle; + + /// No description provided for @newMessage. + /// + /// In fr, this message translates to: + /// **'Nouveau message'** + String get newMessage; + + /// No description provided for @messageTitle. + /// + /// In fr, this message translates to: + /// **'Titre'** + String get messageTitle; + + /// No description provided for @messageBody. + /// + /// In fr, this message translates to: + /// **'Message'** + String get messageBody; + + /// No description provided for @schedule. + /// + /// In fr, this message translates to: + /// **'Planifier'** + String get schedule; + + /// No description provided for @time. + /// + /// In fr, this message translates to: + /// **'Heure'** + String get time; + + /// No description provided for @send. + /// + /// In fr, this message translates to: + /// **'Envoyer'** + String get send; + + /// No description provided for @cancelNotification. + /// + /// In fr, this message translates to: + /// **'Annuler la notification'** + String get cancelNotification; + + /// No description provided for @cancelNotificationConfirm. + /// + /// In fr, this message translates to: + /// **'Annuler « {title} » ?'** + String cancelNotificationConfirm(String title); + + /// No description provided for @allNotifications. + /// + /// In fr, this message translates to: + /// **'Toutes'** + String get allNotifications; + + /// No description provided for @sentNotifications. + /// + /// In fr, this message translates to: + /// **'Envoyées'** + String get sentNotifications; + + /// No description provided for @scheduledNotifications. + /// + /// In fr, this message translates to: + /// **'Planifiées'** + String get scheduledNotifications; + + /// No description provided for @failedNotifications. + /// + /// In fr, this message translates to: + /// **'Échouées'** + String get failedNotifications; + + /// No description provided for @noNotifications. + /// + /// In fr, this message translates to: + /// **'Aucune notification envoyée'** + String get noNotifications; + + /// No description provided for @noNotificationsForFilter. + /// + /// In fr, this message translates to: + /// **'Aucune notification pour ce filtre'** + String get noNotificationsForFilter; + + /// No description provided for @topic. + /// + /// In fr, this message translates to: + /// **'Topic'** + String get topic; + + /// No description provided for @tooltipCancelNotification. + /// + /// In fr, this message translates to: + /// **'Annuler'** + String get tooltipCancelNotification; + + /// No description provided for @resultsCount. + /// + /// In fr, this message translates to: + /// **'{count, plural, one{{count} résultat} other{{count} résultats}}'** + String resultsCount(int count); + + /// No description provided for @apiKeysTitle. + /// + /// In fr, this message translates to: + /// **'Clés API'** + String get apiKeysTitle; + + /// No description provided for @createApiKey. + /// + /// In fr, this message translates to: + /// **'Créer une clé API'** + String get createApiKey; + + /// No description provided for @createKeyBtn. + /// + /// In fr, this message translates to: + /// **'Créer une clé'** + String get createKeyBtn; + + /// No description provided for @appType. + /// + /// In fr, this message translates to: + /// **'Type d\'application'** + String get appType; + + /// No description provided for @noExpiration. + /// + /// In fr, this message translates to: + /// **'Pas d\'expiration'** + String get noExpiration; + + /// No description provided for @expiresOn. + /// + /// In fr, this message translates to: + /// **'Expire le {date}'** + String expiresOn(String date); + + /// No description provided for @choose. + /// + /// In fr, this message translates to: + /// **'Choisir'** + String get choose; + + /// No description provided for @removeExpiration. + /// + /// In fr, this message translates to: + /// **'Supprimer l\'expiration'** + String get removeExpiration; + + /// No description provided for @apiKeyCreatedTitle. + /// + /// In fr, this message translates to: + /// **'Clé API créée'** + String get apiKeyCreatedTitle; + + /// No description provided for @copyKeyWarning. + /// + /// In fr, this message translates to: + /// **'Copiez cette clé maintenant — elle ne sera plus affichée.'** + String get copyKeyWarning; + + /// No description provided for @copy. + /// + /// In fr, this message translates to: + /// **'Copier'** + String get copy; + + /// No description provided for @copiedKey. + /// + /// In fr, this message translates to: + /// **'J\'ai copié la clé'** + String get copiedKey; + + /// No description provided for @revokeApiKeyTitle. + /// + /// In fr, this message translates to: + /// **'Révoquer la clé API'** + String get revokeApiKeyTitle; + + /// No description provided for @revokeApiKeyConfirm. + /// + /// In fr, this message translates to: + /// **'Révoquer « {name} » ? Les apps utilisant cette clé perdront l\'accès.'** + String revokeApiKeyConfirm(String name); + + /// No description provided for @revoke. + /// + /// In fr, this message translates to: + /// **'Révoquer'** + String get revoke; + + /// No description provided for @noApiKeys. + /// + /// In fr, this message translates to: + /// **'Aucune clé API'** + String get noApiKeys; + + /// No description provided for @createdOn. + /// + /// In fr, this message translates to: + /// **'Créée le'** + String get createdOn; + + /// No description provided for @expiration. + /// + /// In fr, this message translates to: + /// **'Expiration'** + String get expiration; + + /// No description provided for @activeKey. + /// + /// In fr, this message translates to: + /// **'Active'** + String get activeKey; + + /// No description provided for @revokedKey. + /// + /// In fr, this message translates to: + /// **'Révoquée'** + String get revokedKey; + + /// No description provided for @tooltipRevoke. + /// + /// In fr, this message translates to: + /// **'Révoquer'** + String get tooltipRevoke; + + /// No description provided for @statisticsTitle. + /// + /// In fr, this message translates to: + /// **'Statistiques'** + String get statisticsTitle; + + /// No description provided for @statsLoadError. + /// + /// In fr, this message translates to: + /// **'Impossible de charger les statistiques'** + String get statsLoadError; + + /// No description provided for @statsNoData. + /// + /// In fr, this message translates to: + /// **'Pas encore de données pour cette période'** + String get statsNoData; + + /// No description provided for @statsNoDataForType. + /// + /// In fr, this message translates to: + /// **'Aucun event reçu pour le type \"{type}\"'** + String statsNoDataForType(String type); + + /// No description provided for @statsSessions. + /// + /// In fr, this message translates to: + /// **'Sessions'** + String get statsSessions; + + /// No description provided for @statsAvgDuration. + /// + /// In fr, this message translates to: + /// **'Durée moy.'** + String get statsAvgDuration; + + /// No description provided for @statsTopApp. + /// + /// In fr, this message translates to: + /// **'App top'** + String get statsTopApp; + + /// No description provided for @statsTopLang. + /// + /// In fr, this message translates to: + /// **'Langue top'** + String get statsTopLang; + + /// No description provided for @statsVisitsByDay. + /// + /// In fr, this message translates to: + /// **'Visites par jour'** + String get statsVisitsByDay; + + /// No description provided for @statsAll. + /// + /// In fr, this message translates to: + /// **'Tous'** + String get statsAll; + + /// No description provided for @statsTopSections. + /// + /// In fr, this message translates to: + /// **'Top sections'** + String get statsTopSections; + + /// No description provided for @statsApps. + /// + /// In fr, this message translates to: + /// **'Apps'** + String get statsApps; + + /// No description provided for @statsLanguages. + /// + /// In fr, this message translates to: + /// **'Langues'** + String get statsLanguages; + + /// No description provided for @statsTopPOI. + /// + /// In fr, this message translates to: + /// **'Top POI'** + String get statsTopPOI; + + /// No description provided for @statsPOI. + /// + /// In fr, this message translates to: + /// **'POI'** + String get statsPOI; + + /// No description provided for @statsTaps. + /// + /// In fr, this message translates to: + /// **'Taps'** + String get statsTaps; + + /// No description provided for @statsTopAgenda. + /// + /// In fr, this message translates to: + /// **'Top événements agenda'** + String get statsTopAgenda; + + /// No description provided for @statsEvent. + /// + /// In fr, this message translates to: + /// **'Événement'** + String get statsEvent; + + /// No description provided for @statsQuiz. + /// + /// In fr, this message translates to: + /// **'Quiz'** + String get statsQuiz; + + /// No description provided for @statsSection. + /// + /// In fr, this message translates to: + /// **'Section'** + String get statsSection; + + /// No description provided for @statsAvgScore. + /// + /// In fr, this message translates to: + /// **'Score moy'** + String get statsAvgScore; + + /// No description provided for @statsCompletions. + /// + /// In fr, this message translates to: + /// **'Complétions'** + String get statsCompletions; + + /// No description provided for @statsGames. + /// + /// In fr, this message translates to: + /// **'Jeux'** + String get statsGames; + + /// No description provided for @statsGameType. + /// + /// In fr, this message translates to: + /// **'Type'** + String get statsGameType; + + /// No description provided for @statsArticles. + /// + /// In fr, this message translates to: + /// **'Articles lus'** + String get statsArticles; + + /// No description provided for @statsReadings. + /// + /// In fr, this message translates to: + /// **'Lectures'** + String get statsReadings; + + /// No description provided for @statsMenuTitle. + /// + /// In fr, this message translates to: + /// **'Menu'** + String get statsMenuTitle; + + /// No description provided for @statsMenuItem. + /// + /// In fr, this message translates to: + /// **'Item'** + String get statsMenuItem; + + /// No description provided for @statsQrScans. + /// + /// In fr, this message translates to: + /// **'QR Scans'** + String get statsQrScans; + + /// No description provided for @statsTotal. + /// + /// In fr, this message translates to: + /// **'Total'** + String get statsTotal; + + /// No description provided for @statsValid. + /// + /// In fr, this message translates to: + /// **'Valides'** + String get statsValid; + + /// No description provided for @statsInvalid. + /// + /// In fr, this message translates to: + /// **'Invalides'** + String get statsInvalid; + + /// No description provided for @statsViews. + /// + /// In fr, this message translates to: + /// **'Vues'** + String get statsViews; + + /// No description provided for @noData. + /// + /// In fr, this message translates to: + /// **'Aucune donnée'** + String get noData; + + /// No description provided for @errorOccurred. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue'** + String get errorOccurred; + + /// No description provided for @yes. + /// + /// In fr, this message translates to: + /// **'Oui'** + String get yes; + + /// No description provided for @newConfiguration. + /// + /// In fr, this message translates to: + /// **'Nouvelle configuration'** + String get newConfiguration; + + /// No description provided for @configNameLabel. + /// + /// In fr, this message translates to: + /// **'Nom :'** + String get configNameLabel; + + /// No description provided for @orText. + /// + /// In fr, this message translates to: + /// **'ou'** + String get orText; + + /// No description provided for @importLabel. + /// + /// In fr, this message translates to: + /// **'Importer'** + String get importLabel; + + /// No description provided for @configNameRequired. + /// + /// In fr, this message translates to: + /// **'Veuillez spécifier un nom pour la nouvelle visite'** + String get configNameRequired; + + /// No description provided for @configCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'La configuration a été créée avec succès'** + String get configCreatedSuccess; + + /// No description provided for @configExportSuccess. + /// + /// In fr, this message translates to: + /// **'L\'export de la configuration a réussi, le document se trouve à cet endroit : {path}'** + String configExportSuccess(String path); + + /// No description provided for @configExportFailed. + /// + /// In fr, this message translates to: + /// **'L\'export de la configuration a échoué'** + String get configExportFailed; + + /// No description provided for @configDeletedSuccess. + /// + /// In fr, this message translates to: + /// **'La configuration a été supprimée avec succès'** + String get configDeletedSuccess; + + /// No description provided for @configSavedSuccess. + /// + /// In fr, this message translates to: + /// **'La configuration a été sauvegardée avec succès'** + String get configSavedSuccess; + + /// No description provided for @configDeleteConfirm. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer cette configuration ?'** + String get configDeleteConfirm; + + /// No description provided for @newSection. + /// + /// In fr, this message translates to: + /// **'Nouvelle section'** + String get newSection; + + /// No description provided for @newSubSection. + /// + /// In fr, this message translates to: + /// **'Nouvelle sous section'** + String get newSubSection; + + /// No description provided for @sectionNameLabel. + /// + /// In fr, this message translates to: + /// **'Nom :'** + String get sectionNameLabel; + + /// No description provided for @sectionTypeLabel. + /// + /// In fr, this message translates to: + /// **'Type:'** + String get sectionTypeLabel; + + /// No description provided for @sectionNameRequired. + /// + /// In fr, this message translates to: + /// **'Veuillez spécifier un nom pour la nouvelle section'** + String get sectionNameRequired; + + /// No description provided for @sectionCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'La section a été créée avec succès !'** + String get sectionCreatedSuccess; + + /// No description provided for @sectionDeleteConfirm. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer cette section ?'** + String get sectionDeleteConfirm; + + /// No description provided for @sectionSavedSuccess. + /// + /// In fr, this message translates to: + /// **'La section a été sauvegardée avec succès'** + String get sectionSavedSuccess; + + /// No description provided for @sectionTranslationSaved. + /// + /// In fr, this message translates to: + /// **'Les traductions de la section ont été sauvegardées avec succès'** + String get sectionTranslationSaved; + + /// No description provided for @sectionLoadError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la récupération de la section'** + String get sectionLoadError; + + /// No description provided for @qrCodeCopied. + /// + /// In fr, this message translates to: + /// **'Ce QR code a été copié dans le presse papier'** + String get qrCodeCopied; + + /// No description provided for @beaconLabel. + /// + /// In fr, this message translates to: + /// **'Beacon :'** + String get beaconLabel; + + /// No description provided for @beaconIdLabel. + /// + /// In fr, this message translates to: + /// **'Identifiant Beacon :'** + String get beaconIdLabel; + + /// No description provided for @beaconMustBeNumber. + /// + /// In fr, this message translates to: + /// **'Cela doit être un chiffre'** + String get beaconMustBeNumber; + + /// No description provided for @identifierLabel. + /// + /// In fr, this message translates to: + /// **'Identifiant :'** + String get identifierLabel; + + /// No description provided for @displayTitleLabel. + /// + /// In fr, this message translates to: + /// **'Titre affiché:'** + String get displayTitleLabel; + + /// No description provided for @imageLabel. + /// + /// In fr, this message translates to: + /// **'Image :'** + String get imageLabel; + + /// No description provided for @backgroundImageLabel. + /// + /// In fr, this message translates to: + /// **'Image fond d\'écran :'** + String get backgroundImageLabel; + + /// No description provided for @questionInputLabel. + /// + /// In fr, this message translates to: + /// **'Question :'** + String get questionInputLabel; + + /// No description provided for @videoUrlLabel. + /// + /// In fr, this message translates to: + /// **'Url de la vidéo:'** + String get videoUrlLabel; + + /// No description provided for @webUrlLabel. + /// + /// In fr, this message translates to: + /// **'Url du site web:'** + String get webUrlLabel; + + /// No description provided for @subSectionUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'La sous section a été mis à jour avec succès'** + String get subSectionUpdatedSuccess; + + /// No description provided for @subSectionUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour de la sous section'** + String get subSectionUpdateError; + + /// No description provided for @subSectionDeletedSuccess. + /// + /// In fr, this message translates to: + /// **'La sous section a été supprimée avec succès'** + String get subSectionDeletedSuccess; + + /// No description provided for @subSectionDeleteError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la suppression de la sous section'** + String get subSectionDeleteError; + + /// No description provided for @subSectionOrderUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'ordre des sous sections a été mis à jour avec succès'** + String get subSectionOrderUpdatedSuccess; + + /// No description provided for @subSectionOrderUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour de l\'ordre des sous sections'** + String get subSectionOrderUpdateError; + + /// No description provided for @subSectionCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'La sous section a été créée avec succès'** + String get subSectionCreatedSuccess; + + /// No description provided for @subSectionCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création de la sous section'** + String get subSectionCreateError; + + /// No description provided for @questionsLabel. + /// + /// In fr, this message translates to: + /// **'Questions'** + String get questionsLabel; + + /// No description provided for @questionLabel. + /// + /// In fr, this message translates to: + /// **'Question'** + String get questionLabel; + + /// No description provided for @editQuestion. + /// + /// In fr, this message translates to: + /// **'Modifier la question'** + String get editQuestion; + + /// No description provided for @questionDeleteConfirm. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer cette question ?'** + String get questionDeleteConfirm; + + /// No description provided for @questionsLoadError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la récupération des questions'** + String get questionsLoadError; + + /// No description provided for @quizBadScore. + /// + /// In fr, this message translates to: + /// **'Mauvais score'** + String get quizBadScore; + + /// No description provided for @quizMediumScore. + /// + /// In fr, this message translates to: + /// **'Moyen score'** + String get quizMediumScore; + + /// No description provided for @quizGoodScore. + /// + /// In fr, this message translates to: + /// **'Bon score'** + String get quizGoodScore; + + /// No description provided for @quizExcellentScore. + /// + /// In fr, this message translates to: + /// **'Excellent score'** + String get quizExcellentScore; + + /// No description provided for @quizBadScoreMsg. + /// + /// In fr, this message translates to: + /// **'Message pour un mauvais score'** + String get quizBadScoreMsg; + + /// No description provided for @quizMediumScoreMsg. + /// + /// In fr, this message translates to: + /// **'Message pour un score moyen'** + String get quizMediumScoreMsg; + + /// No description provided for @quizGoodScoreMsg. + /// + /// In fr, this message translates to: + /// **'Message pour un bon score'** + String get quizGoodScoreMsg; + + /// No description provided for @quizExcellentScoreMsg. + /// + /// In fr, this message translates to: + /// **'Message pour un excellent score'** + String get quizExcellentScoreMsg; + + /// No description provided for @questionOrderUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'ordre des questions a été mis à jour avec succès'** + String get questionOrderUpdatedSuccess; + + /// No description provided for @questionOrderUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour de l\'ordre des questions'** + String get questionOrderUpdateError; + + /// No description provided for @questionCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'La question a été créée avec succès'** + String get questionCreatedSuccess; + + /// No description provided for @questionCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création de la question'** + String get questionCreateError; + + /// No description provided for @questionUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'La question a été mis à jour avec succès'** + String get questionUpdatedSuccess; + + /// No description provided for @questionUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour de la question'** + String get questionUpdateError; + + /// No description provided for @questionDeletedSuccess. + /// + /// In fr, this message translates to: + /// **'La question a été supprimée avec succès'** + String get questionDeletedSuccess; + + /// No description provided for @questionDeleteError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la suppression de la question'** + String get questionDeleteError; + + /// No description provided for @translationIncomplete. + /// + /// In fr, this message translates to: + /// **'La traduction n\'est pas complète'** + String get translationIncomplete; + + /// No description provided for @geopointCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'Le point a été créé avec succès'** + String get geopointCreatedSuccess; + + /// No description provided for @geopointCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création du point'** + String get geopointCreateError; + + /// No description provided for @geopointUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'Le point a été mis à jour avec succès'** + String get geopointUpdatedSuccess; + + /// No description provided for @geopointUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour du point'** + String get geopointUpdateError; + + /// No description provided for @geopointDeletedSuccess. + /// + /// In fr, this message translates to: + /// **'Le point a été supprimé avec succès'** + String get geopointDeletedSuccess; + + /// No description provided for @geopointDeleteError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la suppression du point'** + String get geopointDeleteError; + + /// No description provided for @categoryCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'La catégorie a été créée avec succès'** + String get categoryCreatedSuccess; + + /// No description provided for @categoryCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création de la catégorie'** + String get categoryCreateError; + + /// No description provided for @categoryUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'La catégorie a été mise à jour avec succès'** + String get categoryUpdatedSuccess; + + /// No description provided for @categoryUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour de la catégorie'** + String get categoryUpdateError; + + /// No description provided for @eventCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'événement a été créé avec succès'** + String get eventCreatedSuccess; + + /// No description provided for @eventCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création de l\'événement'** + String get eventCreateError; + + /// No description provided for @eventUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'événement a été mis à jour avec succès'** + String get eventUpdatedSuccess; + + /// No description provided for @eventUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour de l\'événement'** + String get eventUpdateError; + + /// No description provided for @eventDeletedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'événement a été supprimé avec succès'** + String get eventDeletedSuccess; + + /// No description provided for @eventDeleteError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la suppression de l\'événement'** + String get eventDeleteError; + + /// No description provided for @annotationCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'annotation a été créée avec succès'** + String get annotationCreatedSuccess; + + /// No description provided for @annotationCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création de l\'annotation'** + String get annotationCreateError; + + /// No description provided for @annotationUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'annotation a été mise à jour avec succès'** + String get annotationUpdatedSuccess; + + /// No description provided for @annotationUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour de l\'annotation'** + String get annotationUpdateError; + + /// No description provided for @programmeBlockCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'Le bloc a été créé avec succès'** + String get programmeBlockCreatedSuccess; + + /// No description provided for @programmeBlockCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création du bloc'** + String get programmeBlockCreateError; + + /// No description provided for @programmeBlockUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'Le bloc a été mis à jour avec succès'** + String get programmeBlockUpdatedSuccess; + + /// No description provided for @programmeBlockUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour du bloc'** + String get programmeBlockUpdateError; + + /// No description provided for @pathCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'Le parcours a été créé avec succès'** + String get pathCreatedSuccess; + + /// No description provided for @pathCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création du parcours'** + String get pathCreateError; + + /// No description provided for @pathUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'Le parcours a été mis à jour avec succès'** + String get pathUpdatedSuccess; + + /// No description provided for @pathUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour du parcours'** + String get pathUpdateError; + + /// No description provided for @stepCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'étape a été créée avec succès'** + String get stepCreatedSuccess; + + /// No description provided for @stepCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création de l\'étape'** + String get stepCreateError; + + /// No description provided for @stepUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'étape a été mise à jour avec succès'** + String get stepUpdatedSuccess; + + /// No description provided for @stepUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour de l\'étape'** + String get stepUpdateError; + + /// No description provided for @agendaEventsLabel. + /// + /// In fr, this message translates to: + /// **'Évènements'** + String get agendaEventsLabel; + + /// No description provided for @agendaEventFallback. + /// + /// In fr, this message translates to: + /// **'Évènement'** + String get agendaEventFallback; + + /// No description provided for @addEvent. + /// + /// In fr, this message translates to: + /// **'Ajouter un évènement'** + String get addEvent; + + /// No description provided for @noEvents. + /// + /// In fr, this message translates to: + /// **'Aucun évènement'** + String get noEvents; + + /// No description provided for @noAddress. + /// + /// In fr, this message translates to: + /// **'Pas d\'adresse'** + String get noAddress; + + /// No description provided for @onlineLabel. + /// + /// In fr, this message translates to: + /// **'En ligne :'** + String get onlineLabel; + + /// No description provided for @mapViewLabel. + /// + /// In fr, this message translates to: + /// **'Vue carte :'** + String get mapViewLabel; + + /// No description provided for @mapServiceLabel. + /// + /// In fr, this message translates to: + /// **'Service carte :'** + String get mapServiceLabel; + + /// No description provided for @jsonFilesLabel. + /// + /// In fr, this message translates to: + /// **'Fichiers json :'** + String get jsonFilesLabel; + + /// No description provided for @jsonLabel. + /// + /// In fr, this message translates to: + /// **'JSON'** + String get jsonLabel; + + /// No description provided for @guidedPathsLabel. + /// + /// In fr, this message translates to: + /// **'Parcours Guidés'** + String get guidedPathsLabel; + + /// No description provided for @addPath. + /// + /// In fr, this message translates to: + /// **'Ajouter un parcours'** + String get addPath; + + /// No description provided for @noPathConfigured. + /// + /// In fr, this message translates to: + /// **'Aucun parcours configuré'** + String get noPathConfigured; + + /// No description provided for @pathOrderUpdateError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la mise à jour de l\'ordre'** + String get pathOrderUpdateError; + + /// No description provided for @pathNoTitle. + /// + /// In fr, this message translates to: + /// **'Parcours sans titre'** + String get pathNoTitle; + + /// No description provided for @pathDeletedSuccess. + /// + /// In fr, this message translates to: + /// **'Parcours supprimé avec succès'** + String get pathDeletedSuccess; + + /// No description provided for @pathDeleteError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression du parcours'** + String get pathDeleteError; + + /// No description provided for @pathsLabel. + /// + /// In fr, this message translates to: + /// **'Parcours'** + String get pathsLabel; + + /// No description provided for @pointsOfInterestLabel. + /// + /// In fr, this message translates to: + /// **'Points d\'Intérêt'** + String get pointsOfInterestLabel; + + /// No description provided for @geopointsLabel. + /// + /// In fr, this message translates to: + /// **'Points géographiques'** + String get geopointsLabel; + + /// No description provided for @searchLabel. + /// + /// In fr, this message translates to: + /// **'Recherche :'** + String get searchLabel; + + /// No description provided for @geopointsLoadError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la récupération des points géographiques'** + String get geopointsLoadError; + + /// No description provided for @geopointDeleteConfirm. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer ce point géographique ?'** + String get geopointDeleteConfirm; + + /// No description provided for @serviceLabel. + /// + /// In fr, this message translates to: + /// **'Service :'** + String get serviceLabel; + + /// No description provided for @centerPointLabel. + /// + /// In fr, this message translates to: + /// **'Point de centrage :'** + String get centerPointLabel; + + /// No description provided for @iconLabel. + /// + /// In fr, this message translates to: + /// **'Icône :'** + String get iconLabel; + + /// No description provided for @listViewLabel. + /// + /// In fr, this message translates to: + /// **'Vue liste :'** + String get listViewLabel; + + /// No description provided for @typeLabel. + /// + /// In fr, this message translates to: + /// **'Type :'** + String get typeLabel; + + /// No description provided for @zoomLabel. + /// + /// In fr, this message translates to: + /// **'Zoom :'** + String get zoomLabel; + + /// No description provided for @categoriesLabel. + /// + /// In fr, this message translates to: + /// **'Catégories :'** + String get categoriesLabel; + + /// No description provided for @startDateLabel. + /// + /// In fr, this message translates to: + /// **'Date de début'** + String get startDateLabel; + + /// No description provided for @notDefined. + /// + /// In fr, this message translates to: + /// **'Non définie'** + String get notDefined; + + /// No description provided for @endDateLabel. + /// + /// In fr, this message translates to: + /// **'Date de fin'** + String get endDateLabel; + + /// No description provided for @programmeLabel. + /// + /// In fr, this message translates to: + /// **'Programme'** + String get programmeLabel; + + /// No description provided for @addBlock. + /// + /// In fr, this message translates to: + /// **'Ajouter un bloc'** + String get addBlock; + + /// No description provided for @blockFallback. + /// + /// In fr, this message translates to: + /// **'Bloc'** + String get blockFallback; + + /// No description provided for @noBlocks. + /// + /// In fr, this message translates to: + /// **'Aucun bloc de programme défini'** + String get noBlocks; + + /// No description provided for @programmeBlockDeletedSuccess. + /// + /// In fr, this message translates to: + /// **'Bloc supprimé avec succès'** + String get programmeBlockDeletedSuccess; + + /// No description provided for @programmeBlockDeleteError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression du bloc'** + String get programmeBlockDeleteError; + + /// No description provided for @baseMapLabel. + /// + /// In fr, this message translates to: + /// **'Carte de base'** + String get baseMapLabel; + + /// No description provided for @mapLabel. + /// + /// In fr, this message translates to: + /// **'Carte :'** + String get mapLabel; + + /// No description provided for @globalAnnotationsLabel. + /// + /// In fr, this message translates to: + /// **'Annotations globales'** + String get globalAnnotationsLabel; + + /// No description provided for @addAnnotation. + /// + /// In fr, this message translates to: + /// **'Ajouter une annotation'** + String get addAnnotation; + + /// No description provided for @noAnnotations. + /// + /// In fr, this message translates to: + /// **'Aucune annotation globale'** + String get noAnnotations; + + /// No description provided for @annotationFallback. + /// + /// In fr, this message translates to: + /// **'Annotation'** + String get annotationFallback; + + /// No description provided for @annotationDeletedSuccess. + /// + /// In fr, this message translates to: + /// **'Annotation supprimée'** + String get annotationDeletedSuccess; + + /// No description provided for @annotationDeleteError. + /// + /// In fr, this message translates to: + /// **'Erreur lors de la suppression'** + String get annotationDeleteError; + + /// No description provided for @agendaEventCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'événement a été créé avec succès'** + String get agendaEventCreatedSuccess; + + /// No description provided for @agendaEventCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création de l\'événement'** + String get agendaEventCreateError; + + /// No description provided for @agendaEventUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'événement a été mis à jour avec succès'** + String get agendaEventUpdatedSuccess; + + /// No description provided for @agendaEventUpdateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la mise à jour de l\'événement'** + String get agendaEventUpdateError; + + /// No description provided for @agendaEventDeletedSuccess. + /// + /// In fr, this message translates to: + /// **'L\'événement a été supprimé avec succès'** + String get agendaEventDeletedSuccess; + + /// No description provided for @agendaEventDeleteError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la suppression de l\'événement'** + String get agendaEventDeleteError; + + /// No description provided for @newResource. + /// + /// In fr, this message translates to: + /// **'Nouvelle ressource'** + String get newResource; + + /// No description provided for @resourceCreatedSuccess. + /// + /// In fr, this message translates to: + /// **'La ressource a été créée avec succès'** + String get resourceCreatedSuccess; + + /// No description provided for @resourceCreateError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors de la création de la ressource'** + String get resourceCreateError; + + /// No description provided for @resourceUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'La ressource a été mise à jour avec succès'** + String get resourceUpdatedSuccess; + + /// No description provided for @resourceNoFileLoaded. + /// + /// In fr, this message translates to: + /// **'Aucun fichier n\'a été chargé'** + String get resourceNoFileLoaded; + + /// No description provided for @resourceNameRequired. + /// + /// In fr, this message translates to: + /// **'Veuillez donner un nom à la ressource'** + String get resourceNameRequired; + + /// No description provided for @resourceTooLarge. + /// + /// In fr, this message translates to: + /// **'Erreur, attention, la taille de la ressource doit être inférieure à 1.5Mb'** + String get resourceTooLarge; + + /// No description provided for @resourceInvalidUrl. + /// + /// In fr, this message translates to: + /// **'L\'url est invalide'** + String get resourceInvalidUrl; + + /// No description provided for @resourceUrlRequired. + /// + /// In fr, this message translates to: + /// **'Veuillez remplir le champ URL'** + String get resourceUrlRequired; + + /// No description provided for @generalInfo. + /// + /// In fr, this message translates to: + /// **'Informations générales'** + String get generalInfo; + + /// No description provided for @mainImageLabel. + /// + /// In fr, this message translates to: + /// **'Image principale :'** + String get mainImageLabel; + + /// No description provided for @loaderLabel. + /// + /// In fr, this message translates to: + /// **'Loader :'** + String get loaderLabel; + + /// No description provided for @primaryColorLabel. + /// + /// In fr, this message translates to: + /// **'Couleur principale :'** + String get primaryColorLabel; + + /// No description provided for @secondaryColorLabel. + /// + /// In fr, this message translates to: + /// **'Couleur secondaire :'** + String get secondaryColorLabel; + + /// No description provided for @layoutLabel. + /// + /// In fr, this message translates to: + /// **'Affichage :'** + String get layoutLabel; + + /// No description provided for @layoutGrid. + /// + /// In fr, this message translates to: + /// **'Grille'** + String get layoutGrid; + + /// No description provided for @languagesLabel. + /// + /// In fr, this message translates to: + /// **'Langues :'** + String get languagesLabel; + + /// No description provided for @featuredEventLabel. + /// + /// In fr, this message translates to: + /// **'Evènement à l\'affiche :'** + String get featuredEventLabel; + + /// No description provided for @chooseEvent. + /// + /// In fr, this message translates to: + /// **'Choisir un évènement'** + String get chooseEvent; + + /// No description provided for @noneOption. + /// + /// In fr, this message translates to: + /// **'Aucun'** + String get noneOption; + + /// No description provided for @aiAssistantLabel. + /// + /// In fr, this message translates to: + /// **'Assistant IA :'** + String get aiAssistantLabel; + + /// No description provided for @appUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'Application mobile mise à jour'** + String get appUpdatedSuccess; + + /// No description provided for @configActivatedSuccess. + /// + /// In fr, this message translates to: + /// **'Configuration activée avec succès'** + String get configActivatedSuccess; + + /// No description provided for @configDeactivatedSuccess. + /// + /// In fr, this message translates to: + /// **'Configuration désactivée avec succès'** + String get configDeactivatedSuccess; + + /// No description provided for @configRemoveConfirm. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir retirer cette configuration de l\'application ?'** + String get configRemoveConfirm; + + /// No description provided for @configRemovedSuccess. + /// + /// In fr, this message translates to: + /// **'La configuration a été retirée de l\'application avec succès'** + String get configRemovedSuccess; + + /// No description provided for @configRemoveError. + /// + /// In fr, this message translates to: + /// **'Une erreur est survenue lors du retrait de la configuration'** + String get configRemoveError; + + /// No description provided for @phoneConfigTitle. + /// + /// In fr, this message translates to: + /// **'Configurations par appareil'** + String get phoneConfigTitle; + + /// No description provided for @addConfig. + /// + /// In fr, this message translates to: + /// **'Ajouter une configuration'** + String get addConfig; + + /// No description provided for @selectConfigToAdd. + /// + /// In fr, this message translates to: + /// **'Sélectionner une configuration à ajouter'** + String get selectConfigToAdd; + + /// No description provided for @noItems. + /// + /// In fr, this message translates to: + /// **'Aucun élément à afficher'** + String get noItems; + + /// No description provided for @add. + /// + /// In fr, this message translates to: + /// **'Ajouter'** + String get add; + + /// No description provided for @pinCode. + /// + /// In fr, this message translates to: + /// **'Code pin: {code}'** + String pinCode(String code); + + /// No description provided for @tablets. + /// + /// In fr, this message translates to: + /// **'Tablettes'** + String get tablets; + + /// No description provided for @stepsCount. + /// + /// In fr, this message translates to: + /// **'{count} étapes'** + String stepsCount(int count); + + /// No description provided for @displayedDescriptionLabel. + /// + /// In fr, this message translates to: + /// **'Description affichée:'** + String get displayedDescriptionLabel; + + /// No description provided for @displayedContentLabel. + /// + /// In fr, this message translates to: + /// **'Contenu affiché :'** + String get displayedContentLabel; + + /// No description provided for @displayedNameLabel. + /// + /// In fr, this message translates to: + /// **'Nom affiché :'** + String get displayedNameLabel; + + /// No description provided for @startTimeLabel. + /// + /// In fr, this message translates to: + /// **'Heure de début'** + String get startTimeLabel; + + /// No description provided for @noAnnotationConfigured. + /// + /// In fr, this message translates to: + /// **'Aucune annotation configurée.'** + String get noAnnotationConfigured; + + /// No description provided for @newStepTitle. + /// + /// In fr, this message translates to: + /// **'Nouvelle Étape'** + String get newStepTitle; + + /// No description provided for @editStepTitle. + /// + /// In fr, this message translates to: + /// **'Modifier l\'Étape'** + String get editStepTitle; + + /// No description provided for @stepTitleLabel. + /// + /// In fr, this message translates to: + /// **'Titre de l\'étape'** + String get stepTitleLabel; + + /// No description provided for @stepDescriptionLabel. + /// + /// In fr, this message translates to: + /// **'Description de l\'étape'** + String get stepDescriptionLabel; + + /// No description provided for @stepLocationLabel. + /// + /// In fr, this message translates to: + /// **'Emplacement de l\'étape :'** + String get stepLocationLabel; + + /// No description provided for @initiallyHiddenLabel. + /// + /// In fr, this message translates to: + /// **'Cachée initialement'** + String get initiallyHiddenLabel; + + /// No description provided for @lockedLabel. + /// + /// In fr, this message translates to: + /// **'Verrouillée'** + String get lockedLabel; + + /// No description provided for @durationSecondsLabel. + /// + /// In fr, this message translates to: + /// **'Durée (secondes) :'** + String get durationSecondsLabel; + + /// No description provided for @triggerGeopointLabel. + /// + /// In fr, this message translates to: + /// **'GeoPoint de déclenchement (ID) :'** + String get triggerGeopointLabel; + + /// No description provided for @questionsChallengesLabel. + /// + /// In fr, this message translates to: + /// **'Questions / Défis'** + String get questionsChallengesLabel; + + /// No description provided for @noQuestionsConfigured. + /// + /// In fr, this message translates to: + /// **'Aucune question configurée.'** + String get noQuestionsConfigured; + + /// No description provided for @newAgendaEventTitle. + /// + /// In fr, this message translates to: + /// **'Nouvel Évènement'** + String get newAgendaEventTitle; + + /// No description provided for @editAgendaEventTitle. + /// + /// In fr, this message translates to: + /// **'Modifier l\'Évènement'** + String get editAgendaEventTitle; + + /// No description provided for @eventTitleLabel. + /// + /// In fr, this message translates to: + /// **'Titre de l\'évènement'** + String get eventTitleLabel; + + /// No description provided for @eventDescriptionLabel. + /// + /// In fr, this message translates to: + /// **'Description de l\'évènement'** + String get eventDescriptionLabel; + + /// No description provided for @startDateColonLabel. + /// + /// In fr, this message translates to: + /// **'Date de début :'** + String get startDateColonLabel; + + /// No description provided for @videoResourceLabel. + /// + /// In fr, this message translates to: + /// **'Vidéo :'** + String get videoResourceLabel; + + /// No description provided for @directVideoLinkLabel. + /// + /// In fr, this message translates to: + /// **'Lien vidéo direct :'** + String get directVideoLinkLabel; + + /// No description provided for @phoneLabel. + /// + /// In fr, this message translates to: + /// **'Téléphone :'** + String get phoneLabel; + + /// No description provided for @locationGeometryLabel. + /// + /// In fr, this message translates to: + /// **'Localisation / Géométrie'** + String get locationGeometryLabel; + + /// No description provided for @geometryLabel. + /// + /// In fr, this message translates to: + /// **'Géométrie :'** + String get geometryLabel; + + /// No description provided for @materialIconLabel. + /// + /// In fr, this message translates to: + /// **'Icône (material) :'** + String get materialIconLabel; + + /// No description provided for @linearLabel. + /// + /// In fr, this message translates to: + /// **'Linéaire :'** + String get linearLabel; + + /// No description provided for @requiredSuccessLabel. + /// + /// In fr, this message translates to: + /// **'Réussite requise :'** + String get requiredSuccessLabel; + + /// No description provided for @pathStepsLabel. + /// + /// In fr, this message translates to: + /// **'Étapes du parcours'** + String get pathStepsLabel; + + /// No description provided for @noStepConfigured. + /// + /// In fr, this message translates to: + /// **'Aucun point/étape configuré.'** + String get noStepConfigured; + + /// No description provided for @stepFallback. + /// + /// In fr, this message translates to: + /// **'Étape'** + String get stepFallback; + + /// No description provided for @questionAskedLabel. + /// + /// In fr, this message translates to: + /// **'Question posée :'** + String get questionAskedLabel; + + /// No description provided for @questionTitleLabel. + /// + /// In fr, this message translates to: + /// **'Intitulé de la question'** + String get questionTitleLabel; + + /// No description provided for @expectedAnswerLabel. + /// + /// In fr, this message translates to: + /// **'Réponse attendue :'** + String get expectedAnswerLabel; + + /// No description provided for @expectedAnswerModalLabel. + /// + /// In fr, this message translates to: + /// **'Réponse attendue'** + String get expectedAnswerModalLabel; + + /// No description provided for @validationNote. + /// + /// In fr, this message translates to: + /// **'La validation se fait par comparaison (insensible à la casse).'** + String get validationNote; + + /// No description provided for @possibleAnswersLabel. + /// + /// In fr, this message translates to: + /// **'Réponses possibles :'** + String get possibleAnswersLabel; + + /// No description provided for @noAnswerDefined. + /// + /// In fr, this message translates to: + /// **'Aucune réponse définie. Ajoutez-en au moins une.'** + String get noAnswerDefined; + + /// No description provided for @correctAnswer. + /// + /// In fr, this message translates to: + /// **'Bonne réponse ✓'** + String get correctAnswer; + + /// No description provided for @wrongAnswer. + /// + /// In fr, this message translates to: + /// **'Mauvaise réponse'** + String get wrongAnswer; + + /// No description provided for @answerNote. + /// + /// In fr, this message translates to: + /// **'✓ = bonne réponse. Plusieurs peuvent être correctes.'** + String get answerNote; + + /// No description provided for @geopointZoneTitle. + /// + /// In fr, this message translates to: + /// **'Point géographique / Zone'** + String get geopointZoneTitle; + + /// No description provided for @geometryTypeLabel. + /// + /// In fr, this message translates to: + /// **'Géométrie (Point/Ligne/Zone) :'** + String get geometryTypeLabel; + + /// No description provided for @phoneModalLabel. + /// + /// In fr, this message translates to: + /// **'Téléphone'** + String get phoneModalLabel; + + /// No description provided for @categoryLabel. + /// + /// In fr, this message translates to: + /// **'Catégorie :'** + String get categoryLabel; + + /// No description provided for @startMessageLabel. + /// + /// In fr, this message translates to: + /// **'Message départ :'** + String get startMessageLabel; + + /// No description provided for @startMessageModalLabel. + /// + /// In fr, this message translates to: + /// **'Message départ'** + String get startMessageModalLabel; + + /// No description provided for @createCategoryTitle. + /// + /// In fr, this message translates to: + /// **'Création catégorie'** + String get createCategoryTitle; + + /// No description provided for @editCategoryTitle. + /// + /// In fr, this message translates to: + /// **'Modification catégorie'** + String get editCategoryTitle; + + /// No description provided for @categoryIconLabel. + /// + /// In fr, this message translates to: + /// **'Icône catégorie :'** + String get categoryIconLabel; + + /// No description provided for @selectResource. + /// + /// In fr, this message translates to: + /// **'Sélectionner une ressource'** + String get selectResource; + + /// No description provided for @createPdfTitle. + /// + /// In fr, this message translates to: + /// **'Création PDF'** + String get createPdfTitle; + + /// No description provided for @answersLabel. + /// + /// In fr, this message translates to: + /// **'Réponses'** + String get answersLabel; + + /// No description provided for @answerLabel. + /// + /// In fr, this message translates to: + /// **'Réponse'** + String get answerLabel; + + /// No description provided for @createAnswerTitle. + /// + /// In fr, this message translates to: + /// **'Créer la réponse'** + String get createAnswerTitle; + + /// No description provided for @editAnswerTitle. + /// + /// In fr, this message translates to: + /// **'Modifier la réponse'** + String get editAnswerTitle; + + /// No description provided for @answerValidNote. + /// + /// In fr, this message translates to: + /// **'Si coché, la réponse est valide'** + String get answerValidNote; + + /// No description provided for @selectConfiguration. + /// + /// In fr, this message translates to: + /// **'Sélectionner une configuration'** + String get selectConfiguration; + + /// No description provided for @noConfigFound. + /// + /// In fr, this message translates to: + /// **'Aucune configuration trouvée'** + String get noConfigFound; + + /// No description provided for @backgroundColorLabel. + /// + /// In fr, this message translates to: + /// **'Couleur fond d\'écran :'** + String get backgroundColorLabel; + + /// No description provided for @updateTabletBtn. + /// + /// In fr, this message translates to: + /// **'Mettre à jour la tablette'** + String get updateTabletBtn; + + /// No description provided for @kioskUpdatedSuccess. + /// + /// In fr, this message translates to: + /// **'Le kiosk a été mis à jour'** + String get kioskUpdatedSuccess; + + /// No description provided for @webViewError. + /// + /// In fr, this message translates to: + /// **'La page internet ne peut pas être affichée, l\'url est incorrecte ou vide'** + String get webViewError; + + /// No description provided for @download. + /// + /// In fr, this message translates to: + /// **'Télécharger'** + String get download; + + /// No description provided for @resourceDeleteConfirm. + /// + /// In fr, this message translates to: + /// **'Êtes-vous sûr de vouloir supprimer cette ressource ?'** + String get resourceDeleteConfirm; + + /// No description provided for @categoriesTitle. + /// + /// In fr, this message translates to: + /// **'Catégories'** + String get categoriesTitle; + + /// No description provided for @endTimeLabel. + /// + /// In fr, this message translates to: + /// **'Heure de fin'** + String get endTimeLabel; + + /// No description provided for @annotationsLabel. + /// + /// In fr, this message translates to: + /// **'Annotations'** + String get annotationsLabel; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en', 'fr', 'nl'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + case 'fr': + return AppLocalizationsFr(); + case 'nl': + return AppLocalizationsNl(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.'); +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000..a3304c1 --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,1208 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get cancel => 'Cancel'; + + @override + String get save => 'Save'; + + @override + String get create => 'Create'; + + @override + String get delete => 'Delete'; + + @override + String get close => 'Close'; + + @override + String get edit => 'Edit'; + + @override + String get actions => 'Actions'; + + @override + String get status => 'Status'; + + @override + String get no => 'No'; + + @override + String get name => 'Name'; + + @override + String get type => 'Type'; + + @override + String get date => 'Date'; + + @override + String get loginSuccess => 'Login successful'; + + @override + String get loginError => 'An error occurred during login'; + + @override + String get rememberMe => 'Remember me'; + + @override + String get connect => 'LOG IN'; + + @override + String get menuApplications => 'Applications'; + + @override + String get menuConfigurations => 'Configurations'; + + @override + String get menuResources => 'Resources'; + + @override + String get menuStatistics => 'Statistics'; + + @override + String get menuNotifications => 'Notifications'; + + @override + String get menuUsers => 'Users'; + + @override + String get menuApiKeys => 'API Keys'; + + @override + String get noPlansAvailable => 'No plans available. Create a plan first.'; + + @override + String get noPlan => 'No plan (unlimited)'; + + @override + String get unlimitedStorage => 'Unlimited storage'; + + @override + String get unlimitedAI => 'Unlimited AI'; + + @override + String aiRequestsPerMonth(int count) { + return '$count AI req/month'; + } + + @override + String get storageLabel => 'Storage'; + + @override + String get aiThisMonthLabel => 'AI this month'; + + @override + String get unlimited => 'Unlimited'; + + @override + String get requestsAbbr => 'req'; + + @override + String get planUpdated => 'Plan updated'; + + @override + String errorMessage(String error) { + return 'Error: $error'; + } + + @override + String get switchInstance => 'Switch instance'; + + @override + String get noInstanceFound => 'No instance found'; + + @override + String get configurePlan => 'Configure plan'; + + @override + String get tooltipSwitchInstance => 'Switch instance'; + + @override + String get usersTitle => 'Users'; + + @override + String get createUserBtn => 'Create user'; + + @override + String get createUserTitle => 'Create user'; + + @override + String get editUserTitle => 'Edit user'; + + @override + String get deleteUserTitle => 'Delete user'; + + @override + String deleteUserConfirm(String email) { + return 'Delete $email?'; + } + + @override + String get noUsers => 'No users'; + + @override + String get email => 'Email'; + + @override + String get firstName => 'First name'; + + @override + String get lastName => 'Last name'; + + @override + String get password => 'Password'; + + @override + String get role => 'Role'; + + @override + String get tooltipEdit => 'Edit'; + + @override + String get tooltipDelete => 'Delete'; + + @override + String get notificationsTitle => 'Notifications'; + + @override + String get newMessage => 'New message'; + + @override + String get messageTitle => 'Title'; + + @override + String get messageBody => 'Message'; + + @override + String get schedule => 'Schedule'; + + @override + String get time => 'Time'; + + @override + String get send => 'Send'; + + @override + String get cancelNotification => 'Cancel notification'; + + @override + String cancelNotificationConfirm(String title) { + return 'Cancel « $title »?'; + } + + @override + String get allNotifications => 'All'; + + @override + String get sentNotifications => 'Sent'; + + @override + String get scheduledNotifications => 'Scheduled'; + + @override + String get failedNotifications => 'Failed'; + + @override + String get noNotifications => 'No notifications sent'; + + @override + String get noNotificationsForFilter => 'No notifications for this filter'; + + @override + String get topic => 'Topic'; + + @override + String get tooltipCancelNotification => 'Cancel'; + + @override + String resultsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count results', + one: '$count result', + ); + return '$_temp0'; + } + + @override + String get apiKeysTitle => 'API Keys'; + + @override + String get createApiKey => 'Create API key'; + + @override + String get createKeyBtn => 'Create key'; + + @override + String get appType => 'Application type'; + + @override + String get noExpiration => 'No expiration'; + + @override + String expiresOn(String date) { + return 'Expires on $date'; + } + + @override + String get choose => 'Choose'; + + @override + String get removeExpiration => 'Remove expiration'; + + @override + String get apiKeyCreatedTitle => 'API key created'; + + @override + String get copyKeyWarning => 'Copy this key now — it won\'t be shown again.'; + + @override + String get copy => 'Copy'; + + @override + String get copiedKey => 'I\'ve copied the key'; + + @override + String get revokeApiKeyTitle => 'Revoke API key'; + + @override + String revokeApiKeyConfirm(String name) { + return 'Revoke « $name »? Apps using this key will lose access.'; + } + + @override + String get revoke => 'Revoke'; + + @override + String get noApiKeys => 'No API keys'; + + @override + String get createdOn => 'Created on'; + + @override + String get expiration => 'Expiration'; + + @override + String get activeKey => 'Active'; + + @override + String get revokedKey => 'Revoked'; + + @override + String get tooltipRevoke => 'Revoke'; + + @override + String get statisticsTitle => 'Statistics'; + + @override + String get statsLoadError => 'Unable to load statistics'; + + @override + String get statsNoData => 'No data yet for this period'; + + @override + String statsNoDataForType(String type) { + return 'No events received for type \"$type\"'; + } + + @override + String get statsSessions => 'Sessions'; + + @override + String get statsAvgDuration => 'Avg. duration'; + + @override + String get statsTopApp => 'Top app'; + + @override + String get statsTopLang => 'Top language'; + + @override + String get statsVisitsByDay => 'Visits per day'; + + @override + String get statsAll => 'All'; + + @override + String get statsTopSections => 'Top sections'; + + @override + String get statsApps => 'Apps'; + + @override + String get statsLanguages => 'Languages'; + + @override + String get statsTopPOI => 'Top POI'; + + @override + String get statsPOI => 'POI'; + + @override + String get statsTaps => 'Taps'; + + @override + String get statsTopAgenda => 'Top agenda events'; + + @override + String get statsEvent => 'Event'; + + @override + String get statsQuiz => 'Quiz'; + + @override + String get statsSection => 'Section'; + + @override + String get statsAvgScore => 'Avg. score'; + + @override + String get statsCompletions => 'Completions'; + + @override + String get statsGames => 'Games'; + + @override + String get statsGameType => 'Type'; + + @override + String get statsArticles => 'Articles read'; + + @override + String get statsReadings => 'Reads'; + + @override + String get statsMenuTitle => 'Menu'; + + @override + String get statsMenuItem => 'Item'; + + @override + String get statsQrScans => 'QR Scans'; + + @override + String get statsTotal => 'Total'; + + @override + String get statsValid => 'Valid'; + + @override + String get statsInvalid => 'Invalid'; + + @override + String get statsViews => 'Views'; + + @override + String get noData => 'No data'; + + @override + String get errorOccurred => 'An error occurred'; + + @override + String get yes => 'Yes'; + + @override + String get newConfiguration => 'New configuration'; + + @override + String get configNameLabel => 'Name:'; + + @override + String get orText => 'or'; + + @override + String get importLabel => 'Import'; + + @override + String get configNameRequired => 'Please specify a name for the new visit'; + + @override + String get configCreatedSuccess => 'Configuration created successfully'; + + @override + String configExportSuccess(String path) { + return 'Configuration exported successfully, file is at: $path'; + } + + @override + String get configExportFailed => 'Configuration export failed'; + + @override + String get configDeletedSuccess => 'Configuration deleted successfully'; + + @override + String get configSavedSuccess => 'Configuration saved successfully'; + + @override + String get configDeleteConfirm => + 'Are you sure you want to delete this configuration?'; + + @override + String get newSection => 'New section'; + + @override + String get newSubSection => 'New sub section'; + + @override + String get sectionNameLabel => 'Name:'; + + @override + String get sectionTypeLabel => 'Type:'; + + @override + String get sectionNameRequired => 'Please specify a name for the new section'; + + @override + String get sectionCreatedSuccess => 'Section created successfully!'; + + @override + String get sectionDeleteConfirm => + 'Are you sure you want to delete this section?'; + + @override + String get sectionSavedSuccess => 'Section saved successfully'; + + @override + String get sectionTranslationSaved => + 'Section translations saved successfully'; + + @override + String get sectionLoadError => 'An error occurred while loading the section'; + + @override + String get qrCodeCopied => 'This QR code has been copied to the clipboard'; + + @override + String get beaconLabel => 'Beacon:'; + + @override + String get beaconIdLabel => 'Beacon ID:'; + + @override + String get beaconMustBeNumber => 'This must be a number'; + + @override + String get identifierLabel => 'Identifier:'; + + @override + String get displayTitleLabel => 'Display title:'; + + @override + String get imageLabel => 'Image:'; + + @override + String get backgroundImageLabel => 'Background image:'; + + @override + String get questionInputLabel => 'Question:'; + + @override + String get videoUrlLabel => 'Video URL:'; + + @override + String get webUrlLabel => 'Website URL:'; + + @override + String get subSectionUpdatedSuccess => 'Sub section updated successfully'; + + @override + String get subSectionUpdateError => + 'An error occurred while updating the sub section'; + + @override + String get subSectionDeletedSuccess => 'Sub section deleted successfully'; + + @override + String get subSectionDeleteError => + 'An error occurred while deleting the sub section'; + + @override + String get subSectionOrderUpdatedSuccess => + 'Sub section order updated successfully'; + + @override + String get subSectionOrderUpdateError => + 'An error occurred while updating the sub section order'; + + @override + String get subSectionCreatedSuccess => 'Sub section created successfully'; + + @override + String get subSectionCreateError => + 'An error occurred while creating the sub section'; + + @override + String get questionsLabel => 'Questions'; + + @override + String get questionLabel => 'Question'; + + @override + String get editQuestion => 'Edit question'; + + @override + String get questionDeleteConfirm => + 'Are you sure you want to delete this question?'; + + @override + String get questionsLoadError => + 'An error occurred while loading the questions'; + + @override + String get quizBadScore => 'Bad score'; + + @override + String get quizMediumScore => 'Medium score'; + + @override + String get quizGoodScore => 'Good score'; + + @override + String get quizExcellentScore => 'Excellent score'; + + @override + String get quizBadScoreMsg => 'Message for a bad score'; + + @override + String get quizMediumScoreMsg => 'Message for a medium score'; + + @override + String get quizGoodScoreMsg => 'Message for a good score'; + + @override + String get quizExcellentScoreMsg => 'Message for an excellent score'; + + @override + String get questionOrderUpdatedSuccess => + 'Question order updated successfully'; + + @override + String get questionOrderUpdateError => + 'An error occurred while updating the question order'; + + @override + String get questionCreatedSuccess => 'Question created successfully'; + + @override + String get questionCreateError => + 'An error occurred while creating the question'; + + @override + String get questionUpdatedSuccess => 'Question updated successfully'; + + @override + String get questionUpdateError => + 'An error occurred while updating the question'; + + @override + String get questionDeletedSuccess => 'Question deleted successfully'; + + @override + String get questionDeleteError => + 'An error occurred while deleting the question'; + + @override + String get translationIncomplete => 'Translation is incomplete'; + + @override + String get geopointCreatedSuccess => 'Point created successfully'; + + @override + String get geopointCreateError => + 'An error occurred while creating the point'; + + @override + String get geopointUpdatedSuccess => 'Point updated successfully'; + + @override + String get geopointUpdateError => + 'An error occurred while updating the point'; + + @override + String get geopointDeletedSuccess => 'Point deleted successfully'; + + @override + String get geopointDeleteError => + 'An error occurred while deleting the point'; + + @override + String get categoryCreatedSuccess => 'Category created successfully'; + + @override + String get categoryCreateError => + 'An error occurred while creating the category'; + + @override + String get categoryUpdatedSuccess => 'Category updated successfully'; + + @override + String get categoryUpdateError => + 'An error occurred while updating the category'; + + @override + String get eventCreatedSuccess => 'Event created successfully'; + + @override + String get eventCreateError => 'An error occurred while creating the event'; + + @override + String get eventUpdatedSuccess => 'Event updated successfully'; + + @override + String get eventUpdateError => 'An error occurred while updating the event'; + + @override + String get eventDeletedSuccess => 'Event deleted successfully'; + + @override + String get eventDeleteError => 'An error occurred while deleting the event'; + + @override + String get annotationCreatedSuccess => 'Annotation created successfully'; + + @override + String get annotationCreateError => + 'An error occurred while creating the annotation'; + + @override + String get annotationUpdatedSuccess => 'Annotation updated successfully'; + + @override + String get annotationUpdateError => + 'An error occurred while updating the annotation'; + + @override + String get programmeBlockCreatedSuccess => 'Block created successfully'; + + @override + String get programmeBlockCreateError => + 'An error occurred while creating the block'; + + @override + String get programmeBlockUpdatedSuccess => 'Block updated successfully'; + + @override + String get programmeBlockUpdateError => + 'An error occurred while updating the block'; + + @override + String get pathCreatedSuccess => 'Path created successfully'; + + @override + String get pathCreateError => 'An error occurred while creating the path'; + + @override + String get pathUpdatedSuccess => 'Path updated successfully'; + + @override + String get pathUpdateError => 'An error occurred while updating the path'; + + @override + String get stepCreatedSuccess => 'Step created successfully'; + + @override + String get stepCreateError => 'An error occurred while creating the step'; + + @override + String get stepUpdatedSuccess => 'Step updated successfully'; + + @override + String get stepUpdateError => 'An error occurred while updating the step'; + + @override + String get agendaEventsLabel => 'Events'; + + @override + String get agendaEventFallback => 'Event'; + + @override + String get addEvent => 'Add an event'; + + @override + String get noEvents => 'No events'; + + @override + String get noAddress => 'No address'; + + @override + String get onlineLabel => 'Online:'; + + @override + String get mapViewLabel => 'Map view:'; + + @override + String get mapServiceLabel => 'Map service:'; + + @override + String get jsonFilesLabel => 'JSON files:'; + + @override + String get jsonLabel => 'JSON'; + + @override + String get guidedPathsLabel => 'Guided Paths'; + + @override + String get addPath => 'Add a path'; + + @override + String get noPathConfigured => 'No path configured'; + + @override + String get pathOrderUpdateError => 'Error updating order'; + + @override + String get pathNoTitle => 'Untitled path'; + + @override + String get pathDeletedSuccess => 'Path deleted successfully'; + + @override + String get pathDeleteError => 'Error deleting path'; + + @override + String get pathsLabel => 'Paths'; + + @override + String get pointsOfInterestLabel => 'Points of Interest'; + + @override + String get geopointsLabel => 'Geographic points'; + + @override + String get searchLabel => 'Search:'; + + @override + String get geopointsLoadError => 'Error loading geographic points'; + + @override + String get geopointDeleteConfirm => + 'Are you sure you want to delete this geographic point?'; + + @override + String get serviceLabel => 'Service:'; + + @override + String get centerPointLabel => 'Center point:'; + + @override + String get iconLabel => 'Icon:'; + + @override + String get listViewLabel => 'List view:'; + + @override + String get typeLabel => 'Type:'; + + @override + String get zoomLabel => 'Zoom:'; + + @override + String get categoriesLabel => 'Categories:'; + + @override + String get startDateLabel => 'Start date'; + + @override + String get notDefined => 'Not defined'; + + @override + String get endDateLabel => 'End date'; + + @override + String get programmeLabel => 'Programme'; + + @override + String get addBlock => 'Add a block'; + + @override + String get blockFallback => 'Block'; + + @override + String get noBlocks => 'No programme blocks defined'; + + @override + String get programmeBlockDeletedSuccess => 'Block deleted successfully'; + + @override + String get programmeBlockDeleteError => 'Error deleting block'; + + @override + String get baseMapLabel => 'Base map'; + + @override + String get mapLabel => 'Map:'; + + @override + String get globalAnnotationsLabel => 'Global annotations'; + + @override + String get addAnnotation => 'Add an annotation'; + + @override + String get noAnnotations => 'No global annotations'; + + @override + String get annotationFallback => 'Annotation'; + + @override + String get annotationDeletedSuccess => 'Annotation deleted'; + + @override + String get annotationDeleteError => 'Error deleting'; + + @override + String get agendaEventCreatedSuccess => 'Event created successfully'; + + @override + String get agendaEventCreateError => + 'An error occurred while creating the event'; + + @override + String get agendaEventUpdatedSuccess => 'Event updated successfully'; + + @override + String get agendaEventUpdateError => + 'An error occurred while updating the event'; + + @override + String get agendaEventDeletedSuccess => 'Event deleted successfully'; + + @override + String get agendaEventDeleteError => + 'An error occurred while deleting the event'; + + @override + String get newResource => 'New resource'; + + @override + String get resourceCreatedSuccess => 'Resource created successfully'; + + @override + String get resourceCreateError => + 'An error occurred while creating the resource'; + + @override + String get resourceUpdatedSuccess => 'Resource updated successfully'; + + @override + String get resourceNoFileLoaded => 'No file has been loaded'; + + @override + String get resourceNameRequired => 'Please give the resource a name'; + + @override + String get resourceTooLarge => 'Error: resource size must be under 1.5MB'; + + @override + String get resourceInvalidUrl => 'The URL is invalid'; + + @override + String get resourceUrlRequired => 'Please fill in the URL field'; + + @override + String get generalInfo => 'General information'; + + @override + String get mainImageLabel => 'Main image:'; + + @override + String get loaderLabel => 'Loader:'; + + @override + String get primaryColorLabel => 'Primary color:'; + + @override + String get secondaryColorLabel => 'Secondary color:'; + + @override + String get layoutLabel => 'Layout:'; + + @override + String get layoutGrid => 'Grid'; + + @override + String get languagesLabel => 'Languages:'; + + @override + String get featuredEventLabel => 'Featured event:'; + + @override + String get chooseEvent => 'Choose an event'; + + @override + String get noneOption => 'None'; + + @override + String get aiAssistantLabel => 'AI assistant:'; + + @override + String get appUpdatedSuccess => 'Mobile application updated'; + + @override + String get configActivatedSuccess => 'Configuration activated successfully'; + + @override + String get configDeactivatedSuccess => + 'Configuration deactivated successfully'; + + @override + String get configRemoveConfirm => + 'Are you sure you want to remove this configuration from the application?'; + + @override + String get configRemovedSuccess => + 'Configuration removed from application successfully'; + + @override + String get configRemoveError => + 'An error occurred while removing the configuration'; + + @override + String get phoneConfigTitle => 'Configurations per device'; + + @override + String get addConfig => 'Add a configuration'; + + @override + String get selectConfigToAdd => 'Select a configuration to add'; + + @override + String get noItems => 'No items to display'; + + @override + String get add => 'Add'; + + @override + String pinCode(String code) { + return 'Pin code: $code'; + } + + @override + String get tablets => 'Tablets'; + + @override + String stepsCount(int count) { + return '$count steps'; + } + + @override + String get displayedDescriptionLabel => 'Displayed description:'; + + @override + String get displayedContentLabel => 'Displayed content:'; + + @override + String get displayedNameLabel => 'Display name:'; + + @override + String get startTimeLabel => 'Start time'; + + @override + String get noAnnotationConfigured => 'No annotations configured.'; + + @override + String get newStepTitle => 'New step'; + + @override + String get editStepTitle => 'Edit step'; + + @override + String get stepTitleLabel => 'Step title'; + + @override + String get stepDescriptionLabel => 'Step description'; + + @override + String get stepLocationLabel => 'Step location:'; + + @override + String get initiallyHiddenLabel => 'Initially hidden'; + + @override + String get lockedLabel => 'Locked'; + + @override + String get durationSecondsLabel => 'Duration (seconds):'; + + @override + String get triggerGeopointLabel => 'Trigger GeoPoint (ID):'; + + @override + String get questionsChallengesLabel => 'Questions / Challenges'; + + @override + String get noQuestionsConfigured => 'No questions configured.'; + + @override + String get newAgendaEventTitle => 'New event'; + + @override + String get editAgendaEventTitle => 'Edit event'; + + @override + String get eventTitleLabel => 'Event title'; + + @override + String get eventDescriptionLabel => 'Event description'; + + @override + String get startDateColonLabel => 'Start date:'; + + @override + String get videoResourceLabel => 'Video:'; + + @override + String get directVideoLinkLabel => 'Direct video link:'; + + @override + String get phoneLabel => 'Phone:'; + + @override + String get locationGeometryLabel => 'Location / Geometry'; + + @override + String get geometryLabel => 'Geometry:'; + + @override + String get materialIconLabel => 'Icon (material):'; + + @override + String get linearLabel => 'Linear:'; + + @override + String get requiredSuccessLabel => 'Required success:'; + + @override + String get pathStepsLabel => 'Path steps'; + + @override + String get noStepConfigured => 'No point/step configured.'; + + @override + String get stepFallback => 'Step'; + + @override + String get questionAskedLabel => 'Question asked:'; + + @override + String get questionTitleLabel => 'Question title'; + + @override + String get expectedAnswerLabel => 'Expected answer:'; + + @override + String get expectedAnswerModalLabel => 'Expected answer'; + + @override + String get validationNote => + 'Validation is done by comparison (case-insensitive).'; + + @override + String get possibleAnswersLabel => 'Possible answers:'; + + @override + String get noAnswerDefined => 'No answer defined. Add at least one.'; + + @override + String get correctAnswer => 'Correct answer ✓'; + + @override + String get wrongAnswer => 'Wrong answer'; + + @override + String get answerNote => '✓ = correct answer. Multiple can be correct.'; + + @override + String get geopointZoneTitle => 'Geographic point / Zone'; + + @override + String get geometryTypeLabel => 'Geometry (Point/Line/Zone):'; + + @override + String get phoneModalLabel => 'Phone'; + + @override + String get categoryLabel => 'Category:'; + + @override + String get startMessageLabel => 'Start message:'; + + @override + String get startMessageModalLabel => 'Start message'; + + @override + String get createCategoryTitle => 'Create category'; + + @override + String get editCategoryTitle => 'Edit category'; + + @override + String get categoryIconLabel => 'Category icon:'; + + @override + String get selectResource => 'Select a resource'; + + @override + String get createPdfTitle => 'Create PDF'; + + @override + String get answersLabel => 'Answers'; + + @override + String get answerLabel => 'Answer'; + + @override + String get createAnswerTitle => 'Create answer'; + + @override + String get editAnswerTitle => 'Edit answer'; + + @override + String get answerValidNote => 'If checked, the answer is valid'; + + @override + String get selectConfiguration => 'Select a configuration'; + + @override + String get noConfigFound => 'No configuration found'; + + @override + String get backgroundColorLabel => 'Background color:'; + + @override + String get updateTabletBtn => 'Update tablet'; + + @override + String get kioskUpdatedSuccess => 'Kiosk updated'; + + @override + String get webViewError => + 'The web page cannot be displayed, the URL is incorrect or empty'; + + @override + String get download => 'Download'; + + @override + String get resourceDeleteConfirm => + 'Are you sure you want to delete this resource?'; + + @override + String get categoriesTitle => 'Categories'; + + @override + String get endTimeLabel => 'End time'; + + @override + String get annotationsLabel => 'Annotations'; +} diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart new file mode 100644 index 0000000..6bb606f --- /dev/null +++ b/lib/l10n/app_localizations_fr.dart @@ -0,0 +1,1239 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for French (`fr`). +class AppLocalizationsFr extends AppLocalizations { + AppLocalizationsFr([String locale = 'fr']) : super(locale); + + @override + String get cancel => 'Annuler'; + + @override + String get save => 'Enregistrer'; + + @override + String get create => 'Créer'; + + @override + String get delete => 'Supprimer'; + + @override + String get close => 'Fermer'; + + @override + String get edit => 'Modifier'; + + @override + String get actions => 'Actions'; + + @override + String get status => 'Statut'; + + @override + String get no => 'Non'; + + @override + String get name => 'Nom'; + + @override + String get type => 'Type'; + + @override + String get date => 'Date'; + + @override + String get loginSuccess => 'Connexion réussie'; + + @override + String get loginError => 'Un problème est survenu lors de la connexion'; + + @override + String get rememberMe => 'Se souvenir de moi'; + + @override + String get connect => 'SE CONNECTER'; + + @override + String get menuApplications => 'Applications'; + + @override + String get menuConfigurations => 'Configurations'; + + @override + String get menuResources => 'Ressources'; + + @override + String get menuStatistics => 'Statistiques'; + + @override + String get menuNotifications => 'Notifications'; + + @override + String get menuUsers => 'Utilisateurs'; + + @override + String get menuApiKeys => 'Clés API'; + + @override + String get noPlansAvailable => + 'Aucun plan disponible. Créez d\'abord un plan.'; + + @override + String get noPlan => 'Aucun plan (illimité)'; + + @override + String get unlimitedStorage => 'Stockage illimité'; + + @override + String get unlimitedAI => 'IA illimitée'; + + @override + String aiRequestsPerMonth(int count) { + return '$count req IA/mois'; + } + + @override + String get storageLabel => 'Stockage'; + + @override + String get aiThisMonthLabel => 'IA ce mois'; + + @override + String get unlimited => 'Illimité'; + + @override + String get requestsAbbr => 'req'; + + @override + String get planUpdated => 'Plan mis à jour'; + + @override + String errorMessage(String error) { + return 'Erreur : $error'; + } + + @override + String get switchInstance => 'Changer d\'instance'; + + @override + String get noInstanceFound => 'Aucune instance trouvée'; + + @override + String get configurePlan => 'Configurer le plan'; + + @override + String get tooltipSwitchInstance => 'Changer d\'instance'; + + @override + String get usersTitle => 'Utilisateurs'; + + @override + String get createUserBtn => 'Créer un utilisateur'; + + @override + String get createUserTitle => 'Créer un utilisateur'; + + @override + String get editUserTitle => 'Modifier l\'utilisateur'; + + @override + String get deleteUserTitle => 'Supprimer l\'utilisateur'; + + @override + String deleteUserConfirm(String email) { + return 'Supprimer $email ?'; + } + + @override + String get noUsers => 'Aucun utilisateur'; + + @override + String get email => 'Email'; + + @override + String get firstName => 'Prénom'; + + @override + String get lastName => 'Nom'; + + @override + String get password => 'Mot de passe'; + + @override + String get role => 'Rôle'; + + @override + String get tooltipEdit => 'Modifier'; + + @override + String get tooltipDelete => 'Supprimer'; + + @override + String get notificationsTitle => 'Notifications'; + + @override + String get newMessage => 'Nouveau message'; + + @override + String get messageTitle => 'Titre'; + + @override + String get messageBody => 'Message'; + + @override + String get schedule => 'Planifier'; + + @override + String get time => 'Heure'; + + @override + String get send => 'Envoyer'; + + @override + String get cancelNotification => 'Annuler la notification'; + + @override + String cancelNotificationConfirm(String title) { + return 'Annuler « $title » ?'; + } + + @override + String get allNotifications => 'Toutes'; + + @override + String get sentNotifications => 'Envoyées'; + + @override + String get scheduledNotifications => 'Planifiées'; + + @override + String get failedNotifications => 'Échouées'; + + @override + String get noNotifications => 'Aucune notification envoyée'; + + @override + String get noNotificationsForFilter => 'Aucune notification pour ce filtre'; + + @override + String get topic => 'Topic'; + + @override + String get tooltipCancelNotification => 'Annuler'; + + @override + String resultsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count résultats', + one: '$count résultat', + ); + return '$_temp0'; + } + + @override + String get apiKeysTitle => 'Clés API'; + + @override + String get createApiKey => 'Créer une clé API'; + + @override + String get createKeyBtn => 'Créer une clé'; + + @override + String get appType => 'Type d\'application'; + + @override + String get noExpiration => 'Pas d\'expiration'; + + @override + String expiresOn(String date) { + return 'Expire le $date'; + } + + @override + String get choose => 'Choisir'; + + @override + String get removeExpiration => 'Supprimer l\'expiration'; + + @override + String get apiKeyCreatedTitle => 'Clé API créée'; + + @override + String get copyKeyWarning => + 'Copiez cette clé maintenant — elle ne sera plus affichée.'; + + @override + String get copy => 'Copier'; + + @override + String get copiedKey => 'J\'ai copié la clé'; + + @override + String get revokeApiKeyTitle => 'Révoquer la clé API'; + + @override + String revokeApiKeyConfirm(String name) { + return 'Révoquer « $name » ? Les apps utilisant cette clé perdront l\'accès.'; + } + + @override + String get revoke => 'Révoquer'; + + @override + String get noApiKeys => 'Aucune clé API'; + + @override + String get createdOn => 'Créée le'; + + @override + String get expiration => 'Expiration'; + + @override + String get activeKey => 'Active'; + + @override + String get revokedKey => 'Révoquée'; + + @override + String get tooltipRevoke => 'Révoquer'; + + @override + String get statisticsTitle => 'Statistiques'; + + @override + String get statsLoadError => 'Impossible de charger les statistiques'; + + @override + String get statsNoData => 'Pas encore de données pour cette période'; + + @override + String statsNoDataForType(String type) { + return 'Aucun event reçu pour le type \"$type\"'; + } + + @override + String get statsSessions => 'Sessions'; + + @override + String get statsAvgDuration => 'Durée moy.'; + + @override + String get statsTopApp => 'App top'; + + @override + String get statsTopLang => 'Langue top'; + + @override + String get statsVisitsByDay => 'Visites par jour'; + + @override + String get statsAll => 'Tous'; + + @override + String get statsTopSections => 'Top sections'; + + @override + String get statsApps => 'Apps'; + + @override + String get statsLanguages => 'Langues'; + + @override + String get statsTopPOI => 'Top POI'; + + @override + String get statsPOI => 'POI'; + + @override + String get statsTaps => 'Taps'; + + @override + String get statsTopAgenda => 'Top événements agenda'; + + @override + String get statsEvent => 'Événement'; + + @override + String get statsQuiz => 'Quiz'; + + @override + String get statsSection => 'Section'; + + @override + String get statsAvgScore => 'Score moy'; + + @override + String get statsCompletions => 'Complétions'; + + @override + String get statsGames => 'Jeux'; + + @override + String get statsGameType => 'Type'; + + @override + String get statsArticles => 'Articles lus'; + + @override + String get statsReadings => 'Lectures'; + + @override + String get statsMenuTitle => 'Menu'; + + @override + String get statsMenuItem => 'Item'; + + @override + String get statsQrScans => 'QR Scans'; + + @override + String get statsTotal => 'Total'; + + @override + String get statsValid => 'Valides'; + + @override + String get statsInvalid => 'Invalides'; + + @override + String get statsViews => 'Vues'; + + @override + String get noData => 'Aucune donnée'; + + @override + String get errorOccurred => 'Une erreur est survenue'; + + @override + String get yes => 'Oui'; + + @override + String get newConfiguration => 'Nouvelle configuration'; + + @override + String get configNameLabel => 'Nom :'; + + @override + String get orText => 'ou'; + + @override + String get importLabel => 'Importer'; + + @override + String get configNameRequired => + 'Veuillez spécifier un nom pour la nouvelle visite'; + + @override + String get configCreatedSuccess => 'La configuration a été créée avec succès'; + + @override + String configExportSuccess(String path) { + return 'L\'export de la configuration a réussi, le document se trouve à cet endroit : $path'; + } + + @override + String get configExportFailed => 'L\'export de la configuration a échoué'; + + @override + String get configDeletedSuccess => + 'La configuration a été supprimée avec succès'; + + @override + String get configSavedSuccess => + 'La configuration a été sauvegardée avec succès'; + + @override + String get configDeleteConfirm => + 'Êtes-vous sûr de vouloir supprimer cette configuration ?'; + + @override + String get newSection => 'Nouvelle section'; + + @override + String get newSubSection => 'Nouvelle sous section'; + + @override + String get sectionNameLabel => 'Nom :'; + + @override + String get sectionTypeLabel => 'Type:'; + + @override + String get sectionNameRequired => + 'Veuillez spécifier un nom pour la nouvelle section'; + + @override + String get sectionCreatedSuccess => 'La section a été créée avec succès !'; + + @override + String get sectionDeleteConfirm => + 'Êtes-vous sûr de vouloir supprimer cette section ?'; + + @override + String get sectionSavedSuccess => 'La section a été sauvegardée avec succès'; + + @override + String get sectionTranslationSaved => + 'Les traductions de la section ont été sauvegardées avec succès'; + + @override + String get sectionLoadError => + 'Une erreur est survenue lors de la récupération de la section'; + + @override + String get qrCodeCopied => 'Ce QR code a été copié dans le presse papier'; + + @override + String get beaconLabel => 'Beacon :'; + + @override + String get beaconIdLabel => 'Identifiant Beacon :'; + + @override + String get beaconMustBeNumber => 'Cela doit être un chiffre'; + + @override + String get identifierLabel => 'Identifiant :'; + + @override + String get displayTitleLabel => 'Titre affiché:'; + + @override + String get imageLabel => 'Image :'; + + @override + String get backgroundImageLabel => 'Image fond d\'écran :'; + + @override + String get questionInputLabel => 'Question :'; + + @override + String get videoUrlLabel => 'Url de la vidéo:'; + + @override + String get webUrlLabel => 'Url du site web:'; + + @override + String get subSectionUpdatedSuccess => + 'La sous section a été mis à jour avec succès'; + + @override + String get subSectionUpdateError => + 'Une erreur est survenue lors de la mise à jour de la sous section'; + + @override + String get subSectionDeletedSuccess => + 'La sous section a été supprimée avec succès'; + + @override + String get subSectionDeleteError => + 'Une erreur est survenue lors de la suppression de la sous section'; + + @override + String get subSectionOrderUpdatedSuccess => + 'L\'ordre des sous sections a été mis à jour avec succès'; + + @override + String get subSectionOrderUpdateError => + 'Une erreur est survenue lors de la mise à jour de l\'ordre des sous sections'; + + @override + String get subSectionCreatedSuccess => + 'La sous section a été créée avec succès'; + + @override + String get subSectionCreateError => + 'Une erreur est survenue lors de la création de la sous section'; + + @override + String get questionsLabel => 'Questions'; + + @override + String get questionLabel => 'Question'; + + @override + String get editQuestion => 'Modifier la question'; + + @override + String get questionDeleteConfirm => + 'Êtes-vous sûr de vouloir supprimer cette question ?'; + + @override + String get questionsLoadError => + 'Une erreur est survenue lors de la récupération des questions'; + + @override + String get quizBadScore => 'Mauvais score'; + + @override + String get quizMediumScore => 'Moyen score'; + + @override + String get quizGoodScore => 'Bon score'; + + @override + String get quizExcellentScore => 'Excellent score'; + + @override + String get quizBadScoreMsg => 'Message pour un mauvais score'; + + @override + String get quizMediumScoreMsg => 'Message pour un score moyen'; + + @override + String get quizGoodScoreMsg => 'Message pour un bon score'; + + @override + String get quizExcellentScoreMsg => 'Message pour un excellent score'; + + @override + String get questionOrderUpdatedSuccess => + 'L\'ordre des questions a été mis à jour avec succès'; + + @override + String get questionOrderUpdateError => + 'Une erreur est survenue lors de la mise à jour de l\'ordre des questions'; + + @override + String get questionCreatedSuccess => 'La question a été créée avec succès'; + + @override + String get questionCreateError => + 'Une erreur est survenue lors de la création de la question'; + + @override + String get questionUpdatedSuccess => + 'La question a été mis à jour avec succès'; + + @override + String get questionUpdateError => + 'Une erreur est survenue lors de la mise à jour de la question'; + + @override + String get questionDeletedSuccess => + 'La question a été supprimée avec succès'; + + @override + String get questionDeleteError => + 'Une erreur est survenue lors de la suppression de la question'; + + @override + String get translationIncomplete => 'La traduction n\'est pas complète'; + + @override + String get geopointCreatedSuccess => 'Le point a été créé avec succès'; + + @override + String get geopointCreateError => + 'Une erreur est survenue lors de la création du point'; + + @override + String get geopointUpdatedSuccess => 'Le point a été mis à jour avec succès'; + + @override + String get geopointUpdateError => + 'Une erreur est survenue lors de la mise à jour du point'; + + @override + String get geopointDeletedSuccess => 'Le point a été supprimé avec succès'; + + @override + String get geopointDeleteError => + 'Une erreur est survenue lors de la suppression du point'; + + @override + String get categoryCreatedSuccess => 'La catégorie a été créée avec succès'; + + @override + String get categoryCreateError => + 'Une erreur est survenue lors de la création de la catégorie'; + + @override + String get categoryUpdatedSuccess => + 'La catégorie a été mise à jour avec succès'; + + @override + String get categoryUpdateError => + 'Une erreur est survenue lors de la mise à jour de la catégorie'; + + @override + String get eventCreatedSuccess => 'L\'événement a été créé avec succès'; + + @override + String get eventCreateError => + 'Une erreur est survenue lors de la création de l\'événement'; + + @override + String get eventUpdatedSuccess => 'L\'événement a été mis à jour avec succès'; + + @override + String get eventUpdateError => + 'Une erreur est survenue lors de la mise à jour de l\'événement'; + + @override + String get eventDeletedSuccess => 'L\'événement a été supprimé avec succès'; + + @override + String get eventDeleteError => + 'Une erreur est survenue lors de la suppression de l\'événement'; + + @override + String get annotationCreatedSuccess => + 'L\'annotation a été créée avec succès'; + + @override + String get annotationCreateError => + 'Une erreur est survenue lors de la création de l\'annotation'; + + @override + String get annotationUpdatedSuccess => + 'L\'annotation a été mise à jour avec succès'; + + @override + String get annotationUpdateError => + 'Une erreur est survenue lors de la mise à jour de l\'annotation'; + + @override + String get programmeBlockCreatedSuccess => 'Le bloc a été créé avec succès'; + + @override + String get programmeBlockCreateError => + 'Une erreur est survenue lors de la création du bloc'; + + @override + String get programmeBlockUpdatedSuccess => + 'Le bloc a été mis à jour avec succès'; + + @override + String get programmeBlockUpdateError => + 'Une erreur est survenue lors de la mise à jour du bloc'; + + @override + String get pathCreatedSuccess => 'Le parcours a été créé avec succès'; + + @override + String get pathCreateError => + 'Une erreur est survenue lors de la création du parcours'; + + @override + String get pathUpdatedSuccess => 'Le parcours a été mis à jour avec succès'; + + @override + String get pathUpdateError => + 'Une erreur est survenue lors de la mise à jour du parcours'; + + @override + String get stepCreatedSuccess => 'L\'étape a été créée avec succès'; + + @override + String get stepCreateError => + 'Une erreur est survenue lors de la création de l\'étape'; + + @override + String get stepUpdatedSuccess => 'L\'étape a été mise à jour avec succès'; + + @override + String get stepUpdateError => + 'Une erreur est survenue lors de la mise à jour de l\'étape'; + + @override + String get agendaEventsLabel => 'Évènements'; + + @override + String get agendaEventFallback => 'Évènement'; + + @override + String get addEvent => 'Ajouter un évènement'; + + @override + String get noEvents => 'Aucun évènement'; + + @override + String get noAddress => 'Pas d\'adresse'; + + @override + String get onlineLabel => 'En ligne :'; + + @override + String get mapViewLabel => 'Vue carte :'; + + @override + String get mapServiceLabel => 'Service carte :'; + + @override + String get jsonFilesLabel => 'Fichiers json :'; + + @override + String get jsonLabel => 'JSON'; + + @override + String get guidedPathsLabel => 'Parcours Guidés'; + + @override + String get addPath => 'Ajouter un parcours'; + + @override + String get noPathConfigured => 'Aucun parcours configuré'; + + @override + String get pathOrderUpdateError => + 'Erreur lors de la mise à jour de l\'ordre'; + + @override + String get pathNoTitle => 'Parcours sans titre'; + + @override + String get pathDeletedSuccess => 'Parcours supprimé avec succès'; + + @override + String get pathDeleteError => 'Erreur lors de la suppression du parcours'; + + @override + String get pathsLabel => 'Parcours'; + + @override + String get pointsOfInterestLabel => 'Points d\'Intérêt'; + + @override + String get geopointsLabel => 'Points géographiques'; + + @override + String get searchLabel => 'Recherche :'; + + @override + String get geopointsLoadError => + 'Une erreur est survenue lors de la récupération des points géographiques'; + + @override + String get geopointDeleteConfirm => + 'Êtes-vous sûr de vouloir supprimer ce point géographique ?'; + + @override + String get serviceLabel => 'Service :'; + + @override + String get centerPointLabel => 'Point de centrage :'; + + @override + String get iconLabel => 'Icône :'; + + @override + String get listViewLabel => 'Vue liste :'; + + @override + String get typeLabel => 'Type :'; + + @override + String get zoomLabel => 'Zoom :'; + + @override + String get categoriesLabel => 'Catégories :'; + + @override + String get startDateLabel => 'Date de début'; + + @override + String get notDefined => 'Non définie'; + + @override + String get endDateLabel => 'Date de fin'; + + @override + String get programmeLabel => 'Programme'; + + @override + String get addBlock => 'Ajouter un bloc'; + + @override + String get blockFallback => 'Bloc'; + + @override + String get noBlocks => 'Aucun bloc de programme défini'; + + @override + String get programmeBlockDeletedSuccess => 'Bloc supprimé avec succès'; + + @override + String get programmeBlockDeleteError => + 'Erreur lors de la suppression du bloc'; + + @override + String get baseMapLabel => 'Carte de base'; + + @override + String get mapLabel => 'Carte :'; + + @override + String get globalAnnotationsLabel => 'Annotations globales'; + + @override + String get addAnnotation => 'Ajouter une annotation'; + + @override + String get noAnnotations => 'Aucune annotation globale'; + + @override + String get annotationFallback => 'Annotation'; + + @override + String get annotationDeletedSuccess => 'Annotation supprimée'; + + @override + String get annotationDeleteError => 'Erreur lors de la suppression'; + + @override + String get agendaEventCreatedSuccess => 'L\'événement a été créé avec succès'; + + @override + String get agendaEventCreateError => + 'Une erreur est survenue lors de la création de l\'événement'; + + @override + String get agendaEventUpdatedSuccess => + 'L\'événement a été mis à jour avec succès'; + + @override + String get agendaEventUpdateError => + 'Une erreur est survenue lors de la mise à jour de l\'événement'; + + @override + String get agendaEventDeletedSuccess => + 'L\'événement a été supprimé avec succès'; + + @override + String get agendaEventDeleteError => + 'Une erreur est survenue lors de la suppression de l\'événement'; + + @override + String get newResource => 'Nouvelle ressource'; + + @override + String get resourceCreatedSuccess => 'La ressource a été créée avec succès'; + + @override + String get resourceCreateError => + 'Une erreur est survenue lors de la création de la ressource'; + + @override + String get resourceUpdatedSuccess => + 'La ressource a été mise à jour avec succès'; + + @override + String get resourceNoFileLoaded => 'Aucun fichier n\'a été chargé'; + + @override + String get resourceNameRequired => 'Veuillez donner un nom à la ressource'; + + @override + String get resourceTooLarge => + 'Erreur, attention, la taille de la ressource doit être inférieure à 1.5Mb'; + + @override + String get resourceInvalidUrl => 'L\'url est invalide'; + + @override + String get resourceUrlRequired => 'Veuillez remplir le champ URL'; + + @override + String get generalInfo => 'Informations générales'; + + @override + String get mainImageLabel => 'Image principale :'; + + @override + String get loaderLabel => 'Loader :'; + + @override + String get primaryColorLabel => 'Couleur principale :'; + + @override + String get secondaryColorLabel => 'Couleur secondaire :'; + + @override + String get layoutLabel => 'Affichage :'; + + @override + String get layoutGrid => 'Grille'; + + @override + String get languagesLabel => 'Langues :'; + + @override + String get featuredEventLabel => 'Evènement à l\'affiche :'; + + @override + String get chooseEvent => 'Choisir un évènement'; + + @override + String get noneOption => 'Aucun'; + + @override + String get aiAssistantLabel => 'Assistant IA :'; + + @override + String get appUpdatedSuccess => 'Application mobile mise à jour'; + + @override + String get configActivatedSuccess => 'Configuration activée avec succès'; + + @override + String get configDeactivatedSuccess => 'Configuration désactivée avec succès'; + + @override + String get configRemoveConfirm => + 'Êtes-vous sûr de vouloir retirer cette configuration de l\'application ?'; + + @override + String get configRemovedSuccess => + 'La configuration a été retirée de l\'application avec succès'; + + @override + String get configRemoveError => + 'Une erreur est survenue lors du retrait de la configuration'; + + @override + String get phoneConfigTitle => 'Configurations par appareil'; + + @override + String get addConfig => 'Ajouter une configuration'; + + @override + String get selectConfigToAdd => 'Sélectionner une configuration à ajouter'; + + @override + String get noItems => 'Aucun élément à afficher'; + + @override + String get add => 'Ajouter'; + + @override + String pinCode(String code) { + return 'Code pin: $code'; + } + + @override + String get tablets => 'Tablettes'; + + @override + String stepsCount(int count) { + return '$count étapes'; + } + + @override + String get displayedDescriptionLabel => 'Description affichée:'; + + @override + String get displayedContentLabel => 'Contenu affiché :'; + + @override + String get displayedNameLabel => 'Nom affiché :'; + + @override + String get startTimeLabel => 'Heure de début'; + + @override + String get noAnnotationConfigured => 'Aucune annotation configurée.'; + + @override + String get newStepTitle => 'Nouvelle Étape'; + + @override + String get editStepTitle => 'Modifier l\'Étape'; + + @override + String get stepTitleLabel => 'Titre de l\'étape'; + + @override + String get stepDescriptionLabel => 'Description de l\'étape'; + + @override + String get stepLocationLabel => 'Emplacement de l\'étape :'; + + @override + String get initiallyHiddenLabel => 'Cachée initialement'; + + @override + String get lockedLabel => 'Verrouillée'; + + @override + String get durationSecondsLabel => 'Durée (secondes) :'; + + @override + String get triggerGeopointLabel => 'GeoPoint de déclenchement (ID) :'; + + @override + String get questionsChallengesLabel => 'Questions / Défis'; + + @override + String get noQuestionsConfigured => 'Aucune question configurée.'; + + @override + String get newAgendaEventTitle => 'Nouvel Évènement'; + + @override + String get editAgendaEventTitle => 'Modifier l\'Évènement'; + + @override + String get eventTitleLabel => 'Titre de l\'évènement'; + + @override + String get eventDescriptionLabel => 'Description de l\'évènement'; + + @override + String get startDateColonLabel => 'Date de début :'; + + @override + String get videoResourceLabel => 'Vidéo :'; + + @override + String get directVideoLinkLabel => 'Lien vidéo direct :'; + + @override + String get phoneLabel => 'Téléphone :'; + + @override + String get locationGeometryLabel => 'Localisation / Géométrie'; + + @override + String get geometryLabel => 'Géométrie :'; + + @override + String get materialIconLabel => 'Icône (material) :'; + + @override + String get linearLabel => 'Linéaire :'; + + @override + String get requiredSuccessLabel => 'Réussite requise :'; + + @override + String get pathStepsLabel => 'Étapes du parcours'; + + @override + String get noStepConfigured => 'Aucun point/étape configuré.'; + + @override + String get stepFallback => 'Étape'; + + @override + String get questionAskedLabel => 'Question posée :'; + + @override + String get questionTitleLabel => 'Intitulé de la question'; + + @override + String get expectedAnswerLabel => 'Réponse attendue :'; + + @override + String get expectedAnswerModalLabel => 'Réponse attendue'; + + @override + String get validationNote => + 'La validation se fait par comparaison (insensible à la casse).'; + + @override + String get possibleAnswersLabel => 'Réponses possibles :'; + + @override + String get noAnswerDefined => + 'Aucune réponse définie. Ajoutez-en au moins une.'; + + @override + String get correctAnswer => 'Bonne réponse ✓'; + + @override + String get wrongAnswer => 'Mauvaise réponse'; + + @override + String get answerNote => + '✓ = bonne réponse. Plusieurs peuvent être correctes.'; + + @override + String get geopointZoneTitle => 'Point géographique / Zone'; + + @override + String get geometryTypeLabel => 'Géométrie (Point/Ligne/Zone) :'; + + @override + String get phoneModalLabel => 'Téléphone'; + + @override + String get categoryLabel => 'Catégorie :'; + + @override + String get startMessageLabel => 'Message départ :'; + + @override + String get startMessageModalLabel => 'Message départ'; + + @override + String get createCategoryTitle => 'Création catégorie'; + + @override + String get editCategoryTitle => 'Modification catégorie'; + + @override + String get categoryIconLabel => 'Icône catégorie :'; + + @override + String get selectResource => 'Sélectionner une ressource'; + + @override + String get createPdfTitle => 'Création PDF'; + + @override + String get answersLabel => 'Réponses'; + + @override + String get answerLabel => 'Réponse'; + + @override + String get createAnswerTitle => 'Créer la réponse'; + + @override + String get editAnswerTitle => 'Modifier la réponse'; + + @override + String get answerValidNote => 'Si coché, la réponse est valide'; + + @override + String get selectConfiguration => 'Sélectionner une configuration'; + + @override + String get noConfigFound => 'Aucune configuration trouvée'; + + @override + String get backgroundColorLabel => 'Couleur fond d\'écran :'; + + @override + String get updateTabletBtn => 'Mettre à jour la tablette'; + + @override + String get kioskUpdatedSuccess => 'Le kiosk a été mis à jour'; + + @override + String get webViewError => + 'La page internet ne peut pas être affichée, l\'url est incorrecte ou vide'; + + @override + String get download => 'Télécharger'; + + @override + String get resourceDeleteConfirm => + 'Êtes-vous sûr de vouloir supprimer cette ressource ?'; + + @override + String get categoriesTitle => 'Catégories'; + + @override + String get endTimeLabel => 'Heure de fin'; + + @override + String get annotationsLabel => 'Annotations'; +} diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart new file mode 100644 index 0000000..f470680 --- /dev/null +++ b/lib/l10n/app_localizations_nl.dart @@ -0,0 +1,1220 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Dutch Flemish (`nl`). +class AppLocalizationsNl extends AppLocalizations { + AppLocalizationsNl([String locale = 'nl']) : super(locale); + + @override + String get cancel => 'Annuleren'; + + @override + String get save => 'Opslaan'; + + @override + String get create => 'Aanmaken'; + + @override + String get delete => 'Verwijderen'; + + @override + String get close => 'Sluiten'; + + @override + String get edit => 'Bewerken'; + + @override + String get actions => 'Acties'; + + @override + String get status => 'Status'; + + @override + String get no => 'Nee'; + + @override + String get name => 'Naam'; + + @override + String get type => 'Type'; + + @override + String get date => 'Datum'; + + @override + String get loginSuccess => 'Inloggen geslaagd'; + + @override + String get loginError => 'Er is een probleem opgetreden bij het inloggen'; + + @override + String get rememberMe => 'Onthoud mij'; + + @override + String get connect => 'INLOGGEN'; + + @override + String get menuApplications => 'Applicaties'; + + @override + String get menuConfigurations => 'Configuraties'; + + @override + String get menuResources => 'Bronnen'; + + @override + String get menuStatistics => 'Statistieken'; + + @override + String get menuNotifications => 'Meldingen'; + + @override + String get menuUsers => 'Gebruikers'; + + @override + String get menuApiKeys => 'API-sleutels'; + + @override + String get noPlansAvailable => + 'Geen plannen beschikbaar. Maak eerst een plan aan.'; + + @override + String get noPlan => 'Geen plan (onbeperkt)'; + + @override + String get unlimitedStorage => 'Onbeperkte opslag'; + + @override + String get unlimitedAI => 'Onbeperkte AI'; + + @override + String aiRequestsPerMonth(int count) { + return '$count AI-verz./maand'; + } + + @override + String get storageLabel => 'Opslag'; + + @override + String get aiThisMonthLabel => 'AI deze maand'; + + @override + String get unlimited => 'Onbeperkt'; + + @override + String get requestsAbbr => 'verz.'; + + @override + String get planUpdated => 'Plan bijgewerkt'; + + @override + String errorMessage(String error) { + return 'Fout: $error'; + } + + @override + String get switchInstance => 'Instantie wisselen'; + + @override + String get noInstanceFound => 'Geen instantie gevonden'; + + @override + String get configurePlan => 'Plan configureren'; + + @override + String get tooltipSwitchInstance => 'Instantie wisselen'; + + @override + String get usersTitle => 'Gebruikers'; + + @override + String get createUserBtn => 'Gebruiker aanmaken'; + + @override + String get createUserTitle => 'Gebruiker aanmaken'; + + @override + String get editUserTitle => 'Gebruiker bewerken'; + + @override + String get deleteUserTitle => 'Gebruiker verwijderen'; + + @override + String deleteUserConfirm(String email) { + return '$email verwijderen?'; + } + + @override + String get noUsers => 'Geen gebruikers'; + + @override + String get email => 'E-mail'; + + @override + String get firstName => 'Voornaam'; + + @override + String get lastName => 'Naam'; + + @override + String get password => 'Wachtwoord'; + + @override + String get role => 'Rol'; + + @override + String get tooltipEdit => 'Bewerken'; + + @override + String get tooltipDelete => 'Verwijderen'; + + @override + String get notificationsTitle => 'Meldingen'; + + @override + String get newMessage => 'Nieuw bericht'; + + @override + String get messageTitle => 'Titel'; + + @override + String get messageBody => 'Bericht'; + + @override + String get schedule => 'Plannen'; + + @override + String get time => 'Tijd'; + + @override + String get send => 'Versturen'; + + @override + String get cancelNotification => 'Melding annuleren'; + + @override + String cancelNotificationConfirm(String title) { + return '« $title » annuleren?'; + } + + @override + String get allNotifications => 'Alle'; + + @override + String get sentNotifications => 'Verzonden'; + + @override + String get scheduledNotifications => 'Gepland'; + + @override + String get failedNotifications => 'Mislukt'; + + @override + String get noNotifications => 'Geen meldingen verzonden'; + + @override + String get noNotificationsForFilter => 'Geen meldingen voor dit filter'; + + @override + String get topic => 'Onderwerp'; + + @override + String get tooltipCancelNotification => 'Annuleren'; + + @override + String resultsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count resultaten', + one: '$count resultaat', + ); + return '$_temp0'; + } + + @override + String get apiKeysTitle => 'API-sleutels'; + + @override + String get createApiKey => 'API-sleutel aanmaken'; + + @override + String get createKeyBtn => 'Sleutel aanmaken'; + + @override + String get appType => 'Applicatietype'; + + @override + String get noExpiration => 'Geen vervaldatum'; + + @override + String expiresOn(String date) { + return 'Verloopt op $date'; + } + + @override + String get choose => 'Kiezen'; + + @override + String get removeExpiration => 'Vervaldatum verwijderen'; + + @override + String get apiKeyCreatedTitle => 'API-sleutel aangemaakt'; + + @override + String get copyKeyWarning => + 'Kopieer deze sleutel nu — hij wordt niet meer weergegeven.'; + + @override + String get copy => 'Kopiëren'; + + @override + String get copiedKey => 'Ik heb de sleutel gekopieerd'; + + @override + String get revokeApiKeyTitle => 'API-sleutel intrekken'; + + @override + String revokeApiKeyConfirm(String name) { + return '« $name » intrekken? Apps die deze sleutel gebruiken verliezen toegang.'; + } + + @override + String get revoke => 'Intrekken'; + + @override + String get noApiKeys => 'Geen API-sleutels'; + + @override + String get createdOn => 'Aangemaakt op'; + + @override + String get expiration => 'Vervaldatum'; + + @override + String get activeKey => 'Actief'; + + @override + String get revokedKey => 'Ingetrokken'; + + @override + String get tooltipRevoke => 'Intrekken'; + + @override + String get statisticsTitle => 'Statistieken'; + + @override + String get statsLoadError => 'Statistieken kunnen niet worden geladen'; + + @override + String get statsNoData => 'Nog geen gegevens voor deze periode'; + + @override + String statsNoDataForType(String type) { + return 'Geen events ontvangen voor type \"$type\"'; + } + + @override + String get statsSessions => 'Sessies'; + + @override + String get statsAvgDuration => 'Gem. duur'; + + @override + String get statsTopApp => 'Top app'; + + @override + String get statsTopLang => 'Top taal'; + + @override + String get statsVisitsByDay => 'Bezoeken per dag'; + + @override + String get statsAll => 'Alle'; + + @override + String get statsTopSections => 'Top secties'; + + @override + String get statsApps => 'Apps'; + + @override + String get statsLanguages => 'Talen'; + + @override + String get statsTopPOI => 'Top POI'; + + @override + String get statsPOI => 'POI'; + + @override + String get statsTaps => 'Taps'; + + @override + String get statsTopAgenda => 'Top agenda-evenementen'; + + @override + String get statsEvent => 'Evenement'; + + @override + String get statsQuiz => 'Quiz'; + + @override + String get statsSection => 'Sectie'; + + @override + String get statsAvgScore => 'Gem. score'; + + @override + String get statsCompletions => 'Voltooiingen'; + + @override + String get statsGames => 'Spellen'; + + @override + String get statsGameType => 'Type'; + + @override + String get statsArticles => 'Gelezen artikelen'; + + @override + String get statsReadings => 'Lezingen'; + + @override + String get statsMenuTitle => 'Menu'; + + @override + String get statsMenuItem => 'Item'; + + @override + String get statsQrScans => 'QR-scans'; + + @override + String get statsTotal => 'Totaal'; + + @override + String get statsValid => 'Geldig'; + + @override + String get statsInvalid => 'Ongeldig'; + + @override + String get statsViews => 'Weergaven'; + + @override + String get noData => 'Geen gegevens'; + + @override + String get errorOccurred => 'Er is een fout opgetreden'; + + @override + String get yes => 'Ja'; + + @override + String get newConfiguration => 'Nieuwe configuratie'; + + @override + String get configNameLabel => 'Naam:'; + + @override + String get orText => 'of'; + + @override + String get importLabel => 'Importeren'; + + @override + String get configNameRequired => 'Geef een naam op voor het nieuwe bezoek'; + + @override + String get configCreatedSuccess => 'Configuratie succesvol aangemaakt'; + + @override + String configExportSuccess(String path) { + return 'Configuratie succesvol geëxporteerd, bestand op: $path'; + } + + @override + String get configExportFailed => 'Export van configuratie mislukt'; + + @override + String get configDeletedSuccess => 'Configuratie succesvol verwijderd'; + + @override + String get configSavedSuccess => 'Configuratie succesvol opgeslagen'; + + @override + String get configDeleteConfirm => + 'Weet u zeker dat u deze configuratie wilt verwijderen?'; + + @override + String get newSection => 'Nieuwe sectie'; + + @override + String get newSubSection => 'Nieuwe subsectie'; + + @override + String get sectionNameLabel => 'Naam:'; + + @override + String get sectionTypeLabel => 'Type:'; + + @override + String get sectionNameRequired => 'Geef een naam op voor de nieuwe sectie'; + + @override + String get sectionCreatedSuccess => 'Sectie succesvol aangemaakt!'; + + @override + String get sectionDeleteConfirm => + 'Weet u zeker dat u deze sectie wilt verwijderen?'; + + @override + String get sectionSavedSuccess => 'Sectie succesvol opgeslagen'; + + @override + String get sectionTranslationSaved => 'Sectievertaling succesvol opgeslagen'; + + @override + String get sectionLoadError => + 'Er is een fout opgetreden bij het laden van de sectie'; + + @override + String get qrCodeCopied => 'Deze QR-code is naar het klembord gekopieerd'; + + @override + String get beaconLabel => 'Beacon:'; + + @override + String get beaconIdLabel => 'Beacon-ID:'; + + @override + String get beaconMustBeNumber => 'Dit moet een getal zijn'; + + @override + String get identifierLabel => 'Identificator:'; + + @override + String get displayTitleLabel => 'Weergegeven titel:'; + + @override + String get imageLabel => 'Afbeelding:'; + + @override + String get backgroundImageLabel => 'Achtergrondafbeelding:'; + + @override + String get questionInputLabel => 'Vraag:'; + + @override + String get videoUrlLabel => 'Video-URL:'; + + @override + String get webUrlLabel => 'Website-URL:'; + + @override + String get subSectionUpdatedSuccess => 'Subsectie succesvol bijgewerkt'; + + @override + String get subSectionUpdateError => + 'Er is een fout opgetreden bij het bijwerken van de subsectie'; + + @override + String get subSectionDeletedSuccess => 'Subsectie succesvol verwijderd'; + + @override + String get subSectionDeleteError => + 'Er is een fout opgetreden bij het verwijderen van de subsectie'; + + @override + String get subSectionOrderUpdatedSuccess => + 'Volgorde van subsecties succesvol bijgewerkt'; + + @override + String get subSectionOrderUpdateError => + 'Er is een fout opgetreden bij het bijwerken van de volgorde'; + + @override + String get subSectionCreatedSuccess => 'Subsectie succesvol aangemaakt'; + + @override + String get subSectionCreateError => + 'Er is een fout opgetreden bij het aanmaken van de subsectie'; + + @override + String get questionsLabel => 'Vragen'; + + @override + String get questionLabel => 'Vraag'; + + @override + String get editQuestion => 'Vraag bewerken'; + + @override + String get questionDeleteConfirm => + 'Weet u zeker dat u deze vraag wilt verwijderen?'; + + @override + String get questionsLoadError => + 'Er is een fout opgetreden bij het laden van de vragen'; + + @override + String get quizBadScore => 'Slechte score'; + + @override + String get quizMediumScore => 'Gemiddelde score'; + + @override + String get quizGoodScore => 'Goede score'; + + @override + String get quizExcellentScore => 'Uitstekende score'; + + @override + String get quizBadScoreMsg => 'Bericht voor een slechte score'; + + @override + String get quizMediumScoreMsg => 'Bericht voor een gemiddelde score'; + + @override + String get quizGoodScoreMsg => 'Bericht voor een goede score'; + + @override + String get quizExcellentScoreMsg => 'Bericht voor een uitstekende score'; + + @override + String get questionOrderUpdatedSuccess => + 'Vraagvolgorde succesvol bijgewerkt'; + + @override + String get questionOrderUpdateError => + 'Er is een fout opgetreden bij het bijwerken van de vraagvolgorde'; + + @override + String get questionCreatedSuccess => 'Vraag succesvol aangemaakt'; + + @override + String get questionCreateError => + 'Er is een fout opgetreden bij het aanmaken van de vraag'; + + @override + String get questionUpdatedSuccess => 'Vraag succesvol bijgewerkt'; + + @override + String get questionUpdateError => + 'Er is een fout opgetreden bij het bijwerken van de vraag'; + + @override + String get questionDeletedSuccess => 'Vraag succesvol verwijderd'; + + @override + String get questionDeleteError => + 'Er is een fout opgetreden bij het verwijderen van de vraag'; + + @override + String get translationIncomplete => 'De vertaling is niet volledig'; + + @override + String get geopointCreatedSuccess => 'Punt succesvol aangemaakt'; + + @override + String get geopointCreateError => + 'Er is een fout opgetreden bij het aanmaken van het punt'; + + @override + String get geopointUpdatedSuccess => 'Punt succesvol bijgewerkt'; + + @override + String get geopointUpdateError => + 'Er is een fout opgetreden bij het bijwerken van het punt'; + + @override + String get geopointDeletedSuccess => 'Punt succesvol verwijderd'; + + @override + String get geopointDeleteError => + 'Er is een fout opgetreden bij het verwijderen van het punt'; + + @override + String get categoryCreatedSuccess => 'Categorie succesvol aangemaakt'; + + @override + String get categoryCreateError => + 'Er is een fout opgetreden bij het aanmaken van de categorie'; + + @override + String get categoryUpdatedSuccess => 'Categorie succesvol bijgewerkt'; + + @override + String get categoryUpdateError => + 'Er is een fout opgetreden bij het bijwerken van de categorie'; + + @override + String get eventCreatedSuccess => 'Evenement succesvol aangemaakt'; + + @override + String get eventCreateError => + 'Er is een fout opgetreden bij het aanmaken van het evenement'; + + @override + String get eventUpdatedSuccess => 'Evenement succesvol bijgewerkt'; + + @override + String get eventUpdateError => + 'Er is een fout opgetreden bij het bijwerken van het evenement'; + + @override + String get eventDeletedSuccess => 'Evenement succesvol verwijderd'; + + @override + String get eventDeleteError => + 'Er is een fout opgetreden bij het verwijderen van het evenement'; + + @override + String get annotationCreatedSuccess => 'Annotatie succesvol aangemaakt'; + + @override + String get annotationCreateError => + 'Er is een fout opgetreden bij het aanmaken van de annotatie'; + + @override + String get annotationUpdatedSuccess => 'Annotatie succesvol bijgewerkt'; + + @override + String get annotationUpdateError => + 'Er is een fout opgetreden bij het bijwerken van de annotatie'; + + @override + String get programmeBlockCreatedSuccess => 'Blok succesvol aangemaakt'; + + @override + String get programmeBlockCreateError => + 'Er is een fout opgetreden bij het aanmaken van het blok'; + + @override + String get programmeBlockUpdatedSuccess => 'Blok succesvol bijgewerkt'; + + @override + String get programmeBlockUpdateError => + 'Er is een fout opgetreden bij het bijwerken van het blok'; + + @override + String get pathCreatedSuccess => 'Parcours succesvol aangemaakt'; + + @override + String get pathCreateError => + 'Er is een fout opgetreden bij het aanmaken van het parcours'; + + @override + String get pathUpdatedSuccess => 'Parcours succesvol bijgewerkt'; + + @override + String get pathUpdateError => + 'Er is een fout opgetreden bij het bijwerken van het parcours'; + + @override + String get stepCreatedSuccess => 'Stap succesvol aangemaakt'; + + @override + String get stepCreateError => + 'Er is een fout opgetreden bij het aanmaken van de stap'; + + @override + String get stepUpdatedSuccess => 'Stap succesvol bijgewerkt'; + + @override + String get stepUpdateError => + 'Er is een fout opgetreden bij het bijwerken van de stap'; + + @override + String get agendaEventsLabel => 'Evenementen'; + + @override + String get agendaEventFallback => 'Evenement'; + + @override + String get addEvent => 'Evenement toevoegen'; + + @override + String get noEvents => 'Geen evenementen'; + + @override + String get noAddress => 'Geen adres'; + + @override + String get onlineLabel => 'Online:'; + + @override + String get mapViewLabel => 'Kaartweergave:'; + + @override + String get mapServiceLabel => 'Kaartservice:'; + + @override + String get jsonFilesLabel => 'JSON-bestanden:'; + + @override + String get jsonLabel => 'JSON'; + + @override + String get guidedPathsLabel => 'Begeleide parcours'; + + @override + String get addPath => 'Parcours toevoegen'; + + @override + String get noPathConfigured => 'Geen parcours geconfigureerd'; + + @override + String get pathOrderUpdateError => 'Fout bij het bijwerken van de volgorde'; + + @override + String get pathNoTitle => 'Parcours zonder titel'; + + @override + String get pathDeletedSuccess => 'Parcours succesvol verwijderd'; + + @override + String get pathDeleteError => 'Fout bij het verwijderen van het parcours'; + + @override + String get pathsLabel => 'Parcours'; + + @override + String get pointsOfInterestLabel => 'Bezienswaardigheden'; + + @override + String get geopointsLabel => 'Geografische punten'; + + @override + String get searchLabel => 'Zoeken:'; + + @override + String get geopointsLoadError => 'Fout bij het laden van geografische punten'; + + @override + String get geopointDeleteConfirm => + 'Weet u zeker dat u dit geografische punt wilt verwijderen?'; + + @override + String get serviceLabel => 'Service:'; + + @override + String get centerPointLabel => 'Middelpunt:'; + + @override + String get iconLabel => 'Pictogram:'; + + @override + String get listViewLabel => 'Lijstweergave:'; + + @override + String get typeLabel => 'Type:'; + + @override + String get zoomLabel => 'Zoom:'; + + @override + String get categoriesLabel => 'Categorieën:'; + + @override + String get startDateLabel => 'Startdatum'; + + @override + String get notDefined => 'Niet gedefinieerd'; + + @override + String get endDateLabel => 'Einddatum'; + + @override + String get programmeLabel => 'Programma'; + + @override + String get addBlock => 'Blok toevoegen'; + + @override + String get blockFallback => 'Blok'; + + @override + String get noBlocks => 'Geen programmablokken gedefinieerd'; + + @override + String get programmeBlockDeletedSuccess => 'Blok succesvol verwijderd'; + + @override + String get programmeBlockDeleteError => + 'Fout bij het verwijderen van het blok'; + + @override + String get baseMapLabel => 'Basiskaart'; + + @override + String get mapLabel => 'Kaart:'; + + @override + String get globalAnnotationsLabel => 'Globale annotaties'; + + @override + String get addAnnotation => 'Annotatie toevoegen'; + + @override + String get noAnnotations => 'Geen globale annotaties'; + + @override + String get annotationFallback => 'Annotatie'; + + @override + String get annotationDeletedSuccess => 'Annotatie verwijderd'; + + @override + String get annotationDeleteError => 'Fout bij verwijderen'; + + @override + String get agendaEventCreatedSuccess => 'Evenement succesvol aangemaakt'; + + @override + String get agendaEventCreateError => + 'Er is een fout opgetreden bij het aanmaken van het evenement'; + + @override + String get agendaEventUpdatedSuccess => 'Evenement succesvol bijgewerkt'; + + @override + String get agendaEventUpdateError => + 'Er is een fout opgetreden bij het bijwerken van het evenement'; + + @override + String get agendaEventDeletedSuccess => 'Evenement succesvol verwijderd'; + + @override + String get agendaEventDeleteError => + 'Er is een fout opgetreden bij het verwijderen van het evenement'; + + @override + String get newResource => 'Nieuwe bron'; + + @override + String get resourceCreatedSuccess => 'Bron succesvol aangemaakt'; + + @override + String get resourceCreateError => + 'Er is een fout opgetreden bij het aanmaken van de bron'; + + @override + String get resourceUpdatedSuccess => 'Bron succesvol bijgewerkt'; + + @override + String get resourceNoFileLoaded => 'Er is geen bestand geladen'; + + @override + String get resourceNameRequired => 'Geef de bron een naam'; + + @override + String get resourceTooLarge => + 'Fout: de bestandsgrootte moet kleiner zijn dan 1,5 MB'; + + @override + String get resourceInvalidUrl => 'De URL is ongeldig'; + + @override + String get resourceUrlRequired => 'Vul het URL-veld in'; + + @override + String get generalInfo => 'Algemene informatie'; + + @override + String get mainImageLabel => 'Hoofdafbeelding:'; + + @override + String get loaderLabel => 'Loader:'; + + @override + String get primaryColorLabel => 'Primaire kleur:'; + + @override + String get secondaryColorLabel => 'Secundaire kleur:'; + + @override + String get layoutLabel => 'Weergave:'; + + @override + String get layoutGrid => 'Raster'; + + @override + String get languagesLabel => 'Talen:'; + + @override + String get featuredEventLabel => 'Uitgelicht evenement:'; + + @override + String get chooseEvent => 'Kies een evenement'; + + @override + String get noneOption => 'Geen'; + + @override + String get aiAssistantLabel => 'AI-assistent:'; + + @override + String get appUpdatedSuccess => 'Mobiele applicatie bijgewerkt'; + + @override + String get configActivatedSuccess => 'Configuratie succesvol geactiveerd'; + + @override + String get configDeactivatedSuccess => 'Configuratie succesvol gedeactiveerd'; + + @override + String get configRemoveConfirm => + 'Weet u zeker dat u deze configuratie uit de applicatie wilt verwijderen?'; + + @override + String get configRemovedSuccess => + 'Configuratie succesvol verwijderd uit de applicatie'; + + @override + String get configRemoveError => + 'Er is een fout opgetreden bij het verwijderen van de configuratie'; + + @override + String get phoneConfigTitle => 'Configuraties per apparaat'; + + @override + String get addConfig => 'Configuratie toevoegen'; + + @override + String get selectConfigToAdd => 'Selecteer een configuratie om toe te voegen'; + + @override + String get noItems => 'Geen items om weer te geven'; + + @override + String get add => 'Toevoegen'; + + @override + String pinCode(String code) { + return 'Pincode: $code'; + } + + @override + String get tablets => 'Tablets'; + + @override + String stepsCount(int count) { + return '$count stappen'; + } + + @override + String get displayedDescriptionLabel => 'Weergegeven beschrijving:'; + + @override + String get displayedContentLabel => 'Weergegeven inhoud:'; + + @override + String get displayedNameLabel => 'Weergavenaam:'; + + @override + String get startTimeLabel => 'Begintijd'; + + @override + String get noAnnotationConfigured => 'Geen annotaties geconfigureerd.'; + + @override + String get newStepTitle => 'Nieuwe stap'; + + @override + String get editStepTitle => 'Stap bewerken'; + + @override + String get stepTitleLabel => 'Staptitel'; + + @override + String get stepDescriptionLabel => 'Stapbeschrijving'; + + @override + String get stepLocationLabel => 'Stap locatie:'; + + @override + String get initiallyHiddenLabel => 'Aanvankelijk verborgen'; + + @override + String get lockedLabel => 'Vergrendeld'; + + @override + String get durationSecondsLabel => 'Duur (seconden):'; + + @override + String get triggerGeopointLabel => 'GeoPoint trigger (ID):'; + + @override + String get questionsChallengesLabel => 'Vragen / Uitdagingen'; + + @override + String get noQuestionsConfigured => 'Geen vragen geconfigureerd.'; + + @override + String get newAgendaEventTitle => 'Nieuw evenement'; + + @override + String get editAgendaEventTitle => 'Evenement bewerken'; + + @override + String get eventTitleLabel => 'Evenementtitel'; + + @override + String get eventDescriptionLabel => 'Evenementbeschrijving'; + + @override + String get startDateColonLabel => 'Begindatum:'; + + @override + String get videoResourceLabel => 'Video:'; + + @override + String get directVideoLinkLabel => 'Directe videolink:'; + + @override + String get phoneLabel => 'Telefoon:'; + + @override + String get locationGeometryLabel => 'Locatie / Geometrie'; + + @override + String get geometryLabel => 'Geometrie:'; + + @override + String get materialIconLabel => 'Icoon (material):'; + + @override + String get linearLabel => 'Lineair:'; + + @override + String get requiredSuccessLabel => 'Vereist succes:'; + + @override + String get pathStepsLabel => 'Parcoursstappen'; + + @override + String get noStepConfigured => 'Geen punt/stap geconfigureerd.'; + + @override + String get stepFallback => 'Stap'; + + @override + String get questionAskedLabel => 'Gestelde vraag:'; + + @override + String get questionTitleLabel => 'Vraagstitel'; + + @override + String get expectedAnswerLabel => 'Verwacht antwoord:'; + + @override + String get expectedAnswerModalLabel => 'Verwacht antwoord'; + + @override + String get validationNote => + 'Validatie gebeurt door vergelijking (hoofdletterongevoelig).'; + + @override + String get possibleAnswersLabel => 'Mogelijke antwoorden:'; + + @override + String get noAnswerDefined => + 'Geen antwoord gedefinieerd. Voeg er minstens één toe.'; + + @override + String get correctAnswer => 'Correct antwoord ✓'; + + @override + String get wrongAnswer => 'Fout antwoord'; + + @override + String get answerNote => + '✓ = correct antwoord. Meerdere kunnen correct zijn.'; + + @override + String get geopointZoneTitle => 'Geografisch punt / Zone'; + + @override + String get geometryTypeLabel => 'Geometrie (Punt/Lijn/Zone):'; + + @override + String get phoneModalLabel => 'Telefoon'; + + @override + String get categoryLabel => 'Categorie:'; + + @override + String get startMessageLabel => 'Startbericht:'; + + @override + String get startMessageModalLabel => 'Startbericht'; + + @override + String get createCategoryTitle => 'Categorie aanmaken'; + + @override + String get editCategoryTitle => 'Categorie bewerken'; + + @override + String get categoryIconLabel => 'Categorie-icoon:'; + + @override + String get selectResource => 'Selecteer een resource'; + + @override + String get createPdfTitle => 'PDF aanmaken'; + + @override + String get answersLabel => 'Antwoorden'; + + @override + String get answerLabel => 'Antwoord'; + + @override + String get createAnswerTitle => 'Antwoord aanmaken'; + + @override + String get editAnswerTitle => 'Antwoord bewerken'; + + @override + String get answerValidNote => 'Indien aangevinkt, is het antwoord geldig'; + + @override + String get selectConfiguration => 'Selecteer een configuratie'; + + @override + String get noConfigFound => 'Geen configuratie gevonden'; + + @override + String get backgroundColorLabel => 'Achtergrondkleur:'; + + @override + String get updateTabletBtn => 'Tablet bijwerken'; + + @override + String get kioskUpdatedSuccess => 'Kiosk bijgewerkt'; + + @override + String get webViewError => + 'De webpagina kan niet worden weergegeven, de URL is onjuist of leeg'; + + @override + String get download => 'Downloaden'; + + @override + String get resourceDeleteConfirm => + 'Weet u zeker dat u deze resource wilt verwijderen?'; + + @override + String get categoriesTitle => 'Categorieën'; + + @override + String get endTimeLabel => 'Eindtijd'; + + @override + String get annotationsLabel => 'Annotaties'; +} diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb new file mode 100644 index 0000000..d02dbcb --- /dev/null +++ b/lib/l10n/app_nl.arb @@ -0,0 +1,461 @@ +{ + "@@locale": "nl", + + "cancel": "Annuleren", + "save": "Opslaan", + "create": "Aanmaken", + "delete": "Verwijderen", + "close": "Sluiten", + "edit": "Bewerken", + "actions": "Acties", + "status": "Status", + "no": "Nee", + "name": "Naam", + "type": "Type", + "date": "Datum", + + "loginSuccess": "Inloggen geslaagd", + "loginError": "Er is een probleem opgetreden bij het inloggen", + "rememberMe": "Onthoud mij", + "connect": "INLOGGEN", + + "menuApplications": "Applicaties", + "menuConfigurations": "Configuraties", + "menuResources": "Bronnen", + "menuStatistics": "Statistieken", + "menuNotifications": "Meldingen", + "menuUsers": "Gebruikers", + "menuApiKeys": "API-sleutels", + + "noPlansAvailable": "Geen plannen beschikbaar. Maak eerst een plan aan.", + "noPlan": "Geen plan (onbeperkt)", + "unlimitedStorage": "Onbeperkte opslag", + "unlimitedAI": "Onbeperkte AI", + "aiRequestsPerMonth": "{count} AI-verz./maand", + "@aiRequestsPerMonth": { + "placeholders": { + "count": { "type": "int" } + } + }, + "storageLabel": "Opslag", + "aiThisMonthLabel": "AI deze maand", + "unlimited": "Onbeperkt", + "requestsAbbr": "verz.", + "planUpdated": "Plan bijgewerkt", + "errorMessage": "Fout: {error}", + "@errorMessage": { + "placeholders": { + "error": { "type": "String" } + } + }, + "switchInstance": "Instantie wisselen", + "noInstanceFound": "Geen instantie gevonden", + "configurePlan": "Plan configureren", + "tooltipSwitchInstance": "Instantie wisselen", + + "usersTitle": "Gebruikers", + "createUserBtn": "Gebruiker aanmaken", + "createUserTitle": "Gebruiker aanmaken", + "editUserTitle": "Gebruiker bewerken", + "deleteUserTitle": "Gebruiker verwijderen", + "deleteUserConfirm": "{email} verwijderen?", + "@deleteUserConfirm": { + "placeholders": { + "email": { "type": "String" } + } + }, + "noUsers": "Geen gebruikers", + "email": "E-mail", + "firstName": "Voornaam", + "lastName": "Naam", + "password": "Wachtwoord", + "role": "Rol", + "tooltipEdit": "Bewerken", + "tooltipDelete": "Verwijderen", + + "notificationsTitle": "Meldingen", + "newMessage": "Nieuw bericht", + "messageTitle": "Titel", + "messageBody": "Bericht", + "schedule": "Plannen", + "time": "Tijd", + "send": "Versturen", + "cancelNotification": "Melding annuleren", + "cancelNotificationConfirm": "« {title} » annuleren?", + "@cancelNotificationConfirm": { + "placeholders": { + "title": { "type": "String" } + } + }, + "allNotifications": "Alle", + "sentNotifications": "Verzonden", + "scheduledNotifications": "Gepland", + "failedNotifications": "Mislukt", + "noNotifications": "Geen meldingen verzonden", + "noNotificationsForFilter": "Geen meldingen voor dit filter", + "topic": "Onderwerp", + "tooltipCancelNotification": "Annuleren", + "resultsCount": "{count, plural, one{{count} resultaat} other{{count} resultaten}}", + "@resultsCount": { + "placeholders": { + "count": { "type": "int" } + } + }, + + "apiKeysTitle": "API-sleutels", + "createApiKey": "API-sleutel aanmaken", + "createKeyBtn": "Sleutel aanmaken", + "appType": "Applicatietype", + "noExpiration": "Geen vervaldatum", + "expiresOn": "Verloopt op {date}", + "@expiresOn": { + "placeholders": { + "date": { "type": "String" } + } + }, + "choose": "Kiezen", + "removeExpiration": "Vervaldatum verwijderen", + "apiKeyCreatedTitle": "API-sleutel aangemaakt", + "copyKeyWarning": "Kopieer deze sleutel nu — hij wordt niet meer weergegeven.", + "copy": "Kopiëren", + "copiedKey": "Ik heb de sleutel gekopieerd", + "revokeApiKeyTitle": "API-sleutel intrekken", + "revokeApiKeyConfirm": "« {name} » intrekken? Apps die deze sleutel gebruiken verliezen toegang.", + "@revokeApiKeyConfirm": { + "placeholders": { + "name": { "type": "String" } + } + }, + "revoke": "Intrekken", + "noApiKeys": "Geen API-sleutels", + "createdOn": "Aangemaakt op", + "expiration": "Vervaldatum", + "activeKey": "Actief", + "revokedKey": "Ingetrokken", + "tooltipRevoke": "Intrekken", + + "statisticsTitle": "Statistieken", + "statsLoadError": "Statistieken kunnen niet worden geladen", + "statsNoData": "Nog geen gegevens voor deze periode", + "statsNoDataForType": "Geen events ontvangen voor type \"{type}\"", + "@statsNoDataForType": { + "placeholders": { + "type": { "type": "String" } + } + }, + "statsSessions": "Sessies", + "statsAvgDuration": "Gem. duur", + "statsTopApp": "Top app", + "statsTopLang": "Top taal", + "statsVisitsByDay": "Bezoeken per dag", + "statsAll": "Alle", + "statsTopSections": "Top secties", + "statsApps": "Apps", + "statsLanguages": "Talen", + "statsTopPOI": "Top POI", + "statsPOI": "POI", + "statsTaps": "Taps", + "statsTopAgenda": "Top agenda-evenementen", + "statsEvent": "Evenement", + "statsQuiz": "Quiz", + "statsSection": "Sectie", + "statsAvgScore": "Gem. score", + "statsCompletions": "Voltooiingen", + "statsGames": "Spellen", + "statsGameType": "Type", + "statsArticles": "Gelezen artikelen", + "statsReadings": "Lezingen", + "statsMenuTitle": "Menu", + "statsMenuItem": "Item", + "statsQrScans": "QR-scans", + "statsTotal": "Totaal", + "statsValid": "Geldig", + "statsInvalid": "Ongeldig", + "statsViews": "Weergaven", + + "noData": "Geen gegevens", + "errorOccurred": "Er is een fout opgetreden", + "yes": "Ja", + + "newConfiguration": "Nieuwe configuratie", + "configNameLabel": "Naam:", + "orText": "of", + "importLabel": "Importeren", + "configNameRequired": "Geef een naam op voor het nieuwe bezoek", + "configCreatedSuccess": "Configuratie succesvol aangemaakt", + "configExportSuccess": "Configuratie succesvol geëxporteerd, bestand op: {path}", + "@configExportSuccess": { + "placeholders": { + "path": { "type": "String" } + } + }, + "configExportFailed": "Export van configuratie mislukt", + "configDeletedSuccess": "Configuratie succesvol verwijderd", + "configSavedSuccess": "Configuratie succesvol opgeslagen", + "configDeleteConfirm": "Weet u zeker dat u deze configuratie wilt verwijderen?", + + "newSection": "Nieuwe sectie", + "newSubSection": "Nieuwe subsectie", + "sectionNameLabel": "Naam:", + "sectionTypeLabel": "Type:", + "sectionNameRequired": "Geef een naam op voor de nieuwe sectie", + "sectionCreatedSuccess": "Sectie succesvol aangemaakt!", + "sectionDeleteConfirm": "Weet u zeker dat u deze sectie wilt verwijderen?", + "sectionSavedSuccess": "Sectie succesvol opgeslagen", + "sectionTranslationSaved": "Sectievertaling succesvol opgeslagen", + "sectionLoadError": "Er is een fout opgetreden bij het laden van de sectie", + "qrCodeCopied": "Deze QR-code is naar het klembord gekopieerd", + "beaconLabel": "Beacon:", + "beaconIdLabel": "Beacon-ID:", + "beaconMustBeNumber": "Dit moet een getal zijn", + "identifierLabel": "Identificator:", + "displayTitleLabel": "Weergegeven titel:", + "imageLabel": "Afbeelding:", + "backgroundImageLabel": "Achtergrondafbeelding:", + "questionInputLabel": "Vraag:", + "videoUrlLabel": "Video-URL:", + "webUrlLabel": "Website-URL:", + + "subSectionUpdatedSuccess": "Subsectie succesvol bijgewerkt", + "subSectionUpdateError": "Er is een fout opgetreden bij het bijwerken van de subsectie", + "subSectionDeletedSuccess": "Subsectie succesvol verwijderd", + "subSectionDeleteError": "Er is een fout opgetreden bij het verwijderen van de subsectie", + "subSectionOrderUpdatedSuccess": "Volgorde van subsecties succesvol bijgewerkt", + "subSectionOrderUpdateError": "Er is een fout opgetreden bij het bijwerken van de volgorde", + "subSectionCreatedSuccess": "Subsectie succesvol aangemaakt", + "subSectionCreateError": "Er is een fout opgetreden bij het aanmaken van de subsectie", + + "questionsLabel": "Vragen", + "questionLabel": "Vraag", + "editQuestion": "Vraag bewerken", + "questionDeleteConfirm": "Weet u zeker dat u deze vraag wilt verwijderen?", + "questionsLoadError": "Er is een fout opgetreden bij het laden van de vragen", + "quizBadScore": "Slechte score", + "quizMediumScore": "Gemiddelde score", + "quizGoodScore": "Goede score", + "quizExcellentScore": "Uitstekende score", + "quizBadScoreMsg": "Bericht voor een slechte score", + "quizMediumScoreMsg": "Bericht voor een gemiddelde score", + "quizGoodScoreMsg": "Bericht voor een goede score", + "quizExcellentScoreMsg": "Bericht voor een uitstekende score", + "questionOrderUpdatedSuccess": "Vraagvolgorde succesvol bijgewerkt", + "questionOrderUpdateError": "Er is een fout opgetreden bij het bijwerken van de vraagvolgorde", + "questionCreatedSuccess": "Vraag succesvol aangemaakt", + "questionCreateError": "Er is een fout opgetreden bij het aanmaken van de vraag", + "questionUpdatedSuccess": "Vraag succesvol bijgewerkt", + "questionUpdateError": "Er is een fout opgetreden bij het bijwerken van de vraag", + "questionDeletedSuccess": "Vraag succesvol verwijderd", + "questionDeleteError": "Er is een fout opgetreden bij het verwijderen van de vraag", + "translationIncomplete": "De vertaling is niet volledig", + + "geopointCreatedSuccess": "Punt succesvol aangemaakt", + "geopointCreateError": "Er is een fout opgetreden bij het aanmaken van het punt", + "geopointUpdatedSuccess": "Punt succesvol bijgewerkt", + "geopointUpdateError": "Er is een fout opgetreden bij het bijwerken van het punt", + "geopointDeletedSuccess": "Punt succesvol verwijderd", + "geopointDeleteError": "Er is een fout opgetreden bij het verwijderen van het punt", + "categoryCreatedSuccess": "Categorie succesvol aangemaakt", + "categoryCreateError": "Er is een fout opgetreden bij het aanmaken van de categorie", + "categoryUpdatedSuccess": "Categorie succesvol bijgewerkt", + "categoryUpdateError": "Er is een fout opgetreden bij het bijwerken van de categorie", + + "eventCreatedSuccess": "Evenement succesvol aangemaakt", + "eventCreateError": "Er is een fout opgetreden bij het aanmaken van het evenement", + "eventUpdatedSuccess": "Evenement succesvol bijgewerkt", + "eventUpdateError": "Er is een fout opgetreden bij het bijwerken van het evenement", + "eventDeletedSuccess": "Evenement succesvol verwijderd", + "eventDeleteError": "Er is een fout opgetreden bij het verwijderen van het evenement", + "annotationCreatedSuccess": "Annotatie succesvol aangemaakt", + "annotationCreateError": "Er is een fout opgetreden bij het aanmaken van de annotatie", + "annotationUpdatedSuccess": "Annotatie succesvol bijgewerkt", + "annotationUpdateError": "Er is een fout opgetreden bij het bijwerken van de annotatie", + "programmeBlockCreatedSuccess": "Blok succesvol aangemaakt", + "programmeBlockCreateError": "Er is een fout opgetreden bij het aanmaken van het blok", + "programmeBlockUpdatedSuccess": "Blok succesvol bijgewerkt", + "programmeBlockUpdateError": "Er is een fout opgetreden bij het bijwerken van het blok", + + "pathCreatedSuccess": "Parcours succesvol aangemaakt", + "pathCreateError": "Er is een fout opgetreden bij het aanmaken van het parcours", + "pathUpdatedSuccess": "Parcours succesvol bijgewerkt", + "pathUpdateError": "Er is een fout opgetreden bij het bijwerken van het parcours", + "stepCreatedSuccess": "Stap succesvol aangemaakt", + "stepCreateError": "Er is een fout opgetreden bij het aanmaken van de stap", + "stepUpdatedSuccess": "Stap succesvol bijgewerkt", + "stepUpdateError": "Er is een fout opgetreden bij het bijwerken van de stap", + + "agendaEventsLabel": "Evenementen", + "agendaEventFallback": "Evenement", + "addEvent": "Evenement toevoegen", + "noEvents": "Geen evenementen", + "noAddress": "Geen adres", + "onlineLabel": "Online:", + "mapViewLabel": "Kaartweergave:", + "mapServiceLabel": "Kaartservice:", + "jsonFilesLabel": "JSON-bestanden:", + "jsonLabel": "JSON", + + "guidedPathsLabel": "Begeleide parcours", + "addPath": "Parcours toevoegen", + "noPathConfigured": "Geen parcours geconfigureerd", + "pathOrderUpdateError": "Fout bij het bijwerken van de volgorde", + "pathNoTitle": "Parcours zonder titel", + "pathDeletedSuccess": "Parcours succesvol verwijderd", + "pathDeleteError": "Fout bij het verwijderen van het parcours", + "pathsLabel": "Parcours", + + "pointsOfInterestLabel": "Bezienswaardigheden", + "geopointsLabel": "Geografische punten", + "searchLabel": "Zoeken:", + "geopointsLoadError": "Fout bij het laden van geografische punten", + "geopointDeleteConfirm": "Weet u zeker dat u dit geografische punt wilt verwijderen?", + "serviceLabel": "Service:", + "centerPointLabel": "Middelpunt:", + "iconLabel": "Pictogram:", + "listViewLabel": "Lijstweergave:", + "typeLabel": "Type:", + "zoomLabel": "Zoom:", + "categoriesLabel": "Categorieën:", + + "startDateLabel": "Startdatum", + "notDefined": "Niet gedefinieerd", + "endDateLabel": "Einddatum", + "programmeLabel": "Programma", + "addBlock": "Blok toevoegen", + "blockFallback": "Blok", + "noBlocks": "Geen programmablokken gedefinieerd", + "programmeBlockDeletedSuccess": "Blok succesvol verwijderd", + "programmeBlockDeleteError": "Fout bij het verwijderen van het blok", + "baseMapLabel": "Basiskaart", + "mapLabel": "Kaart:", + "globalAnnotationsLabel": "Globale annotaties", + "addAnnotation": "Annotatie toevoegen", + "noAnnotations": "Geen globale annotaties", + "annotationFallback": "Annotatie", + "annotationDeletedSuccess": "Annotatie verwijderd", + "annotationDeleteError": "Fout bij verwijderen", + + "agendaEventCreatedSuccess": "Evenement succesvol aangemaakt", + "agendaEventCreateError": "Er is een fout opgetreden bij het aanmaken van het evenement", + "agendaEventUpdatedSuccess": "Evenement succesvol bijgewerkt", + "agendaEventUpdateError": "Er is een fout opgetreden bij het bijwerken van het evenement", + "agendaEventDeletedSuccess": "Evenement succesvol verwijderd", + "agendaEventDeleteError": "Er is een fout opgetreden bij het verwijderen van het evenement", + + "newResource": "Nieuwe bron", + "resourceCreatedSuccess": "Bron succesvol aangemaakt", + "resourceCreateError": "Er is een fout opgetreden bij het aanmaken van de bron", + "resourceUpdatedSuccess": "Bron succesvol bijgewerkt", + "resourceNoFileLoaded": "Er is geen bestand geladen", + "resourceNameRequired": "Geef de bron een naam", + "resourceTooLarge": "Fout: de bestandsgrootte moet kleiner zijn dan 1,5 MB", + "resourceInvalidUrl": "De URL is ongeldig", + "resourceUrlRequired": "Vul het URL-veld in", + + "generalInfo": "Algemene informatie", + "mainImageLabel": "Hoofdafbeelding:", + "loaderLabel": "Loader:", + "primaryColorLabel": "Primaire kleur:", + "secondaryColorLabel": "Secundaire kleur:", + "layoutLabel": "Weergave:", + "layoutGrid": "Raster", + "languagesLabel": "Talen:", + "featuredEventLabel": "Uitgelicht evenement:", + "chooseEvent": "Kies een evenement", + "noneOption": "Geen", + "aiAssistantLabel": "AI-assistent:", + "appUpdatedSuccess": "Mobiele applicatie bijgewerkt", + "configActivatedSuccess": "Configuratie succesvol geactiveerd", + "configDeactivatedSuccess": "Configuratie succesvol gedeactiveerd", + "configRemoveConfirm": "Weet u zeker dat u deze configuratie uit de applicatie wilt verwijderen?", + "configRemovedSuccess": "Configuratie succesvol verwijderd uit de applicatie", + "configRemoveError": "Er is een fout opgetreden bij het verwijderen van de configuratie", + "phoneConfigTitle": "Configuraties per apparaat", + "addConfig": "Configuratie toevoegen", + "selectConfigToAdd": "Selecteer een configuratie om toe te voegen", + "noItems": "Geen items om weer te geven", + "add": "Toevoegen", + + "pinCode": "Pincode: {code}", + "@pinCode": { + "placeholders": { + "code": { "type": "String" } + } + }, + "tablets": "Tablets", + + "stepsCount": "{count} stappen", + "@stepsCount": { + "placeholders": { + "count": { "type": "int" } + } + }, + "displayedDescriptionLabel": "Weergegeven beschrijving:", + "displayedContentLabel": "Weergegeven inhoud:", + "displayedNameLabel": "Weergavenaam:", + "startTimeLabel": "Begintijd", + "noAnnotationConfigured": "Geen annotaties geconfigureerd.", + "newStepTitle": "Nieuwe stap", + "editStepTitle": "Stap bewerken", + "stepTitleLabel": "Staptitel", + "stepDescriptionLabel": "Stapbeschrijving", + "stepLocationLabel": "Stap locatie:", + "initiallyHiddenLabel": "Aanvankelijk verborgen", + "lockedLabel": "Vergrendeld", + "durationSecondsLabel": "Duur (seconden):", + "triggerGeopointLabel": "GeoPoint trigger (ID):", + "questionsChallengesLabel": "Vragen / Uitdagingen", + "noQuestionsConfigured": "Geen vragen geconfigureerd.", + "newAgendaEventTitle": "Nieuw evenement", + "editAgendaEventTitle": "Evenement bewerken", + "eventTitleLabel": "Evenementtitel", + "eventDescriptionLabel": "Evenementbeschrijving", + "startDateColonLabel": "Begindatum:", + "videoResourceLabel": "Video:", + "directVideoLinkLabel": "Directe videolink:", + "phoneLabel": "Telefoon:", + "locationGeometryLabel": "Locatie / Geometrie", + "geometryLabel": "Geometrie:", + "materialIconLabel": "Icoon (material):", + "linearLabel": "Lineair:", + "requiredSuccessLabel": "Vereist succes:", + "pathStepsLabel": "Parcoursstappen", + "noStepConfigured": "Geen punt/stap geconfigureerd.", + "stepFallback": "Stap", + "questionAskedLabel": "Gestelde vraag:", + "questionTitleLabel": "Vraagstitel", + "expectedAnswerLabel": "Verwacht antwoord:", + "expectedAnswerModalLabel": "Verwacht antwoord", + "validationNote": "Validatie gebeurt door vergelijking (hoofdletterongevoelig).", + "possibleAnswersLabel": "Mogelijke antwoorden:", + "noAnswerDefined": "Geen antwoord gedefinieerd. Voeg er minstens één toe.", + "correctAnswer": "Correct antwoord ✓", + "wrongAnswer": "Fout antwoord", + "answerNote": "✓ = correct antwoord. Meerdere kunnen correct zijn.", + "geopointZoneTitle": "Geografisch punt / Zone", + "geometryTypeLabel": "Geometrie (Punt/Lijn/Zone):", + "phoneModalLabel": "Telefoon", + "categoryLabel": "Categorie:", + "startMessageLabel": "Startbericht:", + "startMessageModalLabel": "Startbericht", + "createCategoryTitle": "Categorie aanmaken", + "editCategoryTitle": "Categorie bewerken", + "categoryIconLabel": "Categorie-icoon:", + "selectResource": "Selecteer een resource", + "createPdfTitle": "PDF aanmaken", + "answersLabel": "Antwoorden", + "answerLabel": "Antwoord", + "createAnswerTitle": "Antwoord aanmaken", + "editAnswerTitle": "Antwoord bewerken", + "answerValidNote": "Indien aangevinkt, is het antwoord geldig", + "selectConfiguration": "Selecteer een configuratie", + "noConfigFound": "Geen configuratie gevonden", + "backgroundColorLabel": "Achtergrondkleur:", + "updateTabletBtn": "Tablet bijwerken", + "kioskUpdatedSuccess": "Kiosk bijgewerkt", + "webViewError": "De webpagina kan niet worden weergegeven, de URL is onjuist of leeg", + "download": "Downloaden", + "resourceDeleteConfirm": "Weet u zeker dat u deze resource wilt verwijderen?", + "categoriesTitle": "Categorieën", + "endTimeLabel": "Eindtijd", + "annotationsLabel": "Annotaties" +} diff --git a/lib/main.dart b/lib/main.dart index 7cf68b1..9331542 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:manager_app/l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -234,6 +235,7 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { + final locale = Provider.of(context).getContext()?.locale ?? const Locale('fr'); return MaterialApp.router( routerConfig: widget.router, key: mainKey, @@ -246,9 +248,10 @@ class _MyAppState extends State { const Breakpoint(start: 1921, end: double.infinity, name: '4K'), ], ), - locale: const Locale('fr'), - supportedLocales: const [Locale('fr')], + locale: locale, + supportedLocales: const [Locale('fr'), Locale('en'), Locale('nl')], localizationsDelegates: const [ + AppLocalizations.delegate, FlutterQuillLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, diff --git a/manager_api_new/lib/api.dart b/manager_api_new/lib/api.dart index 5af683e..d114962 100644 --- a/manager_api_new/lib/api.dart +++ b/manager_api_new/lib/api.dart @@ -35,6 +35,7 @@ part 'api/authentication_api.dart'; part 'api/configuration_api.dart'; part 'api/device_api.dart'; part 'api/instance_api.dart'; +part 'api/subscription_plan_api.dart'; part 'api/resource_api.dart'; part 'api/section_api.dart'; part 'api/section_agenda_api.dart'; @@ -116,6 +117,8 @@ part 'model/guided_step_guided_path.dart'; part 'model/guided_step_trigger_geo_point.dart'; part 'model/instance.dart'; part 'model/instance_dto.dart'; +part 'model/instance_quota_dto.dart'; +part 'model/subscription_plan_dto.dart'; part 'model/layout_main_page_type.dart'; part 'model/login_dto.dart'; part 'model/map_annotation.dart'; diff --git a/manager_api_new/lib/api/instance_api.dart b/manager_api_new/lib/api/instance_api.dart index 38bc796..983f075 100644 --- a/manager_api_new/lib/api/instance_api.dart +++ b/manager_api_new/lib/api/instance_api.dart @@ -407,4 +407,54 @@ class InstanceApi { } return null; } + + /// Performs an HTTP 'GET /api/Instance/{id}/quota' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + Future instanceGetQuotaWithHttpInfo( + String id, + ) async { + // ignore: prefer_const_declarations + final path = r'/api/Instance/{id}/quota'.replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + Future instanceGetQuota( + String id, + ) async { + final response = await instanceGetQuotaWithHttpInfo(id); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + if (response.body.isNotEmpty && + response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync( + await _decodeBodyBytes(response), + 'InstanceQuotaDTO', + ) as InstanceQuotaDTO; + } + return null; + } } diff --git a/manager_api_new/lib/api/subscription_plan_api.dart b/manager_api_new/lib/api/subscription_plan_api.dart new file mode 100644 index 0000000..7de0911 --- /dev/null +++ b/manager_api_new/lib/api/subscription_plan_api.dart @@ -0,0 +1,219 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SubscriptionPlanApi { + SubscriptionPlanApi([ApiClient? apiClient]) + : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Performs an HTTP 'GET /api/SubscriptionPlan' operation and returns the [Response]. + Future subscriptionPlanGetWithHttpInfo() async { + // ignore: prefer_const_declarations + final path = r'/api/SubscriptionPlan'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + Future?> subscriptionPlanGet() async { + final response = await subscriptionPlanGetWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + if (response.body.isNotEmpty && + response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + } + return null; + } + + /// Performs an HTTP 'GET /api/SubscriptionPlan/{id}' operation and returns the [Response]. + Future subscriptionPlanGetByIdWithHttpInfo(String id) async { + // ignore: prefer_const_declarations + final path = r'/api/SubscriptionPlan/{id}'.replaceAll('{id}', id); + + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + Future subscriptionPlanGetById(String id) async { + final response = await subscriptionPlanGetByIdWithHttpInfo(id); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + if (response.body.isNotEmpty && + response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync( + await _decodeBodyBytes(response), + 'SubscriptionPlanDTO', + ) as SubscriptionPlanDTO; + } + return null; + } + + /// Performs an HTTP 'POST /api/SubscriptionPlan' operation and returns the [Response]. + Future subscriptionPlanCreateWithHttpInfo( + SubscriptionPlanDTO subscriptionPlanDTO, + ) async { + // ignore: prefer_const_declarations + final path = r'/api/SubscriptionPlan'; + + Object? postBody = subscriptionPlanDTO; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + return apiClient.invokeAPI( + path, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + Future subscriptionPlanCreate( + SubscriptionPlanDTO subscriptionPlanDTO, + ) async { + final response = await subscriptionPlanCreateWithHttpInfo(subscriptionPlanDTO); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + if (response.body.isNotEmpty && + response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync( + await _decodeBodyBytes(response), + 'SubscriptionPlanDTO', + ) as SubscriptionPlanDTO; + } + return null; + } + + /// Performs an HTTP 'PUT /api/SubscriptionPlan' operation and returns the [Response]. + Future subscriptionPlanUpdateWithHttpInfo( + SubscriptionPlanDTO subscriptionPlanDTO, + ) async { + // ignore: prefer_const_declarations + final path = r'/api/SubscriptionPlan'; + + Object? postBody = subscriptionPlanDTO; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + return apiClient.invokeAPI( + path, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + Future subscriptionPlanUpdate( + SubscriptionPlanDTO subscriptionPlanDTO, + ) async { + final response = await subscriptionPlanUpdateWithHttpInfo(subscriptionPlanDTO); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + if (response.body.isNotEmpty && + response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync( + await _decodeBodyBytes(response), + 'SubscriptionPlanDTO', + ) as SubscriptionPlanDTO; + } + return null; + } + + /// Performs an HTTP 'DELETE /api/SubscriptionPlan/{id}' operation and returns the [Response]. + Future subscriptionPlanDeleteWithHttpInfo(String id) async { + // ignore: prefer_const_declarations + final path = r'/api/SubscriptionPlan/{id}'.replaceAll('{id}', id); + + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + return apiClient.invokeAPI( + path, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + Future subscriptionPlanDelete(String id) async { + final response = await subscriptionPlanDeleteWithHttpInfo(id); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + if (response.body.isNotEmpty && + response.statusCode != HttpStatus.noContent) { + return await _decodeBodyBytes(response); + } + return null; + } +} diff --git a/manager_api_new/lib/api_client.dart b/manager_api_new/lib/api_client.dart index 8f39185..53bdd11 100644 --- a/manager_api_new/lib/api_client.dart +++ b/manager_api_new/lib/api_client.dart @@ -363,6 +363,8 @@ class ApiClient { return Instance.fromJson(value); case 'InstanceDTO': return InstanceDTO.fromJson(value); + case 'InstanceQuotaDTO': + return InstanceQuotaDTO.fromJson(value); case 'LayoutMainPageType': return LayoutMainPageTypeTypeTransformer().decode(value); case 'LoginDTO': @@ -457,6 +459,8 @@ class ApiClient { return Section.fromJson(value); case 'SectionDTO': return SectionDTO.fromJson(value); + case 'SubscriptionPlanDTO': + return SubscriptionPlanDTO.fromJson(value); case 'SectionEvent': return SectionEvent.fromJson(value); case 'SectionEventDTO': diff --git a/manager_api_new/lib/model/instance_dto.dart b/manager_api_new/lib/model/instance_dto.dart index 8d7e076..46d2a24 100644 --- a/manager_api_new/lib/model/instance_dto.dart +++ b/manager_api_new/lib/model/instance_dto.dart @@ -24,6 +24,15 @@ class InstanceDTO { this.isWeb, this.isVR, this.isAssistant, + this.subscriptionPlanId, + this.subscriptionPlan, + this.aiRequestsThisMonth, + this.aiUsageMonthKey, + this.storageQuotaBytes, + this.aiRequestsPerMonth, + this.hasStats, + this.statsHistoryDays, + this.hasAdvancedStats, this.applicationInstanceDTOs = const [], }); @@ -85,6 +94,24 @@ class InstanceDTO { bool? isAssistant; + String? subscriptionPlanId; + + SubscriptionPlanDTO? subscriptionPlan; + + int? aiRequestsThisMonth; + + String? aiUsageMonthKey; + + int? storageQuotaBytes; + + int? aiRequestsPerMonth; + + bool? hasStats; + + int? statsHistoryDays; + + bool? hasAdvancedStats; + List? applicationInstanceDTOs; @override @@ -102,6 +129,15 @@ class InstanceDTO { other.isWeb == isWeb && other.isVR == isVR && other.isAssistant == isAssistant && + other.subscriptionPlanId == subscriptionPlanId && + other.subscriptionPlan == subscriptionPlan && + other.aiRequestsThisMonth == aiRequestsThisMonth && + other.aiUsageMonthKey == aiUsageMonthKey && + other.storageQuotaBytes == storageQuotaBytes && + other.aiRequestsPerMonth == aiRequestsPerMonth && + other.hasStats == hasStats && + other.statsHistoryDays == statsHistoryDays && + other.hasAdvancedStats == hasAdvancedStats && _deepEquality.equals( other.applicationInstanceDTOs, applicationInstanceDTOs); @@ -119,11 +155,15 @@ class InstanceDTO { (isWeb == null ? 0 : isWeb!.hashCode) + (isVR == null ? 0 : isVR!.hashCode) + (isAssistant == null ? 0 : isAssistant!.hashCode) + + (subscriptionPlanId == null ? 0 : subscriptionPlanId!.hashCode) + + (subscriptionPlan == null ? 0 : subscriptionPlan!.hashCode) + + (aiRequestsThisMonth == null ? 0 : aiRequestsThisMonth!.hashCode) + + (aiUsageMonthKey == null ? 0 : aiUsageMonthKey!.hashCode) + (applicationInstanceDTOs == null ? 0 : applicationInstanceDTOs!.hashCode); @override String toString() => - 'InstanceDTO[id=$id, name=$name, dateCreation=$dateCreation, pinCode=$pinCode, isPushNotification=$isPushNotification, isStatistic=$isStatistic, isMobile=$isMobile, isTablet=$isTablet, isWeb=$isWeb, isVR=$isVR, isAssistant=$isAssistant, applicationInstanceDTOs=$applicationInstanceDTOs]'; + 'InstanceDTO[id=$id, name=$name, dateCreation=$dateCreation, pinCode=$pinCode, isPushNotification=$isPushNotification, isStatistic=$isStatistic, isMobile=$isMobile, isTablet=$isTablet, isWeb=$isWeb, isVR=$isVR, isAssistant=$isAssistant, subscriptionPlanId=$subscriptionPlanId, subscriptionPlan=$subscriptionPlan, aiRequestsThisMonth=$aiRequestsThisMonth, aiUsageMonthKey=$aiUsageMonthKey, applicationInstanceDTOs=$applicationInstanceDTOs]'; Map toJson() { final json = {}; @@ -182,6 +222,51 @@ class InstanceDTO { } else { json[r'isAssistant'] = null; } + if (this.subscriptionPlanId != null) { + json[r'subscriptionPlanId'] = this.subscriptionPlanId; + } else { + json[r'subscriptionPlanId'] = null; + } + if (this.subscriptionPlan != null) { + json[r'subscriptionPlan'] = this.subscriptionPlan; + } else { + json[r'subscriptionPlan'] = null; + } + if (this.aiRequestsThisMonth != null) { + json[r'aiRequestsThisMonth'] = this.aiRequestsThisMonth; + } else { + json[r'aiRequestsThisMonth'] = null; + } + if (this.aiUsageMonthKey != null) { + json[r'aiUsageMonthKey'] = this.aiUsageMonthKey; + } else { + json[r'aiUsageMonthKey'] = null; + } + if (this.storageQuotaBytes != null) { + json[r'storageQuotaBytes'] = this.storageQuotaBytes; + } else { + json[r'storageQuotaBytes'] = null; + } + if (this.aiRequestsPerMonth != null) { + json[r'aiRequestsPerMonth'] = this.aiRequestsPerMonth; + } else { + json[r'aiRequestsPerMonth'] = null; + } + if (this.hasStats != null) { + json[r'hasStats'] = this.hasStats; + } else { + json[r'hasStats'] = null; + } + if (this.statsHistoryDays != null) { + json[r'statsHistoryDays'] = this.statsHistoryDays; + } else { + json[r'statsHistoryDays'] = null; + } + if (this.hasAdvancedStats != null) { + json[r'hasAdvancedStats'] = this.hasAdvancedStats; + } else { + json[r'hasAdvancedStats'] = null; + } if (this.applicationInstanceDTOs != null) { json[r'applicationInstanceDTOs'] = this.applicationInstanceDTOs; } else { @@ -222,6 +307,15 @@ class InstanceDTO { isWeb: mapValueOfType(json, r'isWeb'), isVR: mapValueOfType(json, r'isVR'), isAssistant: mapValueOfType(json, r'isAssistant'), + subscriptionPlanId: mapValueOfType(json, r'subscriptionPlanId'), + subscriptionPlan: SubscriptionPlanDTO.fromJson(json[r'subscriptionPlan']), + aiRequestsThisMonth: mapValueOfType(json, r'aiRequestsThisMonth'), + aiUsageMonthKey: mapValueOfType(json, r'aiUsageMonthKey'), + storageQuotaBytes: mapValueOfType(json, r'storageQuotaBytes'), + aiRequestsPerMonth: mapValueOfType(json, r'aiRequestsPerMonth'), + hasStats: mapValueOfType(json, r'hasStats'), + statsHistoryDays: mapValueOfType(json, r'statsHistoryDays'), + hasAdvancedStats: mapValueOfType(json, r'hasAdvancedStats'), applicationInstanceDTOs: ApplicationInstanceDTO.listFromJson( json[r'applicationInstanceDTOs']), ); diff --git a/manager_api_new/lib/model/instance_quota_dto.dart b/manager_api_new/lib/model/instance_quota_dto.dart new file mode 100644 index 0000000..ce64620 --- /dev/null +++ b/manager_api_new/lib/model/instance_quota_dto.dart @@ -0,0 +1,106 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class InstanceQuotaDTO { + /// Returns a new [InstanceQuotaDTO] instance. + InstanceQuotaDTO({ + this.storageUsedBytes, + this.storageQuotaBytes, + this.aiRequestsUsed, + this.aiRequestsPerMonth, + }); + + int? storageUsedBytes; + + int? storageQuotaBytes; + + int? aiRequestsUsed; + + int? aiRequestsPerMonth; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is InstanceQuotaDTO && + other.storageUsedBytes == storageUsedBytes && + other.storageQuotaBytes == storageQuotaBytes && + other.aiRequestsUsed == aiRequestsUsed && + other.aiRequestsPerMonth == aiRequestsPerMonth; + + @override + int get hashCode => + (storageUsedBytes == null ? 0 : storageUsedBytes!.hashCode) + + (storageQuotaBytes == null ? 0 : storageQuotaBytes!.hashCode) + + (aiRequestsUsed == null ? 0 : aiRequestsUsed!.hashCode) + + (aiRequestsPerMonth == null ? 0 : aiRequestsPerMonth!.hashCode); + + @override + String toString() => + 'InstanceQuotaDTO[storageUsedBytes=$storageUsedBytes, storageQuotaBytes=$storageQuotaBytes, aiRequestsUsed=$aiRequestsUsed, aiRequestsPerMonth=$aiRequestsPerMonth]'; + + Map toJson() { + final json = {}; + if (this.storageUsedBytes != null) { + json[r'storageUsedBytes'] = this.storageUsedBytes; + } else { + json[r'storageUsedBytes'] = null; + } + if (this.storageQuotaBytes != null) { + json[r'storageQuotaBytes'] = this.storageQuotaBytes; + } else { + json[r'storageQuotaBytes'] = null; + } + if (this.aiRequestsUsed != null) { + json[r'aiRequestsUsed'] = this.aiRequestsUsed; + } else { + json[r'aiRequestsUsed'] = null; + } + if (this.aiRequestsPerMonth != null) { + json[r'aiRequestsPerMonth'] = this.aiRequestsPerMonth; + } else { + json[r'aiRequestsPerMonth'] = null; + } + return json; + } + + // ignore: prefer_constructors_over_static_methods + static InstanceQuotaDTO? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + return InstanceQuotaDTO( + storageUsedBytes: mapValueOfType(json, r'storageUsedBytes'), + storageQuotaBytes: mapValueOfType(json, r'storageQuotaBytes'), + aiRequestsUsed: mapValueOfType(json, r'aiRequestsUsed'), + aiRequestsPerMonth: mapValueOfType(json, r'aiRequestsPerMonth'), + ); + } + return null; + } + + static List listFromJson( + dynamic json, { + bool growable = false, + }) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = InstanceQuotaDTO.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static const requiredKeys = {}; +} diff --git a/manager_api_new/lib/model/map_annotation.dart b/manager_api_new/lib/model/map_annotation.dart index 407b24c..a96791b 100644 --- a/manager_api_new/lib/model/map_annotation.dart +++ b/manager_api_new/lib/model/map_annotation.dart @@ -38,7 +38,7 @@ class MapAnnotation { /// GeometryType? geometryType; - MapAnnotationGeometry? geometry; + EventAddressDTOGeometry? geometry; String? polyColor; @@ -154,7 +154,7 @@ class MapAnnotation { type: TranslationDTO.listFromJson(json[r'type']), label: TranslationDTO.listFromJson(json[r'label']), geometryType: GeometryType.fromJson(json[r'geometryType']), - geometry: MapAnnotationGeometry.fromJson(json[r'geometry']), + geometry: EventAddressDTOGeometry.fromJson(json[r'geometry']), polyColor: mapValueOfType(json, r'polyColor'), icon: mapValueOfType(json, r'icon'), iconResourceId: mapValueOfType(json, r'iconResourceId'), diff --git a/manager_api_new/lib/model/resource_dto.dart b/manager_api_new/lib/model/resource_dto.dart index 3f8386b..19363fc 100644 --- a/manager_api_new/lib/model/resource_dto.dart +++ b/manager_api_new/lib/model/resource_dto.dart @@ -19,6 +19,7 @@ class ResourceDTO { this.url, this.dateCreation, this.instanceId, + this.sizeBytes, }); String? id; @@ -45,6 +46,8 @@ class ResourceDTO { String? instanceId; + int? sizeBytes; + @override bool operator ==(Object other) => identical(this, other) || @@ -102,6 +105,9 @@ class ResourceDTO { } else { json[r'instanceId'] = null; } + if (this.sizeBytes != null) { + json[r'sizeBytes'] = this.sizeBytes; + } return json; } @@ -132,6 +138,7 @@ class ResourceDTO { url: mapValueOfType(json, r'url'), dateCreation: mapDateTime(json, r'dateCreation', r''), instanceId: mapValueOfType(json, r'instanceId'), + sizeBytes: mapValueOfType(json, r'sizeBytes'), ); } return null; diff --git a/manager_api_new/lib/model/subscription_plan_dto.dart b/manager_api_new/lib/model/subscription_plan_dto.dart new file mode 100644 index 0000000..d7ccefb --- /dev/null +++ b/manager_api_new/lib/model/subscription_plan_dto.dart @@ -0,0 +1,155 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SubscriptionPlanDTO { + /// Returns a new [SubscriptionPlanDTO] instance. + SubscriptionPlanDTO({ + this.id, + required this.name, + this.storageQuotaBytes, + this.aiRequestsPerMonth, + this.statsHistoryDays, + this.hasAdvancedStats, + }); + + String? id; + + String name; + + int? storageQuotaBytes; + + int? aiRequestsPerMonth; + + int? statsHistoryDays; + + bool? hasAdvancedStats; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SubscriptionPlanDTO && + other.id == id && + other.name == name && + other.storageQuotaBytes == storageQuotaBytes && + other.aiRequestsPerMonth == aiRequestsPerMonth && + other.statsHistoryDays == statsHistoryDays && + other.hasAdvancedStats == hasAdvancedStats; + + @override + int get hashCode => + (id == null ? 0 : id!.hashCode) + + (name.hashCode) + + (storageQuotaBytes == null ? 0 : storageQuotaBytes!.hashCode) + + (aiRequestsPerMonth == null ? 0 : aiRequestsPerMonth!.hashCode) + + (statsHistoryDays == null ? 0 : statsHistoryDays!.hashCode) + + (hasAdvancedStats == null ? 0 : hasAdvancedStats!.hashCode); + + @override + String toString() => + 'SubscriptionPlanDTO[id=$id, name=$name, storageQuotaBytes=$storageQuotaBytes, aiRequestsPerMonth=$aiRequestsPerMonth, statsHistoryDays=$statsHistoryDays, hasAdvancedStats=$hasAdvancedStats]'; + + Map toJson() { + final json = {}; + if (this.id != null) { + json[r'id'] = this.id; + } else { + json[r'id'] = null; + } + json[r'name'] = this.name; + if (this.storageQuotaBytes != null) { + json[r'storageQuotaBytes'] = this.storageQuotaBytes; + } else { + json[r'storageQuotaBytes'] = null; + } + if (this.aiRequestsPerMonth != null) { + json[r'aiRequestsPerMonth'] = this.aiRequestsPerMonth; + } else { + json[r'aiRequestsPerMonth'] = null; + } + if (this.statsHistoryDays != null) { + json[r'statsHistoryDays'] = this.statsHistoryDays; + } else { + json[r'statsHistoryDays'] = null; + } + if (this.hasAdvancedStats != null) { + json[r'hasAdvancedStats'] = this.hasAdvancedStats; + } else { + json[r'hasAdvancedStats'] = null; + } + return json; + } + + // ignore: prefer_constructors_over_static_methods + static SubscriptionPlanDTO? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + return SubscriptionPlanDTO( + id: mapValueOfType(json, r'id'), + name: mapValueOfType(json, r'name') ?? '', + storageQuotaBytes: mapValueOfType(json, r'storageQuotaBytes'), + aiRequestsPerMonth: mapValueOfType(json, r'aiRequestsPerMonth'), + statsHistoryDays: mapValueOfType(json, r'statsHistoryDays'), + hasAdvancedStats: mapValueOfType(json, r'hasAdvancedStats'), + ); + } + return null; + } + + static List listFromJson( + dynamic json, { + bool growable = false, + }) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SubscriptionPlanDTO.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); + for (final entry in json.entries) { + final value = SubscriptionPlanDTO.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + static Map> mapListFromJson( + dynamic json, { + bool growable = false, + }) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SubscriptionPlanDTO.listFromJson( + entry.value, + growable: growable, + ); + } + } + return map; + } + + static const requiredKeys = {'name'}; +} diff --git a/pubspec.lock b/pubspec.lock index 18adba5..4231409 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -559,7 +559,7 @@ packages: source: hosted version: "1.0.0" flutter_localizations: - dependency: transitive + dependency: "direct main" description: flutter source: sdk version: "0.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index cc0ee67..7c5460e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,8 @@ environment: dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter rxdart: provider: @@ -100,7 +102,7 @@ flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. - #generate: true + generate: true uses-material-design: true # To add assets to your application, add an assets section, like this: