diff --git a/uni/README.md b/uni/README.md index 200d3c193..a4904acf7 100644 --- a/uni/README.md +++ b/uni/README.md @@ -46,6 +46,16 @@ But you can also watch for changes in `.dart` files and automatically run the `b dart run build_runner watch ``` +## Translation files + +Intl package allows the internationalization of the app, currently supporting Portuguese ('pt_PT') and English ('en_EN). This package creates `.arb` files (one for each language), mapping a key to the correspondent translated string. +In order to access those translations through getters, you must add the translations you want to the `.arb` files and run: +``` +dart pub global run intl_utils:generate +``` +This will generate `.dart` files with the getters you need to access the translations. +You must include `'package:uni/generated/l10n.dart'` and, depending on the locale of the application, `S.of(context).{key_of_translation}` will get you the translated string. + ## Project structure ### Overview diff --git a/uni/analysis_options.yaml b/uni/analysis_options.yaml index 6acf469de..540e952da 100644 --- a/uni/analysis_options.yaml +++ b/uni/analysis_options.yaml @@ -5,6 +5,7 @@ analyzer: exclude: - "**.g.dart" - "**.mocks.dart" + - "**generated/**" # Custom linter rules. A list of all rules can be found at # https://dart-lang.github.io/linter/lints/options/options.html diff --git a/uni/app_version.txt b/uni/app_version.txt index adf98caa0..7ebc9097f 100644 --- a/uni/app_version.txt +++ b/uni/app_version.txt @@ -1 +1 @@ -1.5.62+180 \ No newline at end of file +1.5.64+182 \ No newline at end of file diff --git a/uni/ios/Runner.xcodeproj/project.pbxproj b/uni/ios/Runner.xcodeproj/project.pbxproj index d9fdaf94b..ef55cd3bd 100644 --- a/uni/ios/Runner.xcodeproj/project.pbxproj +++ b/uni/ios/Runner.xcodeproj/project.pbxproj @@ -155,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -204,6 +204,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( diff --git a/uni/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/uni/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a33..a6b826db2 100644 --- a/uni/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/uni/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ setLocale(AppLocale appLocale) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(locale, appLocale.name); + } + + static Future getLocale() async { + final prefs = await SharedPreferences.getInstance(); + final appLocale = + prefs.getString(locale) ?? Platform.localeName.substring(0, 2); + + return AppLocale.values.firstWhere( + (e) => e.toString() == 'AppLocale.$appLocale', + orElse: () => AppLocale.en, + ); + } + /// Deletes the user's student number and password. static Future removePersistentUserInfo() async { final prefs = await SharedPreferences.getInstance(); diff --git a/uni/lib/controller/parsers/parser_course_units.dart b/uni/lib/controller/parsers/parser_course_units.dart index 179ba7b77..8131dc51b 100644 --- a/uni/lib/controller/parsers/parser_course_units.dart +++ b/uni/lib/controller/parsers/parser_course_units.dart @@ -52,37 +52,36 @@ List parseCourseUnitsAndCourseAverage( final codeName = row.children[2].children[0].innerHtml; final name = row.children[3].children[0].innerHtml; final ects = row.children[5].innerHtml.replaceAll(',', '.'); - String? grade; - String? status; + var yearIncrement = -1; for (var i = 0;; i += 2) { if (row.children.length <= 6 + i) { break; } yearIncrement++; - grade = row.children[6 + i].innerHtml.replaceAll(' ', ' ').trim(); - status = row.children[7 + i].innerHtml.replaceAll(' ', ' ').trim(); - if (status != '') { - break; + final status = + row.children[7 + i].innerHtml.replaceAll(' ', ' ').trim(); + final grade = + row.children[6 + i].innerHtml.replaceAll(' ', ' ').trim(); + + if (status.isEmpty) { + continue; } - } - if (yearIncrement < 0) { - continue; - } - final courseUnit = CourseUnit( - schoolYear: - '${firstSchoolYear + yearIncrement}/${firstSchoolYear + yearIncrement + 1}', - occurrId: int.parse(occurId), - abbreviation: codeName, - status: status, - grade: grade, - ects: double.parse(ects), - name: name, - curricularYear: int.parse(year), - semesterCode: semester, - ); - courseUnits.add(courseUnit); + final courseUnit = CourseUnit( + schoolYear: + '${firstSchoolYear + yearIncrement}/${firstSchoolYear + yearIncrement + 1}', + occurrId: int.parse(occurId), + abbreviation: codeName, + status: status, + grade: grade, + ects: double.parse(ects), + name: name, + curricularYear: int.parse(year), + semesterCode: semester, + ); + courseUnits.add(courseUnit); + } } return courseUnits; diff --git a/uni/lib/generated/intl/messages_all.dart b/uni/lib/generated/intl/messages_all.dart new file mode 100644 index 000000000..b77f94db2 --- /dev/null +++ b/uni/lib/generated/intl/messages_all.dart @@ -0,0 +1,67 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that looks up messages for specific locales by +// delegating to the appropriate library. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:implementation_imports, file_names, unnecessary_new +// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering +// ignore_for_file:argument_type_not_assignable, invalid_assignment +// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases +// ignore_for_file:comment_references + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; +import 'package:intl/src/intl_helpers.dart'; + +import 'messages_en.dart' as messages_en; +import 'messages_pt_PT.dart' as messages_pt_pt; + +typedef Future LibraryLoader(); +Map _deferredLibraries = { + 'en': () => new SynchronousFuture(null), + 'pt_PT': () => new SynchronousFuture(null), +}; + +MessageLookupByLibrary? _findExact(String localeName) { + switch (localeName) { + case 'en': + return messages_en.messages; + case 'pt_PT': + return messages_pt_pt.messages; + default: + return null; + } +} + +/// User programs should call this before using [localeName] for messages. +Future initializeMessages(String localeName) { + var availableLocale = Intl.verifiedLocale( + localeName, (locale) => _deferredLibraries[locale] != null, + onFailure: (_) => null); + if (availableLocale == null) { + return new SynchronousFuture(false); + } + var lib = _deferredLibraries[availableLocale]; + lib == null ? new SynchronousFuture(false) : lib(); + initializeInternalMessageLookup(() => new CompositeMessageLookup()); + messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); + return new SynchronousFuture(true); +} + +bool _messagesExistFor(String locale) { + try { + return _findExact(locale) != null; + } catch (e) { + return false; + } +} + +MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { + var actualLocale = + Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); + if (actualLocale == null) return null; + return _findExact(actualLocale); +} diff --git a/uni/lib/generated/intl/messages_en.dart b/uni/lib/generated/intl/messages_en.dart new file mode 100644 index 000000000..7c9d65e6c --- /dev/null +++ b/uni/lib/generated/intl/messages_en.dart @@ -0,0 +1,248 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a en locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'en'; + + static String m0(time) => "last refresh at ${time}"; + + static String m1(time) => + "${Intl.plural(time, zero: 'Refreshed ${time} minutes ago', one: 'Refreshed ${time} minute ago', other: 'Refreshed ${time} minutes ago')}"; + + static String m2(title) => "${Intl.select(title, { + 'horario': 'Schedule', + 'exames': 'Exams', + 'area': 'Personal Area', + 'cadeiras': 'Course Units', + 'autocarros': 'Buses', + 'locais': 'Places', + 'restaurantes': 'Restaurants', + 'calendario': 'Calendar', + 'biblioteca': 'Library', + 'uteis': 'Utils', + 'sobre': 'About', + 'bugs': 'Bugs/Suggestions', + 'other': 'Other', + })}"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "academic_services": + MessageLookupByLibrary.simpleMessage("Academic services"), + "account_card_title": + MessageLookupByLibrary.simpleMessage("Checking account"), + "add": MessageLookupByLibrary.simpleMessage("Add"), + "add_quota": MessageLookupByLibrary.simpleMessage("Add quota"), + "add_widget": MessageLookupByLibrary.simpleMessage("Add widget"), + "agree_terms": MessageLookupByLibrary.simpleMessage( + "By entering you confirm that you agree with these Terms and Conditions"), + "all_widgets_added": MessageLookupByLibrary.simpleMessage( + "All available widgets have already been added to your personal area!"), + "at_least_one_college": + MessageLookupByLibrary.simpleMessage("Select at least one college"), + "available_amount": + MessageLookupByLibrary.simpleMessage("Available amount"), + "average": MessageLookupByLibrary.simpleMessage("Average: "), + "balance": MessageLookupByLibrary.simpleMessage("Balance:"), + "bs_description": MessageLookupByLibrary.simpleMessage( + "Did you find any bugs in the application?\nDo you have any suggestions for the app?\nTell us so we can improve!"), + "bug_description": MessageLookupByLibrary.simpleMessage( + "Bug found, how to reproduce it, etc."), + "bus_error": + MessageLookupByLibrary.simpleMessage("Unable to get information"), + "bus_information": MessageLookupByLibrary.simpleMessage( + "Select the buses you want information about:"), + "buses_personalize": + MessageLookupByLibrary.simpleMessage("Personalize your buses here"), + "buses_text": MessageLookupByLibrary.simpleMessage( + "Favorite buses will be displayed in the favorites \'Bus\' widget. The remaining ones will only be displayed on the page."), + "cancel": MessageLookupByLibrary.simpleMessage("Cancel"), + "change": MessageLookupByLibrary.simpleMessage("Change"), + "change_prompt": MessageLookupByLibrary.simpleMessage( + "Do you want to change the password?"), + "check_internet": MessageLookupByLibrary.simpleMessage( + "Check your internet connection"), + "class_registration": + MessageLookupByLibrary.simpleMessage("Class Registration"), + "college": MessageLookupByLibrary.simpleMessage("College: "), + "college_select": + MessageLookupByLibrary.simpleMessage("select your college(s)"), + "conclude": MessageLookupByLibrary.simpleMessage("Done"), + "configured_buses": + MessageLookupByLibrary.simpleMessage("Configured Buses"), + "confirm": MessageLookupByLibrary.simpleMessage("Confirm"), + "consent": MessageLookupByLibrary.simpleMessage( + "I consent to this information being reviewed by NIAEFEUP and may be deleted at my request."), + "contact": MessageLookupByLibrary.simpleMessage("Contact (optional)"), + "copy_center": MessageLookupByLibrary.simpleMessage("Copy center"), + "copy_center_building": MessageLookupByLibrary.simpleMessage( + "Floor -1 of building B | AEFEUP building"), + "course_class": MessageLookupByLibrary.simpleMessage("Classes"), + "course_info": MessageLookupByLibrary.simpleMessage("Info"), + "current_state": + MessageLookupByLibrary.simpleMessage("Current state: "), + "current_year": + MessageLookupByLibrary.simpleMessage("Current academic year: "), + "decrement": MessageLookupByLibrary.simpleMessage("Decrement 1,00€"), + "description": MessageLookupByLibrary.simpleMessage("Description"), + "desired_email": MessageLookupByLibrary.simpleMessage( + "Email where you want to be contacted"), + "dona_bia": MessageLookupByLibrary.simpleMessage( + "D. Beatriz\'s stationery store"), + "dona_bia_building": MessageLookupByLibrary.simpleMessage( + "Floor -1 of building B (B-142)"), + "ects": MessageLookupByLibrary.simpleMessage("ECTs performed: "), + "edit_off": MessageLookupByLibrary.simpleMessage("Edit"), + "edit_on": MessageLookupByLibrary.simpleMessage("Finish editing"), + "empty_text": + MessageLookupByLibrary.simpleMessage("Please fill in this field"), + "exams_filter": + MessageLookupByLibrary.simpleMessage("Exams Filter Settings"), + "exit_confirm": + MessageLookupByLibrary.simpleMessage("Do you really want to exit?"), + "expired_password": + MessageLookupByLibrary.simpleMessage("Your password has expired"), + "failed_login": MessageLookupByLibrary.simpleMessage("Login failed"), + "fee_date": + MessageLookupByLibrary.simpleMessage("Deadline for next fee:"), + "fee_notification": + MessageLookupByLibrary.simpleMessage("Notify next deadline:"), + "first_year_registration": MessageLookupByLibrary.simpleMessage( + "Year of first registration: "), + "floor": MessageLookupByLibrary.simpleMessage("Floor"), + "floors": MessageLookupByLibrary.simpleMessage("Floors"), + "forgot_password": + MessageLookupByLibrary.simpleMessage("Forgot password?"), + "generate_reference": + MessageLookupByLibrary.simpleMessage("Generate reference"), + "geral_registration": + MessageLookupByLibrary.simpleMessage("General Registration"), + "improvement_registration": + MessageLookupByLibrary.simpleMessage("Enrollment for Improvement"), + "increment": MessageLookupByLibrary.simpleMessage("Increment 1,00€"), + "invalid_credentials": + MessageLookupByLibrary.simpleMessage("Invalid credentials"), + "keep_login": MessageLookupByLibrary.simpleMessage("Stay signed in"), + "last_refresh_time": m0, + "last_timestamp": m1, + "library_occupation": + MessageLookupByLibrary.simpleMessage("Library Occupation"), + "load_error": MessageLookupByLibrary.simpleMessage( + "Error loading the information"), + "loading_terms": MessageLookupByLibrary.simpleMessage( + "Loading Terms and Conditions..."), + "login": MessageLookupByLibrary.simpleMessage("Login"), + "logout": MessageLookupByLibrary.simpleMessage("Log out"), + "menus": MessageLookupByLibrary.simpleMessage("Menus"), + "min_value_reference": + MessageLookupByLibrary.simpleMessage("Minimum value: 1,00 €"), + "multimedia_center": + MessageLookupByLibrary.simpleMessage("Multimedia center"), + "nav_title": m2, + "news": MessageLookupByLibrary.simpleMessage("News"), + "no": MessageLookupByLibrary.simpleMessage("No"), + "no_bus": MessageLookupByLibrary.simpleMessage("Don\'t miss any bus!"), + "no_bus_stops": + MessageLookupByLibrary.simpleMessage("No configured stops"), + "no_class": MessageLookupByLibrary.simpleMessage( + "There are no classes to display"), + "no_classes": + MessageLookupByLibrary.simpleMessage("No classes to present"), + "no_classes_on": + MessageLookupByLibrary.simpleMessage("You don\'t have classes on"), + "no_college": MessageLookupByLibrary.simpleMessage("no college"), + "no_course_units": MessageLookupByLibrary.simpleMessage( + "No course units in the selected period"), + "no_data": MessageLookupByLibrary.simpleMessage( + "There is no data to show at this time"), + "no_date": MessageLookupByLibrary.simpleMessage("No date"), + "no_exams": MessageLookupByLibrary.simpleMessage( + "You have no exams scheduled\n"), + "no_exams_label": MessageLookupByLibrary.simpleMessage( + "Looks like you are on vacation!"), + "no_favorite_restaurants": + MessageLookupByLibrary.simpleMessage("No favorite restaurants"), + "no_info": MessageLookupByLibrary.simpleMessage( + "There is no information to display"), + "no_menu_info": MessageLookupByLibrary.simpleMessage( + "There is no information available about meals"), + "no_menus": MessageLookupByLibrary.simpleMessage( + "There are no meals available"), + "no_name_course": + MessageLookupByLibrary.simpleMessage("Unnamed course"), + "no_places_info": MessageLookupByLibrary.simpleMessage( + "There is no information available about places"), + "no_references": MessageLookupByLibrary.simpleMessage( + "There are no references to pay"), + "no_results": MessageLookupByLibrary.simpleMessage("No match"), + "no_selected_courses": MessageLookupByLibrary.simpleMessage( + "There are no course units to display"), + "no_selected_exams": MessageLookupByLibrary.simpleMessage( + "There are no exams to present"), + "occurrence_type": + MessageLookupByLibrary.simpleMessage("Type of occurrence"), + "other_links": MessageLookupByLibrary.simpleMessage("Other links"), + "pass_change_request": MessageLookupByLibrary.simpleMessage( + "For security reasons, passwords must be changed periodically."), + "password": MessageLookupByLibrary.simpleMessage("password"), + "pendent_references": + MessageLookupByLibrary.simpleMessage("Pending references"), + "personal_assistance": + MessageLookupByLibrary.simpleMessage("Face-to-face assistance"), + "press_again": + MessageLookupByLibrary.simpleMessage("Press again to exit"), + "print": MessageLookupByLibrary.simpleMessage("Print"), + "prints": MessageLookupByLibrary.simpleMessage("Prints"), + "problem_id": MessageLookupByLibrary.simpleMessage( + "Brief identification of the problem"), + "reference_sigarra_help": MessageLookupByLibrary.simpleMessage( + "The generated reference data will appear in Sigarra, checking account.\\nProfile > Checking Account"), + "reference_success": MessageLookupByLibrary.simpleMessage( + "Reference created successfully!"), + "remove": MessageLookupByLibrary.simpleMessage("Delete"), + "report_error": MessageLookupByLibrary.simpleMessage("Report error"), + "room": MessageLookupByLibrary.simpleMessage("Room"), + "school_calendar": + MessageLookupByLibrary.simpleMessage("School Calendar"), + "semester": MessageLookupByLibrary.simpleMessage("Semester"), + "send": MessageLookupByLibrary.simpleMessage("Send"), + "sent_error": MessageLookupByLibrary.simpleMessage( + "An error occurred in sending"), + "some_error": MessageLookupByLibrary.simpleMessage("Some error!"), + "stcp_stops": + MessageLookupByLibrary.simpleMessage("STCP - Upcoming Trips"), + "student_number": + MessageLookupByLibrary.simpleMessage("student number"), + "success": MessageLookupByLibrary.simpleMessage("Sent with success"), + "tele_assistance": + MessageLookupByLibrary.simpleMessage("Telephone assistance"), + "tele_personal_assistance": MessageLookupByLibrary.simpleMessage( + "Face-to-face and telephone assistance"), + "telephone": MessageLookupByLibrary.simpleMessage("Telephone"), + "terms": MessageLookupByLibrary.simpleMessage("Terms and Conditions"), + "title": MessageLookupByLibrary.simpleMessage("Title"), + "unavailable": MessageLookupByLibrary.simpleMessage("Unavailable"), + "valid_email": + MessageLookupByLibrary.simpleMessage("Please enter a valid email"), + "widget_prompt": MessageLookupByLibrary.simpleMessage( + "Choose a widget to add to your personal area:"), + "year": MessageLookupByLibrary.simpleMessage("Year"), + "yes": MessageLookupByLibrary.simpleMessage("Yes") + }; +} diff --git a/uni/lib/generated/intl/messages_pt_PT.dart b/uni/lib/generated/intl/messages_pt_PT.dart new file mode 100644 index 000000000..c86c90267 --- /dev/null +++ b/uni/lib/generated/intl/messages_pt_PT.dart @@ -0,0 +1,249 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a pt_PT locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'pt_PT'; + + static String m0(time) => "última atualização às ${time}"; + + static String m1(time) => + "${Intl.plural(time, zero: 'Atualizado há ${time} minutos', one: 'Atualizado há ${time} minuto', other: 'Atualizado há ${time} minutos')}"; + + static String m2(title) => "${Intl.select(title, { + 'horario': 'Horário', + 'exames': 'Exames', + 'area': 'Área Pessoal', + 'cadeiras': 'Cadeiras', + 'autocarros': 'Autocarros', + 'locais': 'Locais', + 'restaurantes': 'Restaurantes', + 'calendario': 'Calendário', + 'biblioteca': 'Biblioteca', + 'uteis': 'Úteis', + 'sobre': 'Sobre', + 'bugs': 'Bugs e Sugestões', + 'other': 'Outros', + })}"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "academic_services": + MessageLookupByLibrary.simpleMessage("Serviços académicos"), + "account_card_title": + MessageLookupByLibrary.simpleMessage("Conta Corrente"), + "add": MessageLookupByLibrary.simpleMessage("Adicionar"), + "add_quota": MessageLookupByLibrary.simpleMessage("Adicionar quota"), + "add_widget": MessageLookupByLibrary.simpleMessage("Adicionar widget"), + "agree_terms": MessageLookupByLibrary.simpleMessage( + "Ao entrares confirmas que concordas com estes Termos e Condições"), + "all_widgets_added": MessageLookupByLibrary.simpleMessage( + "Todos os widgets disponíveis já foram adicionados à tua área pessoal!"), + "at_least_one_college": MessageLookupByLibrary.simpleMessage( + "Seleciona pelo menos uma faculdade"), + "available_amount": + MessageLookupByLibrary.simpleMessage("Valor disponível"), + "average": MessageLookupByLibrary.simpleMessage("Média: "), + "balance": MessageLookupByLibrary.simpleMessage("Saldo:"), + "bs_description": MessageLookupByLibrary.simpleMessage( + "Encontraste algum bug na aplicação?\nTens alguma sugestão para a app?\nConta-nos para que possamos melhorar!"), + "bug_description": MessageLookupByLibrary.simpleMessage( + "Bug encontrado, como o reproduzir, etc"), + "bus_error": MessageLookupByLibrary.simpleMessage( + "Não foi possível obter informação"), + "bus_information": MessageLookupByLibrary.simpleMessage( + "Seleciona os autocarros dos quais queres informação:"), + "buses_personalize": MessageLookupByLibrary.simpleMessage( + "Configura aqui os teus autocarros"), + "buses_text": MessageLookupByLibrary.simpleMessage( + "Os autocarros favoritos serão apresentados no widget \'Autocarros\' dos favoritos. Os restantes serão apresentados apenas na página."), + "cancel": MessageLookupByLibrary.simpleMessage("Cancelar\n"), + "change": MessageLookupByLibrary.simpleMessage("Alterar"), + "change_prompt": MessageLookupByLibrary.simpleMessage( + "Deseja alterar a palavra-passe?"), + "check_internet": MessageLookupByLibrary.simpleMessage( + "Verifica a tua ligação à internet"), + "class_registration": + MessageLookupByLibrary.simpleMessage("Inscrição de Turmas"), + "college": MessageLookupByLibrary.simpleMessage("Faculdade: "), + "college_select": MessageLookupByLibrary.simpleMessage( + "seleciona a(s) tua(s) faculdade(s)"), + "conclude": MessageLookupByLibrary.simpleMessage("Concluído"), + "configured_buses": + MessageLookupByLibrary.simpleMessage("Autocarros Configurados"), + "confirm": MessageLookupByLibrary.simpleMessage("Confirmar"), + "consent": MessageLookupByLibrary.simpleMessage( + "Consinto que esta informação seja revista pelo NIAEFEUP, podendo ser eliminada a meu pedido."), + "contact": MessageLookupByLibrary.simpleMessage("Contacto (opcional)"), + "copy_center": MessageLookupByLibrary.simpleMessage("Centro de cópias"), + "copy_center_building": MessageLookupByLibrary.simpleMessage( + "Piso -1 do edifício B | Edifício da AEFEUP"), + "course_class": MessageLookupByLibrary.simpleMessage("Turmas"), + "course_info": MessageLookupByLibrary.simpleMessage("Ficha"), + "current_state": MessageLookupByLibrary.simpleMessage("Estado atual: "), + "current_year": + MessageLookupByLibrary.simpleMessage("Ano curricular atual: "), + "decrement": MessageLookupByLibrary.simpleMessage("Decrementar 1,00€"), + "description": MessageLookupByLibrary.simpleMessage("Descrição"), + "desired_email": MessageLookupByLibrary.simpleMessage( + "Email em que desejas ser contactado"), + "dona_bia": + MessageLookupByLibrary.simpleMessage("Papelaria D. Beatriz"), + "dona_bia_building": MessageLookupByLibrary.simpleMessage( + "Piso -1 do edifício B (B-142)"), + "ects": MessageLookupByLibrary.simpleMessage("ECTs realizados: "), + "edit_off": MessageLookupByLibrary.simpleMessage("Editar\n"), + "edit_on": MessageLookupByLibrary.simpleMessage("Concluir edição"), + "empty_text": MessageLookupByLibrary.simpleMessage( + "Por favor preenche este campo"), + "exams_filter": + MessageLookupByLibrary.simpleMessage("Definições Filtro de Exames"), + "exit_confirm": MessageLookupByLibrary.simpleMessage( + "Tem a certeza de que pretende sair?"), + "expired_password": + MessageLookupByLibrary.simpleMessage("A tua palavra-passe expirou"), + "failed_login": MessageLookupByLibrary.simpleMessage("O login falhou"), + "fee_date": MessageLookupByLibrary.simpleMessage( + "Data limite próxima prestação:"), + "fee_notification": MessageLookupByLibrary.simpleMessage( + "Notificar próxima data limite:"), + "first_year_registration": + MessageLookupByLibrary.simpleMessage("Ano da primeira inscrição: "), + "floor": MessageLookupByLibrary.simpleMessage("Piso"), + "floors": MessageLookupByLibrary.simpleMessage("Pisos"), + "forgot_password": + MessageLookupByLibrary.simpleMessage("Esqueceu a palavra-passe?"), + "generate_reference": + MessageLookupByLibrary.simpleMessage("Gerar referência"), + "geral_registration": + MessageLookupByLibrary.simpleMessage("Inscrição Geral"), + "improvement_registration": + MessageLookupByLibrary.simpleMessage("Inscrição para Melhoria"), + "increment": MessageLookupByLibrary.simpleMessage("Incrementar 1,00€"), + "invalid_credentials": + MessageLookupByLibrary.simpleMessage("Credenciais inválidas"), + "keep_login": + MessageLookupByLibrary.simpleMessage("Manter sessão iniciada"), + "last_refresh_time": m0, + "last_timestamp": m1, + "library_occupation": + MessageLookupByLibrary.simpleMessage("Ocupação da Biblioteca"), + "load_error": MessageLookupByLibrary.simpleMessage( + "Aconteceu um erro ao carregar os dados"), + "loading_terms": MessageLookupByLibrary.simpleMessage( + "Carregando os Termos e Condições..."), + "login": MessageLookupByLibrary.simpleMessage("Entrar"), + "logout": MessageLookupByLibrary.simpleMessage("Terminar sessão"), + "menus": MessageLookupByLibrary.simpleMessage("Ementas"), + "min_value_reference": + MessageLookupByLibrary.simpleMessage("Valor mínimo: 1,00 €"), + "multimedia_center": + MessageLookupByLibrary.simpleMessage("Centro de multimédia"), + "nav_title": m2, + "news": MessageLookupByLibrary.simpleMessage("Notícias"), + "no": MessageLookupByLibrary.simpleMessage("Não"), + "no_bus": MessageLookupByLibrary.simpleMessage( + "Não percas nenhum autocarro!"), + "no_bus_stops": MessageLookupByLibrary.simpleMessage( + "Não existe nenhuma paragem configurada"), + "no_class": MessageLookupByLibrary.simpleMessage( + "Não existem turmas para apresentar"), + "no_classes": MessageLookupByLibrary.simpleMessage( + "Não existem aulas para apresentar"), + "no_classes_on": + MessageLookupByLibrary.simpleMessage("Não possui aulas à"), + "no_college": MessageLookupByLibrary.simpleMessage("sem faculdade"), + "no_course_units": MessageLookupByLibrary.simpleMessage( + "Sem cadeiras no período selecionado"), + "no_data": MessageLookupByLibrary.simpleMessage( + "Não há dados a mostrar neste momento"), + "no_date": MessageLookupByLibrary.simpleMessage("Sem data"), + "no_exams": + MessageLookupByLibrary.simpleMessage("Não possui exames marcados"), + "no_exams_label": + MessageLookupByLibrary.simpleMessage("Parece que estás de férias!"), + "no_favorite_restaurants": + MessageLookupByLibrary.simpleMessage("Sem restaurantes favoritos"), + "no_info": MessageLookupByLibrary.simpleMessage( + "Não existem informações para apresentar"), + "no_menu_info": MessageLookupByLibrary.simpleMessage( + "Não há informação disponível sobre refeições"), + "no_menus": MessageLookupByLibrary.simpleMessage( + "Não há refeições disponíveis"), + "no_name_course": + MessageLookupByLibrary.simpleMessage("Curso sem nome"), + "no_places_info": MessageLookupByLibrary.simpleMessage( + "Não há informação disponível sobre locais"), + "no_references": MessageLookupByLibrary.simpleMessage( + "Não existem referências a pagar"), + "no_results": MessageLookupByLibrary.simpleMessage("Sem resultados"), + "no_selected_courses": MessageLookupByLibrary.simpleMessage( + "Não existem cadeiras para apresentar"), + "no_selected_exams": MessageLookupByLibrary.simpleMessage( + "Não existem exames para apresentar"), + "occurrence_type": + MessageLookupByLibrary.simpleMessage("Tipo de ocorrência"), + "other_links": MessageLookupByLibrary.simpleMessage("Outros links"), + "pass_change_request": MessageLookupByLibrary.simpleMessage( + "Por razões de segurança, as palavras-passe têm de ser alteradas periodicamente."), + "password": MessageLookupByLibrary.simpleMessage("palavra-passe"), + "pendent_references": + MessageLookupByLibrary.simpleMessage("Referências pendentes"), + "personal_assistance": + MessageLookupByLibrary.simpleMessage("Atendimento presencial"), + "press_again": MessageLookupByLibrary.simpleMessage( + "Pressione novamente para sair"), + "print": MessageLookupByLibrary.simpleMessage("Impressão"), + "prints": MessageLookupByLibrary.simpleMessage("Impressões"), + "problem_id": MessageLookupByLibrary.simpleMessage( + "Breve identificação do problema"), + "reference_sigarra_help": MessageLookupByLibrary.simpleMessage( + "Os dados da referência gerada aparecerão no Sigarra, conta corrente.\\nPerfil > Conta Corrente"), + "reference_success": MessageLookupByLibrary.simpleMessage( + "Referência criada com sucesso!"), + "remove": MessageLookupByLibrary.simpleMessage("Remover"), + "report_error": MessageLookupByLibrary.simpleMessage("Reportar erro"), + "room": MessageLookupByLibrary.simpleMessage("Sala"), + "school_calendar": + MessageLookupByLibrary.simpleMessage("Calendário Escolar"), + "semester": MessageLookupByLibrary.simpleMessage("Semestre"), + "send": MessageLookupByLibrary.simpleMessage("Enviar"), + "sent_error": + MessageLookupByLibrary.simpleMessage("Ocorreu um erro no envio"), + "some_error": MessageLookupByLibrary.simpleMessage("Algum erro!"), + "stcp_stops": + MessageLookupByLibrary.simpleMessage("STCP - Próximas Viagens"), + "student_number": + MessageLookupByLibrary.simpleMessage("número de estudante"), + "success": MessageLookupByLibrary.simpleMessage("Enviado com sucesso"), + "tele_assistance": + MessageLookupByLibrary.simpleMessage("Atendimento telefónico"), + "tele_personal_assistance": MessageLookupByLibrary.simpleMessage( + "Atendimento presencial e telefónico"), + "telephone": MessageLookupByLibrary.simpleMessage("Telefone"), + "terms": MessageLookupByLibrary.simpleMessage("Termos e Condições"), + "title": MessageLookupByLibrary.simpleMessage("Título"), + "unavailable": MessageLookupByLibrary.simpleMessage("Indisponível"), + "valid_email": MessageLookupByLibrary.simpleMessage( + "Por favor insere um email válido"), + "widget_prompt": MessageLookupByLibrary.simpleMessage( + "Escolhe um widget para adicionares à tua área pessoal:"), + "year": MessageLookupByLibrary.simpleMessage("Ano"), + "yes": MessageLookupByLibrary.simpleMessage("Sim") + }; +} diff --git a/uni/lib/generated/l10n.dart b/uni/lib/generated/l10n.dart new file mode 100644 index 000000000..804bd1400 --- /dev/null +++ b/uni/lib/generated/l10n.dart @@ -0,0 +1,1367 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'intl/messages_all.dart'; + +// ************************************************************************** +// Generator: Flutter Intl IDE plugin +// Made by Localizely +// ************************************************************************** + +// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars +// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each +// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes + +class S { + S(); + + static S? _current; + + static S get current { + assert(_current != null, + 'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.'); + return _current!; + } + + static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); + + static Future load(Locale locale) { + final name = (locale.countryCode?.isEmpty ?? false) + ? locale.languageCode + : locale.toString(); + final localeName = Intl.canonicalizedLocale(name); + return initializeMessages(localeName).then((_) { + Intl.defaultLocale = localeName; + final instance = S(); + S._current = instance; + + return instance; + }); + } + + static S of(BuildContext context) { + final instance = S.maybeOf(context); + assert(instance != null, + 'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?'); + return instance!; + } + + static S? maybeOf(BuildContext context) { + return Localizations.of(context, S); + } + + /// `Do you really want to exit?` + String get exit_confirm { + return Intl.message( + 'Do you really want to exit?', + name: 'exit_confirm', + desc: '', + args: [], + ); + } + + /// `No` + String get no { + return Intl.message( + 'No', + name: 'no', + desc: '', + args: [], + ); + } + + /// `Yes` + String get yes { + return Intl.message( + 'Yes', + name: 'yes', + desc: '', + args: [], + ); + } + + /// `Academic services` + String get academic_services { + return Intl.message( + 'Academic services', + name: 'academic_services', + desc: '', + args: [], + ); + } + + /// `Checking account` + String get account_card_title { + return Intl.message( + 'Checking account', + name: 'account_card_title', + desc: '', + args: [], + ); + } + + /// `Add` + String get add { + return Intl.message( + 'Add', + name: 'add', + desc: '', + args: [], + ); + } + + /// `Add quota` + String get add_quota { + return Intl.message( + 'Add quota', + name: 'add_quota', + desc: '', + args: [], + ); + } + + /// `Add widget` + String get add_widget { + return Intl.message( + 'Add widget', + name: 'add_widget', + desc: '', + args: [], + ); + } + + /// `By entering you confirm that you agree with these Terms and Conditions` + String get agree_terms { + return Intl.message( + 'By entering you confirm that you agree with these Terms and Conditions', + name: 'agree_terms', + desc: '', + args: [], + ); + } + + /// `All available widgets have already been added to your personal area!` + String get all_widgets_added { + return Intl.message( + 'All available widgets have already been added to your personal area!', + name: 'all_widgets_added', + desc: '', + args: [], + ); + } + + /// `Select at least one college` + String get at_least_one_college { + return Intl.message( + 'Select at least one college', + name: 'at_least_one_college', + desc: '', + args: [], + ); + } + + /// `Available amount` + String get available_amount { + return Intl.message( + 'Available amount', + name: 'available_amount', + desc: '', + args: [], + ); + } + + /// `Average: ` + String get average { + return Intl.message( + 'Average: ', + name: 'average', + desc: '', + args: [], + ); + } + + /// `Balance:` + String get balance { + return Intl.message( + 'Balance:', + name: 'balance', + desc: '', + args: [], + ); + } + + /// `Did you find any bugs in the application?\nDo you have any suggestions for the app?\nTell us so we can improve!` + String get bs_description { + return Intl.message( + 'Did you find any bugs in the application?\nDo you have any suggestions for the app?\nTell us so we can improve!', + name: 'bs_description', + desc: '', + args: [], + ); + } + + /// `Bug found, how to reproduce it, etc.` + String get bug_description { + return Intl.message( + 'Bug found, how to reproduce it, etc.', + name: 'bug_description', + desc: '', + args: [], + ); + } + + /// `Unable to get information` + String get bus_error { + return Intl.message( + 'Unable to get information', + name: 'bus_error', + desc: '', + args: [], + ); + } + + /// `Personalize your buses here` + String get buses_personalize { + return Intl.message( + 'Personalize your buses here', + name: 'buses_personalize', + desc: '', + args: [], + ); + } + + /// `Favorite buses will be displayed in the favorites 'Bus' widget. The remaining ones will only be displayed on the page.` + String get buses_text { + return Intl.message( + 'Favorite buses will be displayed in the favorites \'Bus\' widget. The remaining ones will only be displayed on the page.', + name: 'buses_text', + desc: '', + args: [], + ); + } + + /// `Select the buses you want information about:` + String get bus_information { + return Intl.message( + 'Select the buses you want information about:', + name: 'bus_information', + desc: '', + args: [], + ); + } + + /// `Cancel` + String get cancel { + return Intl.message( + 'Cancel', + name: 'cancel', + desc: '', + args: [], + ); + } + + /// `Change` + String get change { + return Intl.message( + 'Change', + name: 'change', + desc: '', + args: [], + ); + } + + /// `Do you want to change the password?` + String get change_prompt { + return Intl.message( + 'Do you want to change the password?', + name: 'change_prompt', + desc: '', + args: [], + ); + } + + /// `Check your internet connection` + String get check_internet { + return Intl.message( + 'Check your internet connection', + name: 'check_internet', + desc: '', + args: [], + ); + } + + /// `Classes` + String get course_class { + return Intl.message( + 'Classes', + name: 'course_class', + desc: '', + args: [], + ); + } + + /// `Class Registration` + String get class_registration { + return Intl.message( + 'Class Registration', + name: 'class_registration', + desc: '', + args: [], + ); + } + + /// `College: ` + String get college { + return Intl.message( + 'College: ', + name: 'college', + desc: '', + args: [], + ); + } + + /// `select your college(s)` + String get college_select { + return Intl.message( + 'select your college(s)', + name: 'college_select', + desc: '', + args: [], + ); + } + + /// `Done` + String get conclude { + return Intl.message( + 'Done', + name: 'conclude', + desc: '', + args: [], + ); + } + + /// `Configured Buses` + String get configured_buses { + return Intl.message( + 'Configured Buses', + name: 'configured_buses', + desc: '', + args: [], + ); + } + + /// `Confirm` + String get confirm { + return Intl.message( + 'Confirm', + name: 'confirm', + desc: '', + args: [], + ); + } + + /// `I consent to this information being reviewed by NIAEFEUP and may be deleted at my request.` + String get consent { + return Intl.message( + 'I consent to this information being reviewed by NIAEFEUP and may be deleted at my request.', + name: 'consent', + desc: '', + args: [], + ); + } + + /// `Contact (optional)` + String get contact { + return Intl.message( + 'Contact (optional)', + name: 'contact', + desc: '', + args: [], + ); + } + + /// `Copy center` + String get copy_center { + return Intl.message( + 'Copy center', + name: 'copy_center', + desc: '', + args: [], + ); + } + + /// `Floor -1 of building B | AEFEUP building` + String get copy_center_building { + return Intl.message( + 'Floor -1 of building B | AEFEUP building', + name: 'copy_center_building', + desc: '', + args: [], + ); + } + + /// `Info` + String get course_info { + return Intl.message( + 'Info', + name: 'course_info', + desc: '', + args: [], + ); + } + + /// `Current state: ` + String get current_state { + return Intl.message( + 'Current state: ', + name: 'current_state', + desc: '', + args: [], + ); + } + + /// `Current academic year: ` + String get current_year { + return Intl.message( + 'Current academic year: ', + name: 'current_year', + desc: '', + args: [], + ); + } + + /// `Decrement 1,00€` + String get decrement { + return Intl.message( + 'Decrement 1,00€', + name: 'decrement', + desc: '', + args: [], + ); + } + + /// `Description` + String get description { + return Intl.message( + 'Description', + name: 'description', + desc: '', + args: [], + ); + } + + /// `Email where you want to be contacted` + String get desired_email { + return Intl.message( + 'Email where you want to be contacted', + name: 'desired_email', + desc: '', + args: [], + ); + } + + /// `D. Beatriz's stationery store` + String get dona_bia { + return Intl.message( + 'D. Beatriz\'s stationery store', + name: 'dona_bia', + desc: '', + args: [], + ); + } + + /// `Floor -1 of building B (B-142)` + String get dona_bia_building { + return Intl.message( + 'Floor -1 of building B (B-142)', + name: 'dona_bia_building', + desc: '', + args: [], + ); + } + + /// `ECTs performed: ` + String get ects { + return Intl.message( + 'ECTs performed: ', + name: 'ects', + desc: '', + args: [], + ); + } + + /// `Edit` + String get edit_off { + return Intl.message( + 'Edit', + name: 'edit_off', + desc: '', + args: [], + ); + } + + /// `Finish editing` + String get edit_on { + return Intl.message( + 'Finish editing', + name: 'edit_on', + desc: '', + args: [], + ); + } + + /// `Please fill in this field` + String get empty_text { + return Intl.message( + 'Please fill in this field', + name: 'empty_text', + desc: '', + args: [], + ); + } + + /// `Exams Filter Settings` + String get exams_filter { + return Intl.message( + 'Exams Filter Settings', + name: 'exams_filter', + desc: '', + args: [], + ); + } + + /// `Your password has expired` + String get expired_password { + return Intl.message( + 'Your password has expired', + name: 'expired_password', + desc: '', + args: [], + ); + } + + /// `Login failed` + String get failed_login { + return Intl.message( + 'Login failed', + name: 'failed_login', + desc: '', + args: [], + ); + } + + /// `Deadline for next fee:` + String get fee_date { + return Intl.message( + 'Deadline for next fee:', + name: 'fee_date', + desc: '', + args: [], + ); + } + + /// `Notify next deadline:` + String get fee_notification { + return Intl.message( + 'Notify next deadline:', + name: 'fee_notification', + desc: '', + args: [], + ); + } + + /// `Year of first registration: ` + String get first_year_registration { + return Intl.message( + 'Year of first registration: ', + name: 'first_year_registration', + desc: '', + args: [], + ); + } + + /// `Floor` + String get floor { + return Intl.message( + 'Floor', + name: 'floor', + desc: '', + args: [], + ); + } + + /// `Floors` + String get floors { + return Intl.message( + 'Floors', + name: 'floors', + desc: '', + args: [], + ); + } + + /// `Forgot password?` + String get forgot_password { + return Intl.message( + 'Forgot password?', + name: 'forgot_password', + desc: '', + args: [], + ); + } + + /// `Generate reference` + String get generate_reference { + return Intl.message( + 'Generate reference', + name: 'generate_reference', + desc: '', + args: [], + ); + } + + /// `General Registration` + String get geral_registration { + return Intl.message( + 'General Registration', + name: 'geral_registration', + desc: '', + args: [], + ); + } + + /// `Enrollment for Improvement` + String get improvement_registration { + return Intl.message( + 'Enrollment for Improvement', + name: 'improvement_registration', + desc: '', + args: [], + ); + } + + /// `Increment 1,00€` + String get increment { + return Intl.message( + 'Increment 1,00€', + name: 'increment', + desc: '', + args: [], + ); + } + + /// `Invalid credentials` + String get invalid_credentials { + return Intl.message( + 'Invalid credentials', + name: 'invalid_credentials', + desc: '', + args: [], + ); + } + + /// `Stay signed in` + String get keep_login { + return Intl.message( + 'Stay signed in', + name: 'keep_login', + desc: '', + args: [], + ); + } + + /// `last refresh at {time}` + String last_refresh_time(Object time) { + return Intl.message( + 'last refresh at $time', + name: 'last_refresh_time', + desc: '', + args: [time], + ); + } + + /// `{time, plural, zero{Refreshed {time} minutes ago} one{Refreshed {time} minute ago} other{Refreshed {time} minutes ago}}` + String last_timestamp(num time) { + return Intl.plural( + time, + zero: 'Refreshed $time minutes ago', + one: 'Refreshed $time minute ago', + other: 'Refreshed $time minutes ago', + name: 'last_timestamp', + desc: '', + args: [time], + ); + } + + /// `Library Occupation` + String get library_occupation { + return Intl.message( + 'Library Occupation', + name: 'library_occupation', + desc: '', + args: [], + ); + } + + /// `Error loading the information` + String get load_error { + return Intl.message( + 'Error loading the information', + name: 'load_error', + desc: '', + args: [], + ); + } + + /// `Loading Terms and Conditions...` + String get loading_terms { + return Intl.message( + 'Loading Terms and Conditions...', + name: 'loading_terms', + desc: '', + args: [], + ); + } + + /// `Login` + String get login { + return Intl.message( + 'Login', + name: 'login', + desc: '', + args: [], + ); + } + + /// `Log out` + String get logout { + return Intl.message( + 'Log out', + name: 'logout', + desc: '', + args: [], + ); + } + + /// `Menus` + String get menus { + return Intl.message( + 'Menus', + name: 'menus', + desc: '', + args: [], + ); + } + + /// `Minimum value: 1,00 €` + String get min_value_reference { + return Intl.message( + 'Minimum value: 1,00 €', + name: 'min_value_reference', + desc: '', + args: [], + ); + } + + /// `Multimedia center` + String get multimedia_center { + return Intl.message( + 'Multimedia center', + name: 'multimedia_center', + desc: '', + args: [], + ); + } + + /// `{title, select, horario{Schedule} exames{Exams} area{Personal Area} cadeiras{Course Units} autocarros{Buses} locais{Places} restaurantes{Restaurants} calendario{Calendar} biblioteca{Library} uteis{Utils} sobre{About} bugs{Bugs/Suggestions} other{Other}}` + String nav_title(Object title) { + return Intl.select( + title, + { + 'horario': 'Schedule', + 'exames': 'Exams', + 'area': 'Personal Area', + 'cadeiras': 'Course Units', + 'autocarros': 'Buses', + 'locais': 'Places', + 'restaurantes': 'Restaurants', + 'calendario': 'Calendar', + 'biblioteca': 'Library', + 'uteis': 'Utils', + 'sobre': 'About', + 'bugs': 'Bugs/Suggestions', + 'other': 'Other', + }, + name: 'nav_title', + desc: '', + args: [title], + ); + } + + /// `News` + String get news { + return Intl.message( + 'News', + name: 'news', + desc: '', + args: [], + ); + } + + /// `Don't miss any bus!` + String get no_bus { + return Intl.message( + 'Don\'t miss any bus!', + name: 'no_bus', + desc: '', + args: [], + ); + } + + /// `No configured stops` + String get no_bus_stops { + return Intl.message( + 'No configured stops', + name: 'no_bus_stops', + desc: '', + args: [], + ); + } + + /// `There are no classes to display` + String get no_class { + return Intl.message( + 'There are no classes to display', + name: 'no_class', + desc: '', + args: [], + ); + } + + /// `No classes to present` + String get no_classes { + return Intl.message( + 'No classes to present', + name: 'no_classes', + desc: '', + args: [], + ); + } + + /// `You don't have classes on` + String get no_classes_on { + return Intl.message( + 'You don\'t have classes on', + name: 'no_classes_on', + desc: '', + args: [], + ); + } + + /// `no college` + String get no_college { + return Intl.message( + 'no college', + name: 'no_college', + desc: '', + args: [], + ); + } + + /// `No course units in the selected period` + String get no_course_units { + return Intl.message( + 'No course units in the selected period', + name: 'no_course_units', + desc: '', + args: [], + ); + } + + /// `There is no data to show at this time` + String get no_data { + return Intl.message( + 'There is no data to show at this time', + name: 'no_data', + desc: '', + args: [], + ); + } + + /// `No date` + String get no_date { + return Intl.message( + 'No date', + name: 'no_date', + desc: '', + args: [], + ); + } + + /// `You have no exams scheduled\n` + String get no_exams { + return Intl.message( + 'You have no exams scheduled\n', + name: 'no_exams', + desc: '', + args: [], + ); + } + + /// `Looks like you are on vacation!` + String get no_exams_label { + return Intl.message( + 'Looks like you are on vacation!', + name: 'no_exams_label', + desc: '', + args: [], + ); + } + + /// `No favorite restaurants` + String get no_favorite_restaurants { + return Intl.message( + 'No favorite restaurants', + name: 'no_favorite_restaurants', + desc: '', + args: [], + ); + } + + /// `There is no information to display` + String get no_info { + return Intl.message( + 'There is no information to display', + name: 'no_info', + desc: '', + args: [], + ); + } + + /// `There is no information available about meals` + String get no_menu_info { + return Intl.message( + 'There is no information available about meals', + name: 'no_menu_info', + desc: '', + args: [], + ); + } + + /// `There is no information available about places` + String get no_places_info { + return Intl.message( + 'There is no information available about places', + name: 'no_places_info', + desc: '', + args: [], + ); + } + + /// `There are no meals available` + String get no_menus { + return Intl.message( + 'There are no meals available', + name: 'no_menus', + desc: '', + args: [], + ); + } + + /// `Unnamed course` + String get no_name_course { + return Intl.message( + 'Unnamed course', + name: 'no_name_course', + desc: '', + args: [], + ); + } + + /// `There are no references to pay` + String get no_references { + return Intl.message( + 'There are no references to pay', + name: 'no_references', + desc: '', + args: [], + ); + } + + /// `No match` + String get no_results { + return Intl.message( + 'No match', + name: 'no_results', + desc: '', + args: [], + ); + } + + /// `There are no course units to display` + String get no_selected_courses { + return Intl.message( + 'There are no course units to display', + name: 'no_selected_courses', + desc: '', + args: [], + ); + } + + /// `There are no exams to present` + String get no_selected_exams { + return Intl.message( + 'There are no exams to present', + name: 'no_selected_exams', + desc: '', + args: [], + ); + } + + /// `Type of occurrence` + String get occurrence_type { + return Intl.message( + 'Type of occurrence', + name: 'occurrence_type', + desc: '', + args: [], + ); + } + + /// `Other links` + String get other_links { + return Intl.message( + 'Other links', + name: 'other_links', + desc: '', + args: [], + ); + } + + /// `For security reasons, passwords must be changed periodically.` + String get pass_change_request { + return Intl.message( + 'For security reasons, passwords must be changed periodically.', + name: 'pass_change_request', + desc: '', + args: [], + ); + } + + /// `password` + String get password { + return Intl.message( + 'password', + name: 'password', + desc: '', + args: [], + ); + } + + /// `Pending references` + String get pendent_references { + return Intl.message( + 'Pending references', + name: 'pendent_references', + desc: '', + args: [], + ); + } + + /// `Face-to-face assistance` + String get personal_assistance { + return Intl.message( + 'Face-to-face assistance', + name: 'personal_assistance', + desc: '', + args: [], + ); + } + + /// `Press again to exit` + String get press_again { + return Intl.message( + 'Press again to exit', + name: 'press_again', + desc: '', + args: [], + ); + } + + /// `Print` + String get print { + return Intl.message( + 'Print', + name: 'print', + desc: '', + args: [], + ); + } + + /// `Prints` + String get prints { + return Intl.message( + 'Prints', + name: 'prints', + desc: '', + args: [], + ); + } + + /// `Brief identification of the problem` + String get problem_id { + return Intl.message( + 'Brief identification of the problem', + name: 'problem_id', + desc: '', + args: [], + ); + } + + /// `The generated reference data will appear in Sigarra, checking account.\nProfile > Checking Account` + String get reference_sigarra_help { + return Intl.message( + 'The generated reference data will appear in Sigarra, checking account.\\nProfile > Checking Account', + name: 'reference_sigarra_help', + desc: '', + args: [], + ); + } + + /// `Reference created successfully!` + String get reference_success { + return Intl.message( + 'Reference created successfully!', + name: 'reference_success', + desc: '', + args: [], + ); + } + + /// `Delete` + String get remove { + return Intl.message( + 'Delete', + name: 'remove', + desc: '', + args: [], + ); + } + + /// `Report error` + String get report_error { + return Intl.message( + 'Report error', + name: 'report_error', + desc: '', + args: [], + ); + } + + /// `Room` + String get room { + return Intl.message( + 'Room', + name: 'room', + desc: '', + args: [], + ); + } + + /// `School Calendar` + String get school_calendar { + return Intl.message( + 'School Calendar', + name: 'school_calendar', + desc: '', + args: [], + ); + } + + /// `Semester` + String get semester { + return Intl.message( + 'Semester', + name: 'semester', + desc: '', + args: [], + ); + } + + /// `Send` + String get send { + return Intl.message( + 'Send', + name: 'send', + desc: '', + args: [], + ); + } + + /// `An error occurred in sending` + String get sent_error { + return Intl.message( + 'An error occurred in sending', + name: 'sent_error', + desc: '', + args: [], + ); + } + + /// `Some error!` + String get some_error { + return Intl.message( + 'Some error!', + name: 'some_error', + desc: '', + args: [], + ); + } + + /// `STCP - Upcoming Trips` + String get stcp_stops { + return Intl.message( + 'STCP - Upcoming Trips', + name: 'stcp_stops', + desc: '', + args: [], + ); + } + + /// `student number` + String get student_number { + return Intl.message( + 'student number', + name: 'student_number', + desc: '', + args: [], + ); + } + + /// `Sent with success` + String get success { + return Intl.message( + 'Sent with success', + name: 'success', + desc: '', + args: [], + ); + } + + /// `Telephone assistance` + String get tele_assistance { + return Intl.message( + 'Telephone assistance', + name: 'tele_assistance', + desc: '', + args: [], + ); + } + + /// `Face-to-face and telephone assistance` + String get tele_personal_assistance { + return Intl.message( + 'Face-to-face and telephone assistance', + name: 'tele_personal_assistance', + desc: '', + args: [], + ); + } + + /// `Telephone` + String get telephone { + return Intl.message( + 'Telephone', + name: 'telephone', + desc: '', + args: [], + ); + } + + /// `Terms and Conditions` + String get terms { + return Intl.message( + 'Terms and Conditions', + name: 'terms', + desc: '', + args: [], + ); + } + + /// `Title` + String get title { + return Intl.message( + 'Title', + name: 'title', + desc: '', + args: [], + ); + } + + /// `Unavailable` + String get unavailable { + return Intl.message( + 'Unavailable', + name: 'unavailable', + desc: '', + args: [], + ); + } + + /// `Please enter a valid email` + String get valid_email { + return Intl.message( + 'Please enter a valid email', + name: 'valid_email', + desc: '', + args: [], + ); + } + + /// `Choose a widget to add to your personal area:` + String get widget_prompt { + return Intl.message( + 'Choose a widget to add to your personal area:', + name: 'widget_prompt', + desc: '', + args: [], + ); + } + + /// `Year` + String get year { + return Intl.message( + 'Year', + name: 'year', + desc: '', + args: [], + ); + } +} + +class AppLocalizationDelegate extends LocalizationsDelegate { + const AppLocalizationDelegate(); + + List get supportedLocales { + return const [ + Locale.fromSubtags(languageCode: 'en'), + Locale.fromSubtags(languageCode: 'pt', countryCode: 'PT'), + ]; + } + + @override + bool isSupported(Locale locale) => _isSupported(locale); + @override + Future load(Locale locale) => S.load(locale); + @override + bool shouldReload(AppLocalizationDelegate old) => false; + + bool _isSupported(Locale locale) { + for (var supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale.languageCode) { + return true; + } + } + return false; + } +} diff --git a/uni/lib/l10n/intl_en.arb b/uni/lib/l10n/intl_en.arb new file mode 100644 index 000000000..acc5d7b7c --- /dev/null +++ b/uni/lib/l10n/intl_en.arb @@ -0,0 +1,265 @@ +{ + "@@locale": "en", + "exit_confirm": "Do you really want to exit?", + "@exit_confirm": {}, + "no": "No", + "@no": {}, + "yes": "Yes", + "@yes": {}, + "academic_services": "Academic services", + "@academic_services": {}, + "account_card_title": "Checking account", + "@account_card_title": {}, + "add": "Add", + "@add": {}, + "add_quota": "Add quota", + "@add_quota": {}, + "add_widget": "Add widget", + "@add_widget": {}, + "agree_terms": "By entering you confirm that you agree with these Terms and Conditions", + "@agree_terms": {}, + "all_widgets_added": "All available widgets have already been added to your personal area!", + "@all_widgets_added": {}, + "at_least_one_college": "Select at least one college", + "@at_least_one_college": {}, + "available_amount": "Available amount", + "@available_amount": {}, + "average": "Average: ", + "@average": {}, + "balance": "Balance:", + "@balance": {}, + "bs_description": "Did you find any bugs in the application?\nDo you have any suggestions for the app?\nTell us so we can improve!", + "@bs_description": {}, + "bug_description": "Bug found, how to reproduce it, etc.", + "@bug_description": {}, + "bus_error": "Unable to get information", + "@bus_error": {}, + "buses_personalize": "Personalize your buses here", + "@buses_personalize": {}, + "buses_text": "Favorite buses will be displayed in the favorites 'Bus' widget. The remaining ones will only be displayed on the page.", + "@buses_text": {}, + "bus_information": "Select the buses you want information about:", + "@bus_information": {}, + "cancel": "Cancel", + "@cancel": {}, + "change": "Change", + "@change": {}, + "change_prompt": "Do you want to change the password?", + "@change_prompt": {}, + "check_internet": "Check your internet connection", + "@check_internet": {}, + "course_class": "Classes", + "@course_class": {}, + "class_registration": "Class Registration", + "@class_registration": {}, + "college": "College: ", + "@college": {}, + "college_select": "select your college(s)", + "@college_select": {}, + "conclude": "Done", + "@conclude": {}, + "configured_buses": "Configured Buses", + "@configured_buses": {}, + "confirm": "Confirm", + "@confirm": {}, + "consent": "I consent to this information being reviewed by NIAEFEUP and may be deleted at my request.", + "@consent": {}, + "contact": "Contact (optional)", + "@contact": {}, + "copy_center": "Copy center", + "@copy_center": {}, + "copy_center_building": "Floor -1 of building B | AEFEUP building", + "@copy_center_building": {}, + "course_info": "Info", + "@course_info": {}, + "current_state": "Current state: ", + "@current_state": {}, + "current_year": "Current academic year: ", + "@current_year": {}, + "decrement": "Decrement 1,00€", + "@decrement": {}, + "description": "Description", + "@description": {}, + "desired_email": "Email where you want to be contacted", + "@desired_email": {}, + "dona_bia": "D. Beatriz's stationery store", + "@dona_bia": {}, + "dona_bia_building": "Floor -1 of building B (B-142)", + "@dona_bia_building": {}, + "ects": "ECTs performed: ", + "@ects": {}, + "edit_off": "Edit", + "@edit_off": {}, + "edit_on": "Finish editing", + "@edit_on": {}, + "empty_text": "Please fill in this field", + "@empty_text": {}, + "exams_filter": "Exams Filter Settings", + "@exams_filter": {}, + "expired_password": "Your password has expired", + "@expired_password": {}, + "failed_login": "Login failed", + "@failed_login": {}, + "fee_date": "Deadline for next fee:", + "@fee_date": {}, + "fee_notification": "Notify next deadline:", + "@fee_notification": {}, + "first_year_registration": "Year of first registration: ", + "@first_year_registration": {}, + "floor": "Floor", + "@floor": {}, + "floors": "Floors", + "@floors": {}, + "forgot_password": "Forgot password?", + "@forgot_password": {}, + "generate_reference": "Generate reference", + "@generate_reference": {}, + "geral_registration": "General Registration", + "@geral_registration": {}, + "improvement_registration": "Enrollment for Improvement", + "@improvement_registration": {}, + "increment": "Increment 1,00€", + "@increment": {}, + "invalid_credentials": "Invalid credentials", + "@invalid_credentials": {}, + "keep_login": "Stay signed in", + "@keep_login": {}, + "last_refresh_time": "last refresh at {time}", + "@last_refresh_time": { + "placeholders": { + "time": {} + } + }, + "last_timestamp": "{time, plural, zero{Refreshed {time} minutes ago} one{Refreshed {time} minute ago} other{Refreshed {time} minutes ago}}", + "@last_timestamp": { + "placeholders": { + "time": {} + } + }, + "library_occupation": "Library Occupation", + "@library_occupation": {}, + "load_error": "Error loading the information", + "@load_error": {}, + "loading_terms": "Loading Terms and Conditions...", + "@loading_terms": {}, + "login": "Login", + "@login": {}, + "logout": "Log out", + "@logout": {}, + "menus": "Menus", + "@menus": {}, + "min_value_reference": "Minimum value: 1,00 €", + "@min_value_reference": {}, + "multimedia_center": "Multimedia center", + "@multimedia_center": {}, + "nav_title": "{title, select, horario{Schedule} exames{Exams} area{Personal Area} cadeiras{Course Units} autocarros{Buses} locais{Places} restaurantes{Restaurants} calendario{Calendar} biblioteca{Library} uteis{Utils} sobre{About} bugs{Bugs/Suggestions} other{Other}}", + "@nav_title": {}, + "news": "News", + "@news": {}, + "no_bus": "Don't miss any bus!", + "@no_bus": {}, + "no_bus_stops": "No configured stops", + "@no_bus_stops": {}, + "no_class": "There are no classes to display", + "@no_class": {}, + "no_classes": "No classes to present", + "@no_classes": {}, + "no_classes_on": "You don't have classes on", + "@no_classes_on": {}, + "no_college": "no college", + "@no_college": {}, + "no_course_units": "No course units in the selected period", + "@no_course_units": {}, + "no_data": "There is no data to show at this time", + "@no_data": {}, + "no_date": "No date", + "@no_date": {}, + "no_exams": "You have no exams scheduled\n", + "@no_exams": {}, + "no_exams_label": "Looks like you are on vacation!", + "@no_exams_label": {}, + "no_favorite_restaurants": "No favorite restaurants", + "@no_favorite_restaurants": {}, + "no_info": "There is no information to display", + "@no_info": {}, + "no_menu_info": "There is no information available about meals", + "@no_menu_info": {}, + "no_places_info": "There is no information available about places", + "@no_places_info": {}, + "no_menus": "There are no meals available", + "@no_menus": {}, + "no_name_course": "Unnamed course", + "@no_name_course": {}, + "no_references": "There are no references to pay", + "@no_references": {}, + "no_results": "No match", + "@no_results": {}, + "no_selected_courses": "There are no course units to display", + "@no_selected_courses": {}, + "no_selected_exams": "There are no exams to present", + "@no_selected_exams": {}, + "occurrence_type": "Type of occurrence", + "@occurrence_type": {}, + "other_links": "Other links", + "@other_links": {}, + "pass_change_request": "For security reasons, passwords must be changed periodically.", + "@pass_change_request": {}, + "password": "password", + "@password": {}, + "pendent_references": "Pending references", + "@pendent_references": {}, + "personal_assistance": "Face-to-face assistance", + "@personal_assistance": {}, + "press_again": "Press again to exit", + "@press_again": {}, + "print": "Print", + "@print": {}, + "prints": "Prints", + "@prints": {}, + "problem_id": "Brief identification of the problem", + "@problem_id": {}, + "reference_sigarra_help": "The generated reference data will appear in Sigarra, checking account.\\nProfile > Checking Account", + "@reference_sigarra_help": {}, + "reference_success": "Reference created successfully!", + "@reference_success": {}, + "remove": "Delete", + "@remove": {}, + "report_error": "Report error", + "@report_error": {}, + "room": "Room", + "@room": {}, + "school_calendar": "School Calendar", + "@school_calendar": {}, + "semester": "Semester", + "@semester": {}, + "send": "Send", + "@send": {}, + "sent_error": "An error occurred in sending", + "@sent_error": {}, + "some_error": "Some error!", + "@some_error": {}, + "stcp_stops": "STCP - Upcoming Trips", + "@stcp_stops": {}, + "student_number": "student number", + "@student_number": {}, + "success": "Sent with success", + "@success": {}, + "tele_assistance": "Telephone assistance", + "@tele_assistance": {}, + "tele_personal_assistance": "Face-to-face and telephone assistance", + "@tele_personal_assistance": {}, + "telephone": "Telephone", + "@telephone": {}, + "terms": "Terms and Conditions", + "@terms": {}, + "title": "Title", + "@title": {}, + "unavailable": "Unavailable", + "@unavailable": {}, + "valid_email": "Please enter a valid email", + "@valid_email": {}, + "widget_prompt": "Choose a widget to add to your personal area:", + "@widget_prompt": {}, + "year": "Year", + "@year": {} +} \ No newline at end of file diff --git a/uni/lib/l10n/intl_pt_PT.arb b/uni/lib/l10n/intl_pt_PT.arb new file mode 100644 index 000000000..7552ae5c3 --- /dev/null +++ b/uni/lib/l10n/intl_pt_PT.arb @@ -0,0 +1,265 @@ +{ + "@@locale": "pt_PT", + "exit_confirm": "Tem a certeza de que pretende sair?", + "@exit_confirm": {}, + "no": "Não", + "@no": {}, + "yes": "Sim", + "@yes": {}, + "academic_services": "Serviços académicos", + "@academic_services": {}, + "account_card_title": "Conta Corrente", + "@account_card_title": {}, + "add": "Adicionar", + "@add": {}, + "add_quota": "Adicionar quota", + "@add_quota": {}, + "add_widget": "Adicionar widget", + "@add_widget": {}, + "agree_terms": "Ao entrares confirmas que concordas com estes Termos e Condições", + "@agree_terms": {}, + "all_widgets_added": "Todos os widgets disponíveis já foram adicionados à tua área pessoal!", + "@all_widgets_added": {}, + "at_least_one_college": "Seleciona pelo menos uma faculdade", + "@at_least_one_college": {}, + "available_amount": "Valor disponível", + "@available_amount": {}, + "average": "Média: ", + "@average": {}, + "balance": "Saldo:", + "@balance": {}, + "bs_description": "Encontraste algum bug na aplicação?\nTens alguma sugestão para a app?\nConta-nos para que possamos melhorar!", + "@bs_description": {}, + "bug_description": "Bug encontrado, como o reproduzir, etc", + "@bug_description": {}, + "bus_error": "Não foi possível obter informação", + "@bus_error": {}, + "buses_personalize": "Configura aqui os teus autocarros", + "@buses_personalize": {}, + "buses_text": "Os autocarros favoritos serão apresentados no widget 'Autocarros' dos favoritos. Os restantes serão apresentados apenas na página.", + "@buses_text": {}, + "bus_information": "Seleciona os autocarros dos quais queres informação:", + "@bus_information": {}, + "cancel": "Cancelar\n", + "@cancel": {}, + "change": "Alterar", + "@change": {}, + "change_prompt": "Deseja alterar a palavra-passe?", + "@change_prompt": {}, + "check_internet": "Verifica a tua ligação à internet", + "@check_internet": {}, + "course_class": "Turmas", + "@course_class": {}, + "class_registration": "Inscrição de Turmas", + "@class_registration": {}, + "college": "Faculdade: ", + "@college": {}, + "college_select": "seleciona a(s) tua(s) faculdade(s)", + "@college_select": {}, + "conclude": "Concluído", + "@conclude": {}, + "configured_buses": "Autocarros Configurados", + "@configured_buses": {}, + "confirm": "Confirmar", + "@confirm": {}, + "consent": "Consinto que esta informação seja revista pelo NIAEFEUP, podendo ser eliminada a meu pedido.", + "@consent": {}, + "contact": "Contacto (opcional)", + "@contact": {}, + "copy_center": "Centro de cópias", + "@copy_center": {}, + "copy_center_building": "Piso -1 do edifício B | Edifício da AEFEUP", + "@copy_center_building": {}, + "course_info": "Ficha", + "@course_info": {}, + "current_state": "Estado atual: ", + "@current_state": {}, + "current_year": "Ano curricular atual: ", + "@current_year": {}, + "decrement": "Decrementar 1,00€", + "@decrement": {}, + "description": "Descrição", + "@description": {}, + "desired_email": "Email em que desejas ser contactado", + "@desired_email": {}, + "dona_bia": "Papelaria D. Beatriz", + "@dona_bia": {}, + "dona_bia_building": "Piso -1 do edifício B (B-142)", + "@dona_bia_building": {}, + "ects": "ECTs realizados: ", + "@ects": {}, + "edit_off": "Editar\n", + "@edit_off": {}, + "edit_on": "Concluir edição", + "@edit_on": {}, + "empty_text": "Por favor preenche este campo", + "@empty_text": {}, + "exams_filter": "Definições Filtro de Exames", + "@exams_filter": {}, + "expired_password": "A tua palavra-passe expirou", + "@expired_password": {}, + "failed_login": "O login falhou", + "@failed_login": {}, + "fee_date": "Data limite próxima prestação:", + "@fee_date": {}, + "fee_notification": "Notificar próxima data limite:", + "@fee_notification": {}, + "first_year_registration": "Ano da primeira inscrição: ", + "@first_year_registration": {}, + "floor": "Piso", + "@floor": {}, + "floors": "Pisos", + "@floors": {}, + "forgot_password": "Esqueceu a palavra-passe?", + "@forgot_password": {}, + "generate_reference": "Gerar referência", + "@generate_reference": {}, + "geral_registration": "Inscrição Geral", + "@geral_registration": {}, + "improvement_registration": "Inscrição para Melhoria", + "@improvement_registration": {}, + "increment": "Incrementar 1,00€", + "@increment": {}, + "invalid_credentials": "Credenciais inválidas", + "@invalid_credentials": {}, + "keep_login": "Manter sessão iniciada", + "@keep_login": {}, + "last_refresh_time": "última atualização às {time}", + "@last_refresh_time": { + "placeholders": { + "time": {} + } + }, + "last_timestamp": "{time, plural, zero{Atualizado há {time} minutos} one{Atualizado há {time} minuto} other{Atualizado há {time} minutos}}", + "@last_timestamp": { + "placeholders": { + "time": {} + } + }, + "library_occupation": "Ocupação da Biblioteca", + "@library_occupation": {}, + "load_error": "Aconteceu um erro ao carregar os dados", + "@load_error": {}, + "loading_terms": "Carregando os Termos e Condições...", + "@loading_terms": {}, + "login": "Entrar", + "@login": {}, + "logout": "Terminar sessão", + "@logout": {}, + "menus": "Ementas", + "@menus": {}, + "min_value_reference": "Valor mínimo: 1,00 €", + "@min_value_reference": {}, + "multimedia_center": "Centro de multimédia", + "@multimedia_center": {}, + "nav_title": "{title, select, horario{Horário} exames{Exames} area{Área Pessoal} cadeiras{Cadeiras} autocarros{Autocarros} locais{Locais} restaurantes{Restaurantes} calendario{Calendário} biblioteca{Biblioteca} uteis{Úteis} sobre{Sobre} bugs{Bugs e Sugestões} other{Outros}}", + "@nav_title": {}, + "news": "Notícias", + "@news": {}, + "no_bus": "Não percas nenhum autocarro!", + "@no_bus": {}, + "no_bus_stops": "Não existe nenhuma paragem configurada", + "@no_bus_stops": {}, + "no_class": "Não existem turmas para apresentar", + "@no_class": {}, + "no_classes": "Não existem aulas para apresentar", + "@no_classes": {}, + "no_classes_on": "Não possui aulas à", + "@no_classes_on": {}, + "no_college": "sem faculdade", + "@no_college": {}, + "no_course_units": "Sem cadeiras no período selecionado", + "@no_course_units": {}, + "no_data": "Não há dados a mostrar neste momento", + "@no_data": {}, + "no_date": "Sem data", + "@no_date": {}, + "no_exams": "Não possui exames marcados", + "@no_exams": {}, + "no_exams_label": "Parece que estás de férias!", + "@no_exams_label": {}, + "no_favorite_restaurants": "Sem restaurantes favoritos", + "@no_favorite_restaurants": {}, + "no_info": "Não existem informações para apresentar", + "@no_info": {}, + "no_menu_info": "Não há informação disponível sobre refeições", + "@no_menu_info": {}, + "no_places_info": "Não há informação disponível sobre locais", + "@no_places_info": {}, + "no_menus": "Não há refeições disponíveis", + "@no_menus": {}, + "no_name_course": "Curso sem nome", + "@no_name_course": {}, + "no_references": "Não existem referências a pagar", + "@no_references": {}, + "no_results": "Sem resultados", + "@no_results": {}, + "no_selected_courses": "Não existem cadeiras para apresentar", + "@no_selected_courses": {}, + "no_selected_exams": "Não existem exames para apresentar", + "@no_selected_exams": {}, + "occurrence_type": "Tipo de ocorrência", + "@occurrence_type": {}, + "other_links": "Outros links", + "@other_links": {}, + "pass_change_request": "Por razões de segurança, as palavras-passe têm de ser alteradas periodicamente.", + "@pass_change_request": {}, + "password": "palavra-passe", + "@password": {}, + "pendent_references": "Referências pendentes", + "@pendent_references": {}, + "personal_assistance": "Atendimento presencial", + "@personal_assistance": {}, + "press_again": "Pressione novamente para sair", + "@press_again": {}, + "print": "Impressão", + "@print": {}, + "prints": "Impressões", + "@prints": {}, + "problem_id": "Breve identificação do problema", + "@problem_id": {}, + "reference_sigarra_help": "Os dados da referência gerada aparecerão no Sigarra, conta corrente.\\nPerfil > Conta Corrente", + "@reference_sigarra_help": {}, + "reference_success": "Referência criada com sucesso!", + "@reference_success": {}, + "remove": "Remover", + "@remove": {}, + "report_error": "Reportar erro", + "@report_error": {}, + "room": "Sala", + "@room": {}, + "school_calendar": "Calendário Escolar", + "@school_calendar": {}, + "semester": "Semestre", + "@semester": {}, + "send": "Enviar", + "@send": {}, + "sent_error": "Ocorreu um erro no envio", + "@sent_error": {}, + "some_error": "Algum erro!", + "@some_error": {}, + "stcp_stops": "STCP - Próximas Viagens", + "@stcp_stops": {}, + "student_number": "número de estudante", + "@student_number": {}, + "success": "Enviado com sucesso", + "@success": {}, + "tele_assistance": "Atendimento telefónico", + "@tele_assistance": {}, + "tele_personal_assistance": "Atendimento presencial e telefónico", + "@tele_personal_assistance": {}, + "telephone": "Telefone", + "@telephone": {}, + "terms": "Termos e Condições", + "@terms": {}, + "title": "Título", + "@title": {}, + "unavailable": "Indisponível", + "@unavailable": {}, + "valid_email": "Por favor insere um email válido", + "@valid_email": {}, + "widget_prompt": "Escolhe um widget para adicionares à tua área pessoal:", + "@widget_prompt": {}, + "year": "Ano", + "@year": {} +} \ No newline at end of file diff --git a/uni/lib/main.dart b/uni/lib/main.dart index 64494226f..6f2e29a30 100644 --- a/uni/lib/main.dart +++ b/uni/lib/main.dart @@ -4,12 +4,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:logger/logger.dart'; import 'package:provider/provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:uni/controller/background_workers/background_callback.dart'; import 'package:uni/controller/load_static/terms_and_conditions.dart'; import 'package:uni/controller/local_storage/app_shared_preferences.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/providers/lazy/bus_stop_provider.dart'; import 'package:uni/model/providers/lazy/calendar_provider.dart'; import 'package:uni/model/providers/lazy/course_units_info_provider.dart'; @@ -33,6 +35,7 @@ import 'package:uni/view/course_units/course_units.dart'; import 'package:uni/view/exams/exams.dart'; import 'package:uni/view/home/home.dart'; import 'package:uni/view/library/library.dart'; +import 'package:uni/view/locale_notifier.dart'; import 'package:uni/view/locations/locations.dart'; import 'package:uni/view/login/login.dart'; import 'package:uni/view/navigation_service.dart'; @@ -92,6 +95,7 @@ Future main() async { }); final savedTheme = await AppSharedPreferences.getThemeMode(); + final savedLocale = await AppSharedPreferences.getLocale(); final route = await firstRoute(); await SentryFlutter.init( @@ -139,11 +143,14 @@ Future main() async { ChangeNotifierProvider( create: (context) => stateProviders.referenceProvider, ), + ChangeNotifierProvider( + create: (_) => LocaleNotifier(savedLocale), + ), + ChangeNotifierProvider( + create: (_) => ThemeNotifier(savedTheme), + ), ], - child: ChangeNotifierProvider( - create: (_) => ThemeNotifier(savedTheme), - child: MyApp(route), - ), + child: MyApp(route), ), ); }, @@ -170,17 +177,23 @@ class MyAppState extends State { SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, ]); - - return Consumer( - builder: (context, themeNotifier, _) => MaterialApp( + return Consumer2( + builder: (context, themeNotifier, localeNotifier, _) => MaterialApp( title: 'uni', theme: applicationLightTheme, darkTheme: applicationDarkTheme, themeMode: themeNotifier.getTheme(), + locale: localeNotifier.getLocale().localeCode, + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, initialRoute: widget.initialRoute, - navigatorKey: NavigationService.navigatorKey, onGenerateRoute: (RouteSettings settings) { - final transitions = >{ + final transitions = { '/${DrawerItem.navPersonalArea.title}': PageTransition.makePageTransition( page: const HomePageView(), diff --git a/uni/lib/model/entities/app_locale.dart b/uni/lib/model/entities/app_locale.dart new file mode 100644 index 000000000..9f6cd2958 --- /dev/null +++ b/uni/lib/model/entities/app_locale.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +enum AppLocale { + en, + pt; + + Locale get localeCode { + switch (this) { + case AppLocale.en: + return const Locale('en'); + case AppLocale.pt: + return const Locale('pt'); + } + } +} diff --git a/uni/lib/model/entities/exam.dart b/uni/lib/model/entities/exam.dart index 85c959423..fb29f704a 100644 --- a/uni/lib/model/entities/exam.dart +++ b/uni/lib/model/entities/exam.dart @@ -1,38 +1,6 @@ import 'package:intl/intl.dart'; import 'package:logger/logger.dart'; - -enum WeekDays { - monday('Segunda'), - tuesday('Terça'), - wednesday('Quarta'), - thursday('Quinta'), - friday('Sexta'), - saturday('Sábado'), - sunday('Domingo'); - - const WeekDays(this.day); - - final String day; -} - -enum Months { - january('janeiro'), - february('fevereiro'), - march('março'), - april('abril'), - may('maio'), - june('junho'), - july('julho'), - august('agosto'), - september('setembro'), - october('outubro'), - november('novembro'), - december('dezembro'); - - const Months(this.month); - - final String month; -} +import 'package:uni/model/entities/app_locale.dart'; /// Manages a generic Exam. /// @@ -96,9 +64,17 @@ class Exam { /// Returns whether or not this exam has already ended. bool hasEnded() => DateTime.now().compareTo(end) >= 0; - String get weekDay => WeekDays.values[begin.weekday - 1].day; + String weekDay(AppLocale locale) { + return DateFormat.EEEE(locale.localeCode.languageCode) + .dateSymbols + .WEEKDAYS[begin.weekday - 1]; + } - String get month => Months.values[begin.month - 1].month; + String month(AppLocale locale) { + return DateFormat.EEEE(locale.localeCode.languageCode) + .dateSymbols + .MONTHS[begin.month - 1]; + } String get beginTime => formatTime(begin); diff --git a/uni/lib/model/entities/login_exceptions.dart b/uni/lib/model/entities/login_exceptions.dart index c170f6405..a6a129f80 100644 --- a/uni/lib/model/entities/login_exceptions.dart +++ b/uni/lib/model/entities/login_exceptions.dart @@ -1,13 +1,25 @@ +import 'package:uni/model/entities/app_locale.dart'; + class ExpiredCredentialsException implements Exception { ExpiredCredentialsException(); } class InternetStatusException implements Exception { - InternetStatusException(); - String message = 'Verifica a tua ligação à internet'; + InternetStatusException(this.locale) + : message = locale == AppLocale.en + ? 'Check your internet connection' + : 'Verifique sua conexão com a internet'; + + final AppLocale locale; + final String message; } class WrongCredentialsException implements Exception { - WrongCredentialsException(); - String message = 'Credenciais inválidas'; + WrongCredentialsException(this.locale) + : message = locale == AppLocale.en + ? 'Invalid credentials' + : 'Credenciais inválidas'; + + final AppLocale locale; + final String message; } diff --git a/uni/lib/model/entities/time_utilities.dart b/uni/lib/model/entities/time_utilities.dart index cd62196d2..dd5314b17 100644 --- a/uni/lib/model/entities/time_utilities.dart +++ b/uni/lib/model/entities/time_utilities.dart @@ -5,29 +5,6 @@ extension TimeString on DateTime { return '${hour.toString().padLeft(2, '0')}:' '${minute.toString().padLeft(2, '0')}'; } - - static List getWeekdaysStrings({ - bool startMonday = true, - bool includeWeekend = true, - }) { - final weekdays = [ - 'Segunda-Feira', - 'Terça-Feira', - 'Quarta-Feira', - 'Quinta-Feira', - 'Sexta-Feira', - 'Sábado', - 'Domingo' - ]; - - if (!startMonday) { - weekdays - ..removeAt(6) - ..insert(0, 'Domingo'); - } - - return includeWeekend ? weekdays : weekdays.sublist(0, 5); - } } extension ClosestMonday on DateTime { diff --git a/uni/lib/model/providers/startup/session_provider.dart b/uni/lib/model/providers/startup/session_provider.dart index 2126f0679..e4b006ea6 100644 --- a/uni/lib/model/providers/startup/session_provider.dart +++ b/uni/lib/model/providers/startup/session_provider.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:uni/controller/background_workers/notifications.dart'; import 'package:uni/controller/load_static/terms_and_conditions.dart'; import 'package:uni/controller/local_storage/app_shared_preferences.dart'; @@ -10,6 +12,7 @@ import 'package:uni/model/entities/profile.dart'; import 'package:uni/model/entities/session.dart'; import 'package:uni/model/providers/state_provider_notifier.dart'; import 'package:uni/model/request_status.dart'; +import 'package:uni/view/locale_notifier.dart'; class SessionProvider extends StateProviderNotifier { SessionProvider() @@ -59,6 +62,7 @@ class SessionProvider extends StateProviderNotifier { } Future postAuthentication( + BuildContext context, String username, String password, List faculties, { @@ -76,7 +80,9 @@ class SessionProvider extends StateProviderNotifier { ); } catch (e) { updateStatus(RequestStatus.failed); - throw InternetStatusException(); + throw InternetStatusException( + Provider.of(context).getLocale(), + ); } if (session == null) { @@ -85,10 +91,12 @@ class SessionProvider extends StateProviderNotifier { updateStatus(RequestStatus.failed); - if (isPasswordExpired(responseHtml)) { + if (isPasswordExpired(responseHtml) && context.mounted) { throw ExpiredCredentialsException(); } else { - throw WrongCredentialsException(); + throw WrongCredentialsException( + Provider.of(context).getLocale(), + ); } } diff --git a/uni/lib/utils/drawer_items.dart b/uni/lib/utils/drawer_items.dart index bda77248b..ca70697f0 100644 --- a/uni/lib/utils/drawer_items.dart +++ b/uni/lib/utils/drawer_items.dart @@ -1,16 +1,16 @@ enum DrawerItem { - navPersonalArea('Área Pessoal'), - navSchedule('Horário'), - navExams('Exames'), - navCourseUnits('Cadeiras'), - navStops('Autocarros'), - navLocations('Locais', faculties: {'feup'}), - navRestaurants('Restaurantes'), - navCalendar('Calendário'), - navLibrary('Biblioteca', faculties: {'feup'}), - navUsefulInfo('Úteis', faculties: {'feup'}), - navAbout('Sobre'), - navBugReport('Bugs e Sugestões'), + navPersonalArea('area'), + navSchedule('horario'), + navExams('exames'), + navCourseUnits('cadeiras'), + navStops('autocarros'), + navLocations('locais', faculties: {'feup'}), + navRestaurants('restaurantes'), + navCalendar('calendario'), + navLibrary('biblioteca', faculties: {'feup'}), + navUsefulInfo('uteis', faculties: {'feup'}), + navAbout('sobre'), + navBugReport('bugs'), navLogIn('Iniciar sessão'), navLogOut('Terminar sessão'); diff --git a/uni/lib/view/about/widgets/terms_and_conditions.dart b/uni/lib/view/about/widgets/terms_and_conditions.dart index fb2a62f3b..1a53d07da 100644 --- a/uni/lib/view/about/widgets/terms_and_conditions.dart +++ b/uni/lib/view/about/widgets/terms_and_conditions.dart @@ -2,14 +2,15 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:uni/controller/load_static/terms_and_conditions.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:url_launcher/url_launcher.dart'; class TermsAndConditions extends StatelessWidget { const TermsAndConditions({super.key}); - static String termsAndConditionsSaved = 'Carregando os Termos e Condições...'; @override Widget build(BuildContext context) { + var termsAndConditionsSaved = S.of(context).loading_terms; final termsAndConditionsFuture = fetchTermsAndConditions(); return FutureBuilder( future: termsAndConditionsFuture, diff --git a/uni/lib/view/bug_report/widgets/form.dart b/uni/lib/view/bug_report/widgets/form.dart index 3a0a26ab2..c94af32b6 100644 --- a/uni/lib/view/bug_report/widgets/form.dart +++ b/uni/lib/view/bug_report/widgets/form.dart @@ -1,13 +1,18 @@ import 'package:email_validator/email_validator.dart'; import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; +import 'package:provider/provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:tuple/tuple.dart'; import 'package:uni/controller/local_storage/app_shared_preferences.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/model/entities/app_locale.dart'; import 'package:uni/model/entities/bug_report.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/bug_report/widgets/text_field.dart'; import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/common_widgets/toast_message.dart'; +import 'package:uni/view/locale_notifier.dart'; class BugReportForm extends StatefulWidget { const BugReportForm({super.key}); @@ -20,7 +25,9 @@ class BugReportForm extends StatefulWidget { /// Manages the 'Bugs and Suggestions' section of the app class BugReportFormState extends State { - BugReportFormState() { + @override + void didChangeDependencies() { + super.didChangeDependencies(); loadBugClassList(); } @@ -48,12 +55,26 @@ class BugReportFormState extends State { bool _isConsentGiven = false; void loadBugClassList() { - bugList = []; + final locale = + Provider.of(context, listen: false).getLocale(); - bugDescriptions.forEach( - (int key, Tuple2 tup) => - bugList.add(DropdownMenuItem(value: key, child: Text(tup.item1))), - ); + bugList = bugDescriptions.entries + .map( + (entry) => DropdownMenuItem( + value: entry.key, + child: Text( + () { + switch (locale) { + case AppLocale.pt: + return entry.value.item1; + case AppLocale.en: + return entry.value.item2; + } + }(), + ), + ), + ) + .toList(); } @override @@ -73,34 +94,38 @@ class BugReportFormState extends State { titleController, Icons.title, maxLines: 2, - description: 'Título', - labelText: 'Breve identificação do problema', + description: S.of(context).title, + labelText: S.of(context).problem_id, bottomMargin: 30, ), FormTextField( descriptionController, Icons.description, maxLines: 30, - description: 'Descrição', - labelText: 'Bug encontrado, como o reproduzir, etc', + description: S.of(context).description, + labelText: S.of(context).bug_description, bottomMargin: 30, ), FormTextField( emailController, Icons.mail, maxLines: 2, - description: 'Contacto (opcional)', - labelText: 'Email em que desejas ser contactado', + description: S.of(context).contact, + labelText: S.of(context).desired_email, bottomMargin: 30, isOptional: true, formatValidator: (String? value) { - return EmailValidator.validate(value ?? '') + if (value == null || value.isEmpty) { + return null; + } + + return EmailValidator.validate(value) ? null - : 'Por favor insere um email válido'; + : S.of(context).valid_email; }, ), consentBox(context), - submitButton(context) + submitButton(context), ]; } @@ -108,12 +133,17 @@ class BugReportFormState extends State { Widget bugReportTitle(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(vertical: 10), - child: const Row( + child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Icon(Icons.bug_report, size: 40), - PageTitle(name: 'Bugs e Sugestões', center: false), - Icon(Icons.bug_report, size: 40), + const Icon(Icons.bug_report, size: 40), + PageTitle( + name: S.of(context).nav_title( + DrawerItem.navBugReport.title, + ), + center: false, + ), + const Icon(Icons.bug_report, size: 40), ], ), ); @@ -126,8 +156,7 @@ class BugReportFormState extends State { padding: const EdgeInsets.only(bottom: 20), child: Center( child: Text( - '''Encontraste algum bug na aplicação?\nTens alguma ''' - '''sugestão para a app?\nConta-nos para que possamos melhorar!''', + S.of(context).bs_description, style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center, ), @@ -144,7 +173,7 @@ class BugReportFormState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Tipo de ocorrência', + S.of(context).occurrence_type, style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.left, ), @@ -158,13 +187,15 @@ class BugReportFormState extends State { ), Expanded( child: DropdownButton( - hint: const Text('Tipo de ocorrência'), + hint: Text(S.of(context).occurrence_type), items: bugList, value: _selectedBug, - onChanged: (value) { - setState(() { - _selectedBug = value!; - }); + onChanged: (int? value) { + if (value != null) { + setState(() { + _selectedBug = value; + }); + } }, isExpanded: true, ), @@ -184,7 +215,7 @@ class BugReportFormState extends State { contentPadding: EdgeInsets.zero, child: CheckboxListTile( title: Text( - '''Consinto que esta informação seja revista pelo NIAEFEUP, podendo ser eliminada a meu pedido.''', + S.of(context).consent, style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.left, ), @@ -212,9 +243,11 @@ class BugReportFormState extends State { submitBugReport(); } }, - child: const Text( - 'Enviar', - style: TextStyle(/*color: Colors.white*/ fontSize: 20), + child: Text( + S.of(context).send, + style: const TextStyle( + /*color: Colors.white*/ fontSize: 20, + ), ), ); } @@ -236,16 +269,16 @@ class BugReportFormState extends State { bugDescriptions[_selectedBug], faculties, ).toMap(); - String toastMsg; + var toastMsg = ''; bool status; try { await submitSentryEvent(bugReport); Logger().i('Successfully submitted bug report.'); - toastMsg = 'Enviado com sucesso'; + if (context.mounted) toastMsg = S.of(context).success; status = true; } catch (e) { Logger().e('Error while posting bug report:$e'); - toastMsg = 'Ocorreu um erro no envio'; + if (context.mounted) toastMsg = S.of(context).sent_error; status = false; } diff --git a/uni/lib/view/bug_report/widgets/text_field.dart b/uni/lib/view/bug_report/widgets/text_field.dart index 1ff858a90..037b5e3db 100644 --- a/uni/lib/view/bug_report/widgets/text_field.dart +++ b/uni/lib/view/bug_report/widgets/text_field.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; class FormTextField extends StatelessWidget { const FormTextField( @@ -9,12 +10,13 @@ class FormTextField extends StatelessWidget { this.maxLines = 1, this.labelText = '', this.hintText = '', - this.emptyText = 'Por favor preenche este campo', + this.emptyText = '', this.bottomMargin = 0, this.isOptional = false, this.formatValidator, super.key, }); + final TextEditingController controller; final IconData icon; final String description; @@ -62,7 +64,7 @@ class FormTextField extends StatelessWidget { controller: controller, validator: (String? value) { if (value == null || value.isEmpty) { - return isOptional ? null : emptyText; + return isOptional ? null : S.of(context).empty_text; } return formatValidator?.call(value); }, diff --git a/uni/lib/view/bus_stop_next_arrivals/bus_stop_next_arrivals.dart b/uni/lib/view/bus_stop_next_arrivals/bus_stop_next_arrivals.dart index 880c17865..807f85b9d 100644 --- a/uni/lib/view/bus_stop_next_arrivals/bus_stop_next_arrivals.dart +++ b/uni/lib/view/bus_stop_next_arrivals/bus_stop_next_arrivals.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/bus_stop.dart'; import 'package:uni/model/providers/lazy/bus_stop_provider.dart'; import 'package:uni/model/request_status.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/bus_stop_next_arrivals/widgets/bus_stop_row.dart'; import 'package:uni/view/bus_stop_selection/bus_stop_selection.dart'; import 'package:uni/view/common_widgets/expanded_image_label.dart'; @@ -93,7 +95,7 @@ class NextArrivalsState extends State { ..add( ImageLabel( imagePath: 'assets/images/bus.png', - label: 'Não percas nenhum autocarro', + label: S.of(context).no_bus, labelTextStyle: TextStyle( fontWeight: FontWeight.bold, fontSize: 17, @@ -111,7 +113,7 @@ class NextArrivalsState extends State { builder: (context) => const BusStopSelectionPage(), ), ), - child: const Text('Adicionar'), + child: Text(S.of(context).add), ), ], ), @@ -136,7 +138,9 @@ class NextArrivalsState extends State { Container getPageTitle() { return Container( padding: const EdgeInsets.only(bottom: 12), - child: const PageTitle(name: 'Autocarros'), + child: PageTitle( + name: S.of(context).nav_title(DrawerItem.navStops.title), + ), ); } diff --git a/uni/lib/view/bus_stop_selection/bus_stop_selection.dart b/uni/lib/view/bus_stop_selection/bus_stop_selection.dart index 34bb85649..651d038c6 100644 --- a/uni/lib/view/bus_stop_selection/bus_stop_selection.dart +++ b/uni/lib/view/bus_stop_selection/bus_stop_selection.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:uni/controller/local_storage/app_bus_stop_database.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/bus_stop.dart'; import 'package:uni/model/providers/lazy/bus_stop_provider.dart'; import 'package:uni/view/bus_stop_selection/widgets/bus_stop_search.dart'; @@ -48,12 +49,11 @@ class BusStopSelectionPageState bottom: 20, ), children: [ - const PageTitle(name: 'Autocarros Configurados'), + PageTitle(name: S.of(context).configured_buses), Container( padding: const EdgeInsets.all(20), - child: const Text( - '''Os autocarros favoritos serão apresentados no widget 'Autocarros' dos favoritos. ''' - '''Os restantes serão apresentados apenas na página.''', + child: Text( + S.of(context).buses_text, textAlign: TextAlign.center, ), ), @@ -68,11 +68,11 @@ class BusStopSelectionPageState context: context, delegate: BusStopSearch(), ), - child: const Text('Adicionar'), + child: Text(S.of(context).add), ), ElevatedButton( onPressed: () => Navigator.pop(context), - child: const Text('Concluído'), + child: Text(S.of(context).conclude), ), ], ), diff --git a/uni/lib/view/bus_stop_selection/widgets/bus_stop_search.dart b/uni/lib/view/bus_stop_selection/widgets/bus_stop_search.dart index 9f24bccb4..be55fc3b8 100644 --- a/uni/lib/view/bus_stop_selection/widgets/bus_stop_search.dart +++ b/uni/lib/view/bus_stop_selection/widgets/bus_stop_search.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uni/controller/fetchers/departures_fetcher.dart'; import 'package:uni/controller/local_storage/app_bus_stop_database.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/bus_stop.dart'; import 'package:uni/model/providers/lazy/bus_stop_provider.dart'; import 'package:uni/view/bus_stop_selection/widgets/form.dart'; @@ -91,7 +92,7 @@ class BusStopSearch extends SearchDelegate { ); return AlertDialog( title: Text( - 'Seleciona os autocarros dos quais queres informação:', + S.of(context).bus_information, style: Theme.of(context).textTheme.headlineSmall, ), content: SizedBox( @@ -102,20 +103,18 @@ class BusStopSearch extends SearchDelegate { actions: [ TextButton( child: Text( - 'Cancelar', + S.of(context).cancel, style: Theme.of(context).textTheme.bodyMedium, ), onPressed: () => Navigator.pop(context), ), ElevatedButton( - child: const Text('Confirmar'), + child: Text(S.of(context).confirm), onPressed: () async { if (stopData!.configuredBuses.isNotEmpty) { await Provider.of(context, listen: false) .addUserBusStop(stopCode!, stopData!); - if (context.mounted) { - Navigator.pop(context); - } + if (context.mounted) Navigator.pop(context); } }, ) @@ -141,8 +140,8 @@ class BusStopSearch extends SearchDelegate { return Container( margin: const EdgeInsets.all(8), height: 24, - child: const Center( - child: Text('Sem resultados.'), + child: Center( + child: Text(S.of(context).no_results), ), ); } else { diff --git a/uni/lib/view/calendar/calendar.dart b/uni/lib/view/calendar/calendar.dart index 4e4f3ed83..5e5e79b68 100644 --- a/uni/lib/view/calendar/calendar.dart +++ b/uni/lib/view/calendar/calendar.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:timelines/timelines.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/calendar_event.dart'; import 'package:uni/model/providers/lazy/calendar_provider.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/calendar/widgets/calendar_tile.dart'; import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/general.dart'; @@ -24,7 +26,9 @@ class CalendarPageViewState extends GeneralPageViewState { children: [ Container( padding: const EdgeInsets.only(bottom: 6), - child: const PageTitle(name: 'Calendário Escolar'), + child: PageTitle( + name: S.of(context).nav_title(DrawerItem.navCalendar.title), + ), ), RequestDependentWidgetBuilder( status: calendarProvider.status, diff --git a/uni/lib/view/common_widgets/generic_card.dart b/uni/lib/view/common_widgets/generic_card.dart index 710edfd01..fb7ddde1e 100644 --- a/uni/lib/view/common_widgets/generic_card.dart +++ b/uni/lib/view/common_widgets/generic_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/time_utilities.dart'; /// App default card @@ -37,7 +38,7 @@ abstract class GenericCard extends StatefulWidget { Widget buildCardContent(BuildContext context); - String getTitle(); + String getTitle(BuildContext context); void onClick(BuildContext context); @@ -64,7 +65,9 @@ abstract class GenericCard extends StatefulWidget { return Container( alignment: Alignment.center, child: Text( - 'última atualização às ${parsedTime.toTimeHourMinString()}', + S.of(context).last_refresh_time( + parsedTime.toTimeHourMinString(), + ), style: Theme.of(context).textTheme.bodySmall, ), ); @@ -123,7 +126,7 @@ class GenericCardState extends State { padding: const EdgeInsets.symmetric(horizontal: 15), margin: const EdgeInsets.only(top: 15, bottom: 10), child: Text( - widget.getTitle(), + widget.getTitle(context), style: (widget.hasSmallTitle ? Theme.of(context).textTheme.titleLarge! : Theme.of(context) diff --git a/uni/lib/view/common_widgets/generic_expansion_card.dart b/uni/lib/view/common_widgets/generic_expansion_card.dart index 57702be05..e5faf99ca 100644 --- a/uni/lib/view/common_widgets/generic_expansion_card.dart +++ b/uni/lib/view/common_widgets/generic_expansion_card.dart @@ -16,14 +16,14 @@ abstract class GenericExpansionCard extends StatelessWidget { color: Theme.of(context).primaryColor, ); - String getTitle(); + String getTitle(BuildContext context); Widget buildCardContent(BuildContext context); @override Widget build(BuildContext context) { return Container( - margin: cardMargin ?? const EdgeInsets.fromLTRB(20, 10, 20, 0), + margin: const EdgeInsets.fromLTRB(20, 10, 20, 0), child: ExpansionTileCard( expandedTextColor: Theme.of(context).primaryColor, heightFactorCurve: Curves.ease, @@ -32,10 +32,11 @@ abstract class GenericExpansionCard extends StatelessWidget { ? const Color.fromARGB(0xf, 0, 0, 0) : const Color.fromARGB(255, 43, 43, 43), title: Text( - getTitle(), - style: Theme.of(context).textTheme.headlineSmall?.apply( - color: Theme.of(context).primaryColor, - ), + getTitle(context), + style: Theme.of(context) + .textTheme + .headlineSmall + ?.apply(color: Theme.of(context).primaryColor), ), elevation: 0, children: [ diff --git a/uni/lib/view/common_widgets/last_update_timestamp.dart b/uni/lib/view/common_widgets/last_update_timestamp.dart index 4f4820b17..f85358ddc 100644 --- a/uni/lib/view/common_widgets/last_update_timestamp.dart +++ b/uni/lib/view/common_widgets/last_update_timestamp.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/providers/state_provider_notifier.dart'; import 'package:uni/view/lazy_consumer.dart'; @@ -56,8 +57,7 @@ class _LastUpdateTimeStampState return Row( children: [ Text( - 'Atualizado há $elapsedTimeMinutes ' - 'minuto${elapsedTimeMinutes != 1 ? 's' : ''}', + S.of(context).last_timestamp(elapsedTimeMinutes), style: Theme.of(context).textTheme.titleSmall, ) ], diff --git a/uni/lib/view/common_widgets/pages_layouts/general/widgets/navigation_drawer.dart b/uni/lib/view/common_widgets/pages_layouts/general/widgets/navigation_drawer.dart index e8878acf6..5560d5611 100644 --- a/uni/lib/view/common_widgets/pages_layouts/general/widgets/navigation_drawer.dart +++ b/uni/lib/view/common_widgets/pages_layouts/general/widgets/navigation_drawer.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; import 'package:uni/utils/drawer_items.dart'; +import 'package:uni/view/locale_notifier.dart'; import 'package:uni/view/theme_notifier.dart'; class AppNavigationDrawer extends StatefulWidget { @@ -77,7 +79,7 @@ class AppNavigationDrawerState extends State { child: Container( padding: const EdgeInsets.all(15), child: Text( - logOutText, + S.of(context).logout, style: Theme.of(context) .textTheme .titleLarge! @@ -87,6 +89,30 @@ class AppNavigationDrawerState extends State { ); } + Widget createLocaleBtn() { + return Consumer( + builder: (context, localeNotifier, _) { + return TextButton( + onPressed: () => localeNotifier.setNextLocale(), + style: TextButton.styleFrom( + elevation: 0, + padding: const EdgeInsets.symmetric(horizontal: 5), + ), + child: Container( + padding: const EdgeInsets.all(15), + child: Text( + localeNotifier.getLocale().localeCode.languageCode.toUpperCase(), + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith(color: Theme.of(context).primaryColor), + ), + ), + ); + }, + ); + } + Widget createThemeSwitchBtn() { Icon getThemeIcon(ThemeMode theme) { switch (theme) { @@ -116,7 +142,7 @@ class AppNavigationDrawerState extends State { title: Container( padding: const EdgeInsets.only(bottom: 3, left: 20), child: Text( - d.title, + S.of(context).nav_title(d.title), style: TextStyle( fontSize: 18, color: Theme.of(context).primaryColor, @@ -156,7 +182,15 @@ class AppNavigationDrawerState extends State { ), Row( children: [ - Expanded(child: createLogoutBtn()), + Expanded( + child: Align( + child: createLogoutBtn(), + ), + ), + Align( + alignment: Alignment.centerRight, + child: createLocaleBtn(), + ), createThemeSwitchBtn() ], ) diff --git a/uni/lib/view/common_widgets/request_dependent_widget_builder.dart b/uni/lib/view/common_widgets/request_dependent_widget_builder.dart index 170203fdb..899de33a1 100644 --- a/uni/lib/view/common_widgets/request_dependent_widget_builder.dart +++ b/uni/lib/view/common_widgets/request_dependent_widget_builder.dart @@ -1,6 +1,7 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/request_status.dart'; import 'package:uni/utils/drawer_items.dart'; @@ -74,7 +75,7 @@ class RequestDependentWidgetBuilder extends StatelessWidget { return Center( heightFactor: 3, child: Text( - 'Sem ligação à internet', + S.of(context).check_internet, style: Theme.of(context).textTheme.titleMedium, ), ); @@ -86,7 +87,7 @@ class RequestDependentWidgetBuilder extends StatelessWidget { padding: const EdgeInsets.only(top: 15, bottom: 10), child: Center( child: Text( - 'Aconteceu um erro ao carregar os dados', + S.of(context).load_error, style: Theme.of(context).textTheme.titleMedium, ), ), @@ -96,7 +97,7 @@ class RequestDependentWidgetBuilder extends StatelessWidget { context, '/${DrawerItem.navBugReport.title}', ), - child: const Text('Reportar erro'), + child: Text(S.of(context).report_error), ) ], ); diff --git a/uni/lib/view/course_unit_info/course_unit_info.dart b/uni/lib/view/course_unit_info/course_unit_info.dart index 7c0d61596..ade00b29d 100644 --- a/uni/lib/view/course_unit_info/course_unit_info.dart +++ b/uni/lib/view/course_unit_info/course_unit_info.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/course_units/course_unit.dart'; import 'package:uni/model/providers/lazy/course_units_info_provider.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; @@ -67,8 +68,11 @@ class CourseUnitDetailPageViewState center: false, name: widget.courseUnit.name, ), - const TabBar( - tabs: [Tab(text: 'Ficha'), Tab(text: 'Turmas')], + TabBar( + tabs: [ + Tab(text: S.of(context).course_info), + Tab(text: S.of(context).course_class) + ], ), Expanded( child: Padding( @@ -90,9 +94,9 @@ class CourseUnitDetailPageViewState return LazyConsumer( builder: (context, courseUnitsInfoProvider) { return RequestDependentWidgetBuilder( - onNullContent: const Center( + onNullContent: Center( child: Text( - 'Não existem informações para apresentar', + S.of(context).no_info, textAlign: TextAlign.center, ), ), @@ -114,9 +118,9 @@ class CourseUnitDetailPageViewState return LazyConsumer( builder: (context, courseUnitsInfoProvider) { return RequestDependentWidgetBuilder( - onNullContent: const Center( + onNullContent: Center( child: Text( - 'Não existem turmas para apresentar', + S.of(context).no_class, textAlign: TextAlign.center, ), ), diff --git a/uni/lib/view/course_unit_info/widgets/course_unit_info_card.dart b/uni/lib/view/course_unit_info/widgets/course_unit_info_card.dart index 389e36ad8..b79eb7a06 100644 --- a/uni/lib/view/course_unit_info/widgets/course_unit_info_card.dart +++ b/uni/lib/view/course_unit_info/widgets/course_unit_info_card.dart @@ -16,7 +16,7 @@ class CourseUnitInfoCard extends GenericExpansionCard { } @override - String getTitle() { + String getTitle(BuildContext context) { return sectionTitle; } } diff --git a/uni/lib/view/course_units/course_units.dart b/uni/lib/view/course_units/course_units.dart index e59b22150..210930a9b 100644 --- a/uni/lib/view/course_units/course_units.dart +++ b/uni/lib/view/course_units/course_units.dart @@ -1,6 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/course_units/course_unit.dart'; import 'package:uni/model/providers/startup/profile_provider.dart'; import 'package:uni/model/request_status.dart'; @@ -90,7 +91,7 @@ class CourseUnitsPageViewState onNullContent: Center( heightFactor: 10, child: Text( - 'Não existem cadeiras para apresentar', + S.of(context).no_selected_courses, style: Theme.of(context).textTheme.titleLarge, ), ), @@ -106,12 +107,14 @@ class CourseUnitsPageViewState return Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - PageTitle(name: DrawerItem.navCourseUnits.title), + PageTitle( + name: S.of(context).nav_title(DrawerItem.navCourseUnits.title), + ), const Spacer(), DropdownButtonHideUnderline( child: DropdownButton( alignment: AlignmentDirectional.centerEnd, - disabledHint: const Text('Semestre'), + disabledHint: Text(S.of(context).semester), value: selectedSemester, icon: const Icon(Icons.arrow_drop_down), onChanged: (String? newValue) { @@ -129,7 +132,7 @@ class CourseUnitsPageViewState const SizedBox(width: 10), DropdownButtonHideUnderline( child: DropdownButton( - disabledHint: const Text('Ano'), + disabledHint: Text(S.of(context).year), value: selectedSchoolYear, icon: const Icon(Icons.arrow_drop_down), onChanged: (String? newValue) { @@ -156,7 +159,7 @@ class CourseUnitsPageViewState return Center( heightFactor: 10, child: Text( - 'Sem cadeiras no período selecionado', + S.of(context).no_course_units, style: Theme.of(context).textTheme.titleLarge, ), ); diff --git a/uni/lib/view/course_units/widgets/course_unit_card.dart b/uni/lib/view/course_units/widgets/course_unit_card.dart index 385fed7f8..b269666ee 100644 --- a/uni/lib/view/course_units/widgets/course_unit_card.dart +++ b/uni/lib/view/course_units/widgets/course_unit_card.dart @@ -29,7 +29,7 @@ class CourseUnitCard extends GenericCard { } @override - String getTitle() { + String getTitle(BuildContext context) { return courseUnit.name.length > maxTitleLength ? '${courseUnit.name.split(' ').sublist(0, 5).join(' ')}...' : courseUnit.name; diff --git a/uni/lib/view/exams/exams.dart b/uni/lib/view/exams/exams.dart index 59bda1785..0731720e9 100644 --- a/uni/lib/view/exams/exams.dart +++ b/uni/lib/view/exams/exams.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/exam.dart'; import 'package:uni/model/providers/lazy/exam_provider.dart'; import 'package:uni/view/common_widgets/expanded_image_label.dart'; @@ -9,6 +10,7 @@ import 'package:uni/view/exams/widgets/day_title.dart'; import 'package:uni/view/exams/widgets/exam_page_title.dart'; import 'package:uni/view/exams/widgets/exam_row.dart'; import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni/view/locale_notifier.dart'; class ExamsPageView extends StatefulWidget { const ExamsPageView({super.key}); @@ -47,13 +49,13 @@ class ExamsPageViewState extends GeneralPageViewState { heightFactor: 1.2, child: ImageLabel( imagePath: 'assets/images/vacation.png', - label: 'Parece que estás de férias!', + label: S.of(context).no_exams_label, labelTextStyle: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, color: Theme.of(context).colorScheme.primary, ), - sublabel: 'Não tens exames marcados', + sublabel: S.of(context).no_exams, sublabelTextStyle: const TextStyle(fontSize: 15), ), ), @@ -107,11 +109,12 @@ class ExamsPageViewState extends GeneralPageViewState { } Widget createExamsCards(BuildContext context, List exams) { + final locale = Provider.of(context).getLocale(); final examCards = [ DayTitle( day: exams[0].begin.day.toString(), - weekDay: exams[0].weekDay, - month: exams[0].month, + weekDay: exams[0].weekDay(locale), + month: exams[0].month(locale), ), ]; for (var i = 0; i < exams.length; i++) { diff --git a/uni/lib/view/exams/widgets/exam_filter_form.dart b/uni/lib/view/exams/widgets/exam_filter_form.dart index 91a1483aa..baf86edfb 100644 --- a/uni/lib/view/exams/widgets/exam_filter_form.dart +++ b/uni/lib/view/exams/widgets/exam_filter_form.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/exam.dart'; import 'package:uni/model/providers/lazy/exam_provider.dart'; class ExamFilterForm extends StatefulWidget { const ExamFilterForm(this.filteredExamsTypes, {super.key}); + final Map filteredExamsTypes; @override @@ -16,17 +18,19 @@ class ExamFilterFormState extends State { Widget build(BuildContext context) { return AlertDialog( title: Text( - 'Definições Filtro de Exames', + S.of(context).exams_filter, style: Theme.of(context).textTheme.headlineSmall, ), actions: [ TextButton( - child: - Text('Cancelar', style: Theme.of(context).textTheme.bodyMedium), + child: Text( + S.of(context).cancel, + style: Theme.of(context).textTheme.bodyMedium, + ), onPressed: () => Navigator.pop(context), ), ElevatedButton( - child: const Text('Confirmar'), + child: Text(S.of(context).confirm), onPressed: () { Provider.of(context, listen: false) .setFilteredExams(widget.filteredExamsTypes); diff --git a/uni/lib/view/exams/widgets/exam_page_title.dart b/uni/lib/view/exams/widgets/exam_page_title.dart index 1ffac8819..ff3d46e64 100644 --- a/uni/lib/view/exams/widgets/exam_page_title.dart +++ b/uni/lib/view/exams/widgets/exam_page_title.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/exams/widgets/exam_filter_menu.dart'; @@ -10,11 +12,15 @@ class ExamPageTitle extends StatelessWidget { return Container( padding: const EdgeInsets.fromLTRB(20, 20, 20, 10), alignment: Alignment.center, - child: const Row( + child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - PageTitle(name: 'Exames', center: false, pad: false), - Material(child: ExamFilterMenu()), + PageTitle( + name: S.of(context).nav_title(DrawerItem.navExams.title), + center: false, + pad: false, + ), + const Material(child: ExamFilterMenu()), ], ), ); diff --git a/uni/lib/view/home/widgets/bus_stop_card.dart b/uni/lib/view/home/widgets/bus_stop_card.dart index 6612ba988..813e2fe2c 100644 --- a/uni/lib/view/home/widgets/bus_stop_card.dart +++ b/uni/lib/view/home/widgets/bus_stop_card.dart @@ -1,6 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/bus_stop.dart'; import 'package:uni/model/providers/lazy/bus_stop_provider.dart'; import 'package:uni/utils/drawer_items.dart'; @@ -20,7 +21,8 @@ class BusStopCard extends GenericCard { }) : super.fromEditingInformation(); @override - String getTitle() => 'Autocarros'; + String getTitle(BuildContext context) => + S.of(context).nav_title(DrawerItem.navStops.title); @override Future onClick(BuildContext context) => @@ -95,19 +97,7 @@ class BusStopCard extends GenericCard { @override void onRefresh(BuildContext context) { Provider.of(context, listen: false).forceRefresh(context); - } - - /// Returns a widget for the title of the bus stops card - Widget getCardTitle(BuildContext context) { - return Row( - children: [ - const Icon(Icons.directions_bus), // color lightgrey - Text( - 'STCP - Próximas Viagens', - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ); + } } /// Returns a list of widgets for each bus stop info that exists diff --git a/uni/lib/view/home/widgets/exam_card.dart b/uni/lib/view/home/widgets/exam_card.dart index 0ce9e46de..3aeb60cf4 100644 --- a/uni/lib/view/home/widgets/exam_card.dart +++ b/uni/lib/view/home/widgets/exam_card.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/model/entities/app_locale.dart'; import 'package:uni/model/entities/exam.dart'; import 'package:uni/model/providers/lazy/exam_provider.dart'; import 'package:uni/utils/drawer_items.dart'; @@ -11,6 +13,7 @@ import 'package:uni/view/exams/widgets/exam_row.dart'; import 'package:uni/view/exams/widgets/exam_title.dart'; import 'package:uni/view/home/widgets/exam_card_shimmer.dart'; import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni/view/locale_notifier.dart'; /// Manages the exam card section inside the personal area. class ExamCard extends GenericCard { @@ -23,7 +26,8 @@ class ExamCard extends GenericCard { }) : super.fromEditingInformation(); @override - String getTitle() => 'Exames'; + String getTitle(BuildContext context) => + S.of(context).nav_title(DrawerItem.navExams.title); @override Future onClick(BuildContext context) => @@ -53,7 +57,7 @@ class ExamCard extends GenericCard { hasContentPredicate: exams.isNotEmpty, onNullContent: Center( child: Text( - 'Não existem exames para apresentar', + S.of(context).no_selected_exams, style: Theme.of(context).textTheme.titleLarge, ), ), @@ -103,18 +107,25 @@ class ExamCard extends GenericCard { /// Creates a row with the closest exam (which appears separated from the /// others in the card). Widget createRowFromExam(BuildContext context, Exam exam) { + final locale = Provider.of(context).getLocale(); return Column( children: [ - DateRectangle( - date: '${exam.weekDay}, ${exam.begin.day} de ${exam.month}', - ), + if (locale == AppLocale.pt) ...[ + DateRectangle( + date: '${exam.weekDay}, ${exam.begin.day} de ${exam.month}', + ) + ] else ...[ + DateRectangle( + date: '${exam.weekDay}, ${exam.begin.day} ${exam.month}', + ) + ], RowContainer( child: ExamRow( exam: exam, teacher: '', mainPage: true, ), - ), + ) ], ); } diff --git a/uni/lib/view/home/widgets/exit_app_dialog.dart b/uni/lib/view/home/widgets/exit_app_dialog.dart index 58be3e236..fe6ed0ab5 100644 --- a/uni/lib/view/home/widgets/exit_app_dialog.dart +++ b/uni/lib/view/home/widgets/exit_app_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:uni/generated/l10n.dart'; /// Manages the app section displayed when the user presses the back button class BackButtonExitWrapper extends StatelessWidget { @@ -17,18 +18,18 @@ class BackButtonExitWrapper extends StatelessWidget { context: context, builder: (context) => AlertDialog( title: Text( - 'Tens a certeza de que pretendes sair?', + S.of(context).exit_confirm, style: Theme.of(context).textTheme.headlineSmall, ), actions: [ ElevatedButton( onPressed: () => Navigator.of(context).pop(false), - child: const Text('Não'), + child: Text(S.of(context).no), ), ElevatedButton( onPressed: () => SystemChannels.platform.invokeMethod('SystemNavigator.pop'), - child: const Text('Sim'), + child: Text(S.of(context).yes), ) ], ), diff --git a/uni/lib/view/home/widgets/main_cards_list.dart b/uni/lib/view/home/widgets/main_cards_list.dart index acac8a12e..b044dccf2 100644 --- a/uni/lib/view/home/widgets/main_cards_list.dart +++ b/uni/lib/view/home/widgets/main_cards_list.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uni/controller/local_storage/app_shared_preferences.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/providers/lazy/home_page_provider.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; -import 'package:uni/utils/drawer_items.dart'; import 'package:uni/utils/favorite_widget_type.dart'; import 'package:uni/view/common_widgets/generic_card.dart'; import 'package:uni/view/common_widgets/page_title.dart'; @@ -87,7 +87,7 @@ class MainCardsList extends StatelessWidget { builder: (BuildContext context) { return AlertDialog( title: Text( - 'Escolhe um widget para adicionares à tua área pessoal:', + S.of(context).widget_prompt, style: Theme.of(context).textTheme.headlineSmall, ), content: SizedBox( @@ -98,7 +98,7 @@ class MainCardsList extends StatelessWidget { actions: [ TextButton( child: Text( - 'Cancelar', + S.of(context).cancel, style: Theme.of(context).textTheme.bodyMedium, ), onPressed: () => Navigator.pop(context), @@ -107,7 +107,7 @@ class MainCardsList extends StatelessWidget { ); }, ), //Add FAB functionality here - tooltip: 'Adicionar widget', + tooltip: S.of(context).add_widget, child: Icon(Icons.add, color: Theme.of(context).colorScheme.onPrimary), ); } @@ -128,7 +128,7 @@ class MainCardsList extends StatelessWidget { title: Text( e .value(Key(e.key.index.toString()), editingMode: false) - .getTitle(), + .getTitle(context), textAlign: TextAlign.center, ), onTap: () { @@ -141,11 +141,7 @@ class MainCardsList extends StatelessWidget { .toList(); return possibleCardAdditions.isEmpty - ? [ - const Text( - '''Todos os widgets disponíveis já foram adicionados à tua área pessoal!''', - ) - ] + ? [Text(S.of(context).all_widgets_added)] : possibleCardAdditions; } @@ -159,7 +155,7 @@ class MainCardsList extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ PageTitle( - name: DrawerItem.navPersonalArea.title, + name: S.of(context).nav_title('area'), center: false, pad: false, ), @@ -169,7 +165,9 @@ class MainCardsList extends StatelessWidget { editingMode: !editingModeProvider.isEditing, ), child: Text( - editingModeProvider.isEditing ? 'Concluir Edição' : 'Editar', + editingModeProvider.isEditing + ? S.of(context).edit_on + : S.of(context).edit_off, style: Theme.of(context).textTheme.bodySmall, ), ) diff --git a/uni/lib/view/home/widgets/restaurant_card.dart b/uni/lib/view/home/widgets/restaurant_card.dart index c12331a7a..ea23dc5be 100644 --- a/uni/lib/view/home/widgets/restaurant_card.dart +++ b/uni/lib/view/home/widgets/restaurant_card.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/meal.dart'; import 'package:uni/model/entities/restaurant.dart'; import 'package:uni/model/providers/lazy/restaurant_provider.dart'; @@ -21,7 +22,8 @@ class RestaurantCard extends GenericCard { }) : super.fromEditingInformation(); @override - String getTitle() => 'Restaurantes'; + String getTitle(BuildContext context) => + S.of(context).nav_title(DrawerItem.navRestaurants.title); @override Future onClick(BuildContext context) => @@ -53,7 +55,7 @@ class RestaurantCard extends GenericCard { padding: const EdgeInsets.only(top: 15, bottom: 10), child: Center( child: Text( - 'Sem restaurantes favoritos', + S.of(context).no_favorite_restaurants, style: Theme.of(context).textTheme.titleMedium, ), ), @@ -63,7 +65,7 @@ class RestaurantCard extends GenericCard { context, '/${DrawerItem.navRestaurants.title}', ), - child: const Text('Adicionar'), + child: Text(S.of(context).add), ) ], ), @@ -137,7 +139,7 @@ class RestaurantCard extends GenericCard { child: Container( padding: const EdgeInsets.fromLTRB(9, 0, 0, 0), width: 400, - child: const Text('Não há refeições disponíveis'), + child: Text(S.of(context).no_menu_info), ), ), ) diff --git a/uni/lib/view/home/widgets/schedule_card.dart b/uni/lib/view/home/widgets/schedule_card.dart index ac86fa085..edb0e3451 100644 --- a/uni/lib/view/home/widgets/schedule_card.dart +++ b/uni/lib/view/home/widgets/schedule_card.dart @@ -2,8 +2,8 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/lecture.dart'; -import 'package:uni/model/entities/time_utilities.dart'; import 'package:uni/model/providers/lazy/lecture_provider.dart'; import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/date_rectangle.dart'; @@ -11,6 +11,7 @@ import 'package:uni/view/common_widgets/generic_card.dart'; import 'package:uni/view/common_widgets/request_dependent_widget_builder.dart'; import 'package:uni/view/home/widgets/schedule_card_shimmer.dart'; import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni/view/locale_notifier.dart'; import 'package:uni/view/schedule/widgets/schedule_slot.dart'; class ScheduleCard extends GenericCard { @@ -40,7 +41,7 @@ class ScheduleCard extends GenericCard { hasContentPredicate: lectureProvider.lectures.isNotEmpty, onNullContent: Center( child: Text( - 'Não existem aulas para apresentar', + S.of(context).no_classes, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center, ), @@ -74,8 +75,9 @@ class ScheduleCard extends GenericCard { lastAddedLectureDate.compareTo(lectures[i].startTime) <= 0) { rows.add( DateRectangle( - date: TimeString.getWeekdaysStrings()[ - (lectures[i].startTime.weekday - 1) % 7], + date: + Provider.of(context).getWeekdaysWithLocale()[ + (lectures[i].startTime.weekday - 1) % 7], ), ); } @@ -90,8 +92,8 @@ class ScheduleCard extends GenericCard { rows ..add( DateRectangle( - date: TimeString.getWeekdaysStrings()[ - lectures[0].startTime.weekday % 7], + date: Provider.of(context) + .getWeekdaysWithLocale()[lectures[0].startTime.weekday % 7], ), ) ..add(createRowFromLecture(context, lectures[0])); @@ -116,7 +118,8 @@ class ScheduleCard extends GenericCard { } @override - String getTitle() => 'Horário'; + String getTitle(BuildContext context) => + S.of(context).nav_title(DrawerItem.navSchedule.title); @override Future onClick(BuildContext context) => diff --git a/uni/lib/view/lazy_consumer.dart b/uni/lib/view/lazy_consumer.dart index c1a15dcb3..49b19d748 100644 --- a/uni/lib/view/lazy_consumer.dart +++ b/uni/lib/view/lazy_consumer.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:logger/logger.dart'; @@ -46,10 +47,14 @@ class LazyConsumer extends StatelessWidget { }) : Future(() {}); } catch (exception, stackTrace) { - Logger().e( - 'Failed to initialize startup providers: $exception', - ); - await Sentry.captureException(exception, stackTrace: stackTrace); + // In tests, it is ok to not find the startup providers: + // all provider data should be mocked by the test itself. + if (!Platform.environment.containsKey('FLUTTER_TEST')) { + Logger().e( + 'Failed to initialize startup providers: $exception', + ); + await Sentry.captureException(exception, stackTrace: stackTrace); + } } // Load data stored in the database immediately diff --git a/uni/lib/view/library/library.dart b/uni/lib/view/library/library.dart index bacb1cac2..17f9d0f7c 100644 --- a/uni/lib/view/library/library.dart +++ b/uni/lib/view/library/library.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/library_occupation.dart'; import 'package:uni/model/providers/lazy/library_occupation_provider.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/general.dart'; import 'package:uni/view/lazy_consumer.dart'; @@ -40,9 +42,9 @@ class LibraryPage extends StatelessWidget { return ListView( shrinkWrap: true, children: [ - const PageTitle(name: 'Biblioteca'), + PageTitle(name: S.of(context).nav_title(DrawerItem.navLibrary.title)), LibraryOccupationCard(), - if (occupation != null) const PageTitle(name: 'Pisos'), + if (occupation != null) PageTitle(name: S.of(context).floors), if (occupation != null) getFloorRows(context, occupation!), ], ); @@ -99,7 +101,7 @@ class LibraryPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text( - 'Piso ${floor.number}', + '${S.of(context).floor} ${floor.number}', style: Theme.of(context).textTheme.headlineSmall, ), Text( diff --git a/uni/lib/view/library/widgets/library_occupation_card.dart b/uni/lib/view/library/widgets/library_occupation_card.dart index bd45a6678..ee6a4548b 100644 --- a/uni/lib/view/library/widgets/library_occupation_card.dart +++ b/uni/lib/view/library/widgets/library_occupation_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:percent_indicator/percent_indicator.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/library_occupation.dart'; import 'package:uni/model/providers/lazy/library_occupation_provider.dart'; import 'package:uni/model/request_status.dart'; @@ -20,7 +21,7 @@ class LibraryOccupationCard extends GenericCard { }) : super.fromEditingInformation(); @override - String getTitle() => 'Ocupação da Biblioteca'; + String getTitle(BuildContext context) => S.of(context).library_occupation; @override Future onClick(BuildContext context) => @@ -56,7 +57,7 @@ class LibraryOccupationCard extends GenericCard { if (occupation == null || occupation.capacity == 0) { return Center( child: Text( - 'Não existem dados para apresentar', + S.of(context).no_data, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center, ), diff --git a/uni/lib/view/locale_notifier.dart b/uni/lib/view/locale_notifier.dart new file mode 100644 index 000000000..5ecbccda6 --- /dev/null +++ b/uni/lib/view/locale_notifier.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:uni/controller/local_storage/app_shared_preferences.dart'; +import 'package:uni/model/entities/app_locale.dart'; + +class LocaleNotifier with ChangeNotifier { + LocaleNotifier(this._locale); + + AppLocale _locale; + + AppLocale getLocale() => _locale; + + void setNextLocale() { + const availableLocales = AppLocale.values; + final currentIndex = availableLocales.indexOf(_locale); + final nextLocale = + availableLocales[(currentIndex + 1) % availableLocales.length]; + setLocale(nextLocale); + } + + void setLocale(AppLocale locale) { + _locale = locale; + AppSharedPreferences.setLocale(locale); + notifyListeners(); + } + + List getWeekdaysWithLocale() { + final allWeekDays = DateFormat.EEEE().dateSymbols.WEEKDAYS; + final europeanWeekDays = allWeekDays.skip(1).toList() + ..add(allWeekDays.first); + return europeanWeekDays + .map((weekday) => weekday[0].toUpperCase() + weekday.substring(1)) + .toList(); + } +} diff --git a/uni/lib/view/locations/locations.dart b/uni/lib/view/locations/locations.dart index 777647ac2..f722cc7dd 100644 --- a/uni/lib/view/locations/locations.dart +++ b/uni/lib/view/locations/locations.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/location_group.dart'; import 'package:uni/model/providers/lazy/faculty_locations_provider.dart'; import 'package:uni/model/request_status.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/general.dart'; import 'package:uni/view/common_widgets/request_dependent_widget_builder.dart'; @@ -46,6 +48,7 @@ class LocationsPageView extends StatelessWidget { required this.status, super.key, }); + final List locations; final RequestStatus status; @@ -56,7 +59,10 @@ class LocationsPageView extends StatelessWidget { Container( width: MediaQuery.of(context).size.width * 0.95, padding: const EdgeInsets.fromLTRB(0, 0, 0, 4), - child: PageTitle(name: 'Locais: ${getLocation()}'), + child: PageTitle( + name: '${S.of(context).nav_title(DrawerItem.navLocations.title)}:' + ' ${getLocation()}', + ), ), Container( padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -66,8 +72,7 @@ class LocationsPageView extends StatelessWidget { status: status, builder: () => FacultyMap(faculty: 'FEUP', locations: locations), hasContentPredicate: locations.isNotEmpty, - onNullContent: - const Center(child: Text('Não existem locais disponíveis')), + onNullContent: Center(child: Text(S.of(context).no_places_info)), ), // TODO(bdmendes): add support for multiple faculties ) diff --git a/uni/lib/view/locations/widgets/map.dart b/uni/lib/view/locations/widgets/map.dart index f810d1a3a..1cb1b3365 100644 --- a/uni/lib/view/locations/widgets/map.dart +++ b/uni/lib/view/locations/widgets/map.dart @@ -17,6 +17,7 @@ class LocationsMap extends StatelessWidget { required this.locations, super.key, }); + final PopupController _popupLayerController = PopupController(); final List locations; final LatLng northEastBoundary; @@ -78,7 +79,7 @@ class LocationsMap extends StatelessWidget { ? FloorlessLocationMarkerPopup(marker.locationGroup) : LocationMarkerPopup(marker.locationGroup); } - return const Card(child: Text('undefined')); + return const Card(child: Text('')); }, ), ), diff --git a/uni/lib/view/locations/widgets/marker_popup.dart b/uni/lib/view/locations/widgets/marker_popup.dart index a683f613b..068bae2d6 100644 --- a/uni/lib/view/locations/widgets/marker_popup.dart +++ b/uni/lib/view/locations/widgets/marker_popup.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/location.dart'; import 'package:uni/model/entities/location_group.dart'; import 'package:uni/view/locations/widgets/faculty_map.dart'; @@ -46,6 +47,7 @@ class LocationMarkerPopup extends StatelessWidget { class Floor extends StatelessWidget { const Floor({required this.locations, required this.floor, super.key}); + final List locations; final int floor; @@ -62,7 +64,10 @@ class Floor extends StatelessWidget { children: [ Container( padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), - child: Text('Andar $floorString', style: TextStyle(color: fontColor)), + child: Text( + '${S.of(context).floor} $floorString', + style: TextStyle(color: fontColor), + ), ) ], ); @@ -86,6 +91,7 @@ class Floor extends StatelessWidget { class LocationRow extends StatelessWidget { const LocationRow({required this.location, required this.color, super.key}); + final Location location; final Color color; diff --git a/uni/lib/view/login/login.dart b/uni/lib/view/login/login.dart index 3a4bb418c..7cea404ee 100644 --- a/uni/lib/view/login/login.dart +++ b/uni/lib/view/login/login.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/login_exceptions.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; import 'package:uni/model/providers/state_providers.dart'; @@ -49,6 +50,7 @@ class LoginPageViewState extends State { try { await sessionProvider.postAuthentication( + context, user, pass, faculties, @@ -65,7 +67,7 @@ class LoginPageViewState extends State { } else if (error is WrongCredentialsException) { unawaited(ToastMessage.error(context, error.message)); } else { - unawaited(ToastMessage.error(context, 'Erro no login')); + unawaited(ToastMessage.error(context, S.of(context).failed_login)); } } } @@ -176,7 +178,7 @@ class LoginPageViewState extends State { if (_exitApp) { return Future.value(true); } - ToastMessage.info(context, 'Pressione novamente para sair'); + ToastMessage.info(context, S.of(context).press_again); exitAppWaiter(); return Future.value(false); } @@ -235,6 +237,7 @@ class LoginPageViewState extends State { padding: EdgeInsets.only(bottom: queryData.size.height / 35), ), createSaveDataCheckBox( + context, _setKeepSignedIn, keepSignedIn: _keepSignedIn, ), @@ -249,7 +252,7 @@ class LoginPageViewState extends State { return InkWell( child: Center( child: Text( - 'Esqueceu a palavra-passe?', + S.of(context).forgot_password, style: Theme.of(context).textTheme.bodyLarge!.copyWith( decoration: TextDecoration.underline, color: Colors.white, @@ -282,6 +285,8 @@ class LoginPageViewState extends State { context, '/${DrawerItem.navPersonalArea.title}', ); + } else if (status == RequestStatus.failed) { + ToastMessage.error(context, S.of(context).failed_login); } } @@ -290,21 +295,20 @@ class LoginPageViewState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('A tua palavra-passe expirou'), + title: Text(S.of(context).expired_password), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( - 'Por razões de segurança, as palavras-passe têm de ser ' - 'alteradas periodicamente.', + S.of(context).pass_change_request, textAlign: TextAlign.start, style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 20), - const Align( + Align( alignment: Alignment.centerLeft, child: Text( - 'Deseja alterar a palavra-passe?', + S.of(context).change_prompt, textAlign: TextAlign.start, ), ), @@ -312,13 +316,13 @@ class LoginPageViewState extends State { ), actions: [ TextButton( - child: const Text('Cancelar'), + child: Text(S.of(context).cancel), onPressed: () { Navigator.of(context).pop(); }, ), ElevatedButton( - child: const Text('Alterar'), + child: Text(S.of(context).change), onPressed: () async { const url = 'https://self-id.up.pt/password'; if (await canLaunchUrl(Uri.parse(url))) { diff --git a/uni/lib/view/login/widgets/faculties_multiselect.dart b/uni/lib/view/login/widgets/faculties_multiselect.dart index 91e47c030..4188d558f 100644 --- a/uni/lib/view/login/widgets/faculties_multiselect.dart +++ b/uni/lib/view/login/widgets/faculties_multiselect.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/view/login/widgets/faculties_selection_form.dart'; class FacultiesMultiselect extends StatelessWidget { @@ -51,7 +52,7 @@ class FacultiesMultiselect extends StatelessWidget { children: [ Expanded( child: Text( - _facultiesListText(), + _facultiesListText(context), style: const TextStyle(color: Colors.white), ), ), @@ -64,9 +65,9 @@ class FacultiesMultiselect extends StatelessWidget { ); } - String _facultiesListText() { + String _facultiesListText(BuildContext context) { if (selectedFaculties.isEmpty) { - return 'sem faculdade'; + return S.of(context).no_college; } final buffer = StringBuffer(); for (final faculty in selectedFaculties) { diff --git a/uni/lib/view/login/widgets/faculties_selection_form.dart b/uni/lib/view/login/widgets/faculties_selection_form.dart index 537bf5edd..88db0b18b 100644 --- a/uni/lib/view/login/widgets/faculties_selection_form.dart +++ b/uni/lib/view/login/widgets/faculties_selection_form.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/utils/constants.dart' as constants; import 'package:uni/view/common_widgets/toast_message.dart'; @@ -20,7 +21,7 @@ class _FacultiesSelectionFormState extends State { Widget build(BuildContext context) { return AlertDialog( backgroundColor: const Color.fromARGB(255, 0x75, 0x17, 0x1e), - title: const Text('seleciona a(s) tua(s) faculdade(s)'), + title: Text(S.of(context).college_select), titleTextStyle: const TextStyle( color: Color.fromARGB(255, 0xfa, 0xfa, 0xfa), fontSize: 18, @@ -38,7 +39,10 @@ class _FacultiesSelectionFormState extends State { return [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancelar', style: TextStyle(color: Colors.white)), + child: Text( + S.of(context).cancel, + style: const TextStyle(color: Colors.white), + ), ), ElevatedButton( style: ElevatedButton.styleFrom( @@ -49,14 +53,14 @@ class _FacultiesSelectionFormState extends State { if (widget.selectedFaculties.isEmpty) { ToastMessage.warning( context, - 'Seleciona pelo menos uma faculdade', + S.of(context).at_least_one_college, ); return; } Navigator.pop(context); widget.setFaculties(widget.selectedFaculties); }, - child: const Text('Confirmar'), + child: Text(S.of(context).confirm), ) ]; } diff --git a/uni/lib/view/login/widgets/inputs.dart b/uni/lib/view/login/widgets/inputs.dart index a123af1c3..ddbde1f26 100644 --- a/uni/lib/view/login/widgets/inputs.dart +++ b/uni/lib/view/login/widgets/inputs.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/view/about/widgets/terms_and_conditions.dart'; import 'package:uni/view/login/widgets/faculties_multiselect.dart'; @@ -30,8 +31,9 @@ Widget createUsernameInput( }, textInputAction: TextInputAction.next, textAlign: TextAlign.left, - decoration: textFieldDecoration('número de estudante'), - validator: (String? value) => value!.isEmpty ? 'Preenche este campo' : null, + decoration: textFieldDecoration(S.of(context).student_number), + validator: (String? value) => + value!.isEmpty ? S.of(context).empty_text : null, ); } @@ -57,27 +59,28 @@ Widget createPasswordInput( obscureText: obscurePasswordInput, textAlign: TextAlign.left, decoration: passwordFieldDecoration( - 'palavra-passe', + S.of(context).password, toggleObscurePasswordInput, obscurePasswordInput: obscurePasswordInput, ), validator: (String? value) => - value != null && value.isEmpty ? 'Preenche este campo' : null, + value != null && value.isEmpty ? S.of(context).empty_text : null, ); } /// Creates the widget for the user to keep signed in (save his data). Widget createSaveDataCheckBox( + BuildContext context, void Function({bool? value})? setKeepSignedIn, { required bool keepSignedIn, }) { return CheckboxListTile( value: keepSignedIn, onChanged: (value) => setKeepSignedIn?.call(value: value), - title: const Text( - 'Manter sessão iniciada', + title: Text( + S.of(context).keep_login, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( color: Colors.white, fontSize: 17, fontWeight: FontWeight.w300, @@ -113,7 +116,7 @@ Widget createLogInButton( login(context); }, child: Text( - 'Entrar', + S.of(context).login, style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.w400, @@ -177,10 +180,10 @@ InkResponse createSafeLoginButton(BuildContext context) { highlightColor: Colors.transparent, child: Container( padding: const EdgeInsets.all(8), - child: const Text( - '''Ao entrares confirmas que concordas com estes Termos e Condições''', + child: Text( + S.of(context).agree_terms, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( decoration: TextDecoration.underline, color: Colors.white, fontSize: 17, @@ -197,7 +200,7 @@ Future _showLoginDetails(BuildContext context) async { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Termos e Condições'), + title: Text(S.of(context).terms), content: const SingleChildScrollView(child: TermsAndConditions()), actions: [ SimpleDialogOption( diff --git a/uni/lib/view/profile/widgets/account_info_card.dart b/uni/lib/view/profile/widgets/account_info_card.dart index 7a2bbb148..a93394460 100644 --- a/uni/lib/view/profile/widgets/account_info_card.dart +++ b/uni/lib/view/profile/widgets/account_info_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/reference.dart'; import 'package:uni/model/providers/lazy/reference_provider.dart'; import 'package:uni/model/providers/startup/profile_provider.dart'; @@ -51,7 +52,7 @@ class AccountInfoCard extends GenericCard { left: 20, ), child: Text( - 'Saldo: ', + S.of(context).balance, style: Theme.of(context).textTheme.titleSmall, ), ), @@ -74,7 +75,7 @@ class AccountInfoCard extends GenericCard { left: 20, ), child: Text( - 'Data limite próxima prestação: ', + S.of(context).fee_date, style: Theme.of(context).textTheme.titleSmall, ), ), @@ -88,7 +89,7 @@ class AccountInfoCard extends GenericCard { profile.feesLimit != null ? DateFormat('yyyy-MM-dd') .format(profile.feesLimit!) - : 'Sem data', + : S.of(context).no_date, context, ), ) @@ -103,7 +104,7 @@ class AccountInfoCard extends GenericCard { left: 20, ), child: Text( - 'Notificar próxima data limite: ', + S.of(context).fee_notification, style: Theme.of(context).textTheme.titleSmall, ), ), @@ -124,7 +125,7 @@ class AccountInfoCard extends GenericCard { child: Row( children: [ Text( - 'Referências pendentes', + S.of(context).pendent_references, style: Theme.of(context).textTheme.titleLarge?.apply( color: Theme.of(context).colorScheme.secondary, ), @@ -147,7 +148,7 @@ class AccountInfoCard extends GenericCard { } @override - String getTitle() => 'Conta Corrente'; + String getTitle(BuildContext context) => S.of(context).account_card_title; @override void onClick(BuildContext context) {} @@ -164,7 +165,7 @@ class ReferenceList extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric(vertical: 10), child: Text( - 'Não existem referências a pagar', + S.of(context).no_references, style: Theme.of(context).textTheme.titleSmall, textScaleFactor: 0.96, ), diff --git a/uni/lib/view/profile/widgets/course_info_card.dart b/uni/lib/view/profile/widgets/course_info_card.dart index 55d0eb890..eb920ea65 100644 --- a/uni/lib/view/profile/widgets/course_info_card.dart +++ b/uni/lib/view/profile/widgets/course_info_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/course.dart'; import 'package:uni/view/common_widgets/generic_card.dart'; @@ -19,13 +20,16 @@ class CourseInfoCard extends GenericCard { Container( margin: const EdgeInsets.only(top: 20, bottom: 8, left: 20), child: Text( - 'Ano curricular atual: ', + S.of(context).current_year, style: Theme.of(context).textTheme.titleSmall, ), ), Container( margin: const EdgeInsets.only(top: 20, bottom: 8, right: 20), - child: getInfoText(course.currYear ?? 'Indisponível', context), + child: getInfoText( + course.currYear ?? S.of(context).unavailable, + context, + ), ) ], ), @@ -34,13 +38,16 @@ class CourseInfoCard extends GenericCard { Container( margin: const EdgeInsets.only(top: 10, bottom: 8, left: 20), child: Text( - 'Estado atual: ', + S.of(context).current_state, style: Theme.of(context).textTheme.titleSmall, ), ), Container( margin: const EdgeInsets.only(top: 10, bottom: 8, right: 20), - child: getInfoText(course.state ?? 'Indisponível', context), + child: getInfoText( + course.state ?? S.of(context).unavailable, + context, + ), ) ], ), @@ -49,7 +56,7 @@ class CourseInfoCard extends GenericCard { Container( margin: const EdgeInsets.only(top: 10, bottom: 8, left: 20), child: Text( - 'Ano da primeira inscrição: ', + S.of(context).first_year_registration, style: Theme.of(context).textTheme.titleSmall, ), ), @@ -69,14 +76,14 @@ class CourseInfoCard extends GenericCard { Container( margin: const EdgeInsets.only(top: 10, bottom: 8, left: 20), child: Text( - 'Faculdade: ', + S.of(context).college, style: Theme.of(context).textTheme.titleSmall, ), ), Container( margin: const EdgeInsets.only(top: 10, bottom: 8, right: 20), child: getInfoText( - course.faculty?.toUpperCase() ?? 'Indisponível', + course.faculty?.toUpperCase() ?? S.of(context).unavailable, context, ), ) @@ -87,14 +94,14 @@ class CourseInfoCard extends GenericCard { Container( margin: const EdgeInsets.only(top: 10, bottom: 8, left: 20), child: Text( - 'Média: ', + S.of(context).average, style: Theme.of(context).textTheme.titleSmall, ), ), Container( margin: const EdgeInsets.only(top: 10, bottom: 8, right: 20), child: getInfoText( - course.currentAverage?.toString() ?? 'Indisponível', + course.currentAverage?.toString() ?? S.of(context).unavailable, context, ), ) @@ -105,7 +112,7 @@ class CourseInfoCard extends GenericCard { Container( margin: const EdgeInsets.only(top: 10, bottom: 20, left: 20), child: Text( - 'ECTs realizados: ', + S.of(context).ects, style: Theme.of(context).textTheme.titleSmall, ), ), @@ -123,8 +130,8 @@ class CourseInfoCard extends GenericCard { } @override - String getTitle() { - return course.name ?? 'Curso sem nome'; + String getTitle(BuildContext context) { + return course.name ?? S.of(context).no_course_units; } @override diff --git a/uni/lib/view/profile/widgets/create_print_mb_dialog.dart b/uni/lib/view/profile/widgets/create_print_mb_dialog.dart index f2fb513e5..51ddddd4a 100644 --- a/uni/lib/view/profile/widgets/create_print_mb_dialog.dart +++ b/uni/lib/view/profile/widgets/create_print_mb_dialog.dart @@ -2,6 +2,7 @@ import 'package:currency_text_input_formatter/currency_text_input_formatter.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uni/controller/fetchers/print_fetcher.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; import 'package:uni/view/common_widgets/toast_message.dart'; @@ -32,9 +33,7 @@ Future addMoneyDialog(BuildContext context) async { Padding( padding: const EdgeInsets.only(top: 5, bottom: 10), child: Text( - 'Os dados da referência gerada aparecerão no Sigarra, ' - 'conta corrente. \n' - 'Perfil > Conta Corrente', + S.of(context).reference_sigarra_help, textAlign: TextAlign.start, style: Theme.of(context).textTheme.titleSmall, ), @@ -43,7 +42,7 @@ Future addMoneyDialog(BuildContext context) async { children: [ IconButton( icon: const Icon(Icons.indeterminate_check_box), - tooltip: 'Decrementar 1,00€', + tooltip: S.of(context).decrement, onPressed: () { final decreasedValue = valueTextToNumber(controller.text) - 1; @@ -83,7 +82,7 @@ Future addMoneyDialog(BuildContext context) async { ), IconButton( icon: const Icon(Icons.add_box), - tooltip: 'Incrementar 1,00€', + tooltip: S.of(context).increment, onPressed: () { controller.value = TextEditingValue( text: numberToValueText( @@ -98,20 +97,20 @@ Future addMoneyDialog(BuildContext context) async { ), ), title: Text( - 'Adicionar quota', + S.of(context).add_quota, style: Theme.of(context).textTheme.headlineSmall, ), actions: [ TextButton( child: Text( - 'Cancelar', + S.of(context).cancel, style: Theme.of(context).textTheme.bodyMedium, ), onPressed: () => Navigator.pop(context), ), ElevatedButton( onPressed: () => generateReference(context, value), - child: const Text('Gerar referência'), + child: Text(S.of(context).generate_reference), ) ], ); @@ -132,7 +131,7 @@ String numberToValueText(double number) => Future generateReference(BuildContext context, double amount) async { if (amount < 1) { - await ToastMessage.warning(context, 'Valor mínimo: 1,00 €'); + await ToastMessage.warning(context, S.of(context).min_value_reference); return; } @@ -142,8 +141,8 @@ Future generateReference(BuildContext context, double amount) async { if (response.statusCode == 200 && context.mounted) { Navigator.of(context).pop(false); - await ToastMessage.success(context, 'Referência criada com sucesso!'); + await ToastMessage.success(context, S.of(context).reference_success); } else { - await ToastMessage.error(context, 'Algum erro!'); + await ToastMessage.error(context, S.of(context).some_error); } } diff --git a/uni/lib/view/profile/widgets/print_info_card.dart b/uni/lib/view/profile/widgets/print_info_card.dart index b7bee2fbe..fa26a3ebb 100644 --- a/uni/lib/view/profile/widgets/print_info_card.dart +++ b/uni/lib/view/profile/widgets/print_info_card.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/providers/startup/profile_provider.dart'; import 'package:uni/view/common_widgets/generic_card.dart'; import 'package:uni/view/lazy_consumer.dart'; @@ -38,7 +39,7 @@ class PrintInfoCard extends GenericCard { left: 20, ), child: Text( - 'Valor disponível: ', + S.of(context).available_amount, style: Theme.of(context).textTheme.titleSmall, ), ), @@ -76,7 +77,7 @@ class PrintInfoCard extends GenericCard { } @override - String getTitle() => 'Impressões'; + String getTitle(BuildContext context) => S.of(context).prints; @override void onClick(BuildContext context) {} diff --git a/uni/lib/view/restaurant/restaurant_page_view.dart b/uni/lib/view/restaurant/restaurant_page_view.dart index 0703c5519..80a1c96b5 100644 --- a/uni/lib/view/restaurant/restaurant_page_view.dart +++ b/uni/lib/view/restaurant/restaurant_page_view.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/meal.dart'; import 'package:uni/model/entities/restaurant.dart'; import 'package:uni/model/providers/lazy/restaurant_provider.dart'; import 'package:uni/model/utils/day_of_week.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/general.dart'; import 'package:uni/view/common_widgets/request_dependent_widget_builder.dart'; import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni/view/locale_notifier.dart'; import 'package:uni/view/restaurant/widgets/restaurant_page_card.dart'; import 'package:uni/view/restaurant/widgets/restaurant_slot.dart'; @@ -46,8 +49,10 @@ class _RestaurantPageViewState extends GeneralPageViewState Container( padding: const EdgeInsets.fromLTRB(20, 20, 20, 10), alignment: Alignment.center, - child: const PageTitle( - name: 'Restaurantes', + child: PageTitle( + name: S + .of(context) + .nav_title(DrawerItem.navRestaurants.title), center: false, pad: false, ), @@ -65,9 +70,8 @@ class _RestaurantPageViewState extends GeneralPageViewState builder: () => createTabViewBuilder(restaurantProvider.restaurants, context), hasContentPredicate: restaurantProvider.restaurants.isNotEmpty, - onNullContent: - const Center(child: Text('Não há refeições disponíveis.')), - ) + onNullContent: Center(child: Text(S.of(context).no_menus)), + ), ], ); }, @@ -96,12 +100,14 @@ class _RestaurantPageViewState extends GeneralPageViewState } List createTabs(BuildContext context) { + final daysOfTheWeek = + Provider.of(context).getWeekdaysWithLocale(); final tabs = []; for (var i = 0; i < DayOfWeek.values.length; i++) { tabs.add( Tab( key: Key('cantine-page-tab-$i'), - text: toString(DayOfWeek.values[i]), + text: daysOfTheWeek[i], ), ); } @@ -135,12 +141,10 @@ class _RestaurantPageViewState extends GeneralPageViewState return Container( margin: const EdgeInsets.only(top: 10, bottom: 5), key: Key('restaurant-page-day-column-$day'), - child: const Column( + child: Column( mainAxisSize: MainAxisSize.min, children: [ - Center( - child: Text('Não há informação disponível sobre refeições'), - ), + Center(child: Text(S.of(context).no_menu_info)), ], ), ); diff --git a/uni/lib/view/restaurant/widgets/restaurant_page_card.dart b/uni/lib/view/restaurant/widgets/restaurant_page_card.dart index 5abefb029..d02d616aa 100644 --- a/uni/lib/view/restaurant/widgets/restaurant_page_card.dart +++ b/uni/lib/view/restaurant/widgets/restaurant_page_card.dart @@ -22,7 +22,7 @@ class RestaurantPageCard extends GenericCard { } @override - String getTitle() { + String getTitle(BuildContext context) { return restaurant.name; } diff --git a/uni/lib/view/schedule/schedule.dart b/uni/lib/view/schedule/schedule.dart index fd146c54d..084a9d856 100644 --- a/uni/lib/view/schedule/schedule.dart +++ b/uni/lib/view/schedule/schedule.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/lecture.dart'; -import 'package:uni/model/entities/time_utilities.dart'; import 'package:uni/model/providers/lazy/lecture_provider.dart'; import 'package:uni/model/request_status.dart'; import 'package:uni/utils/drawer_items.dart'; @@ -10,6 +10,7 @@ import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/general.dart'; import 'package:uni/view/common_widgets/request_dependent_widget_builder.dart'; import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni/view/locale_notifier.dart'; import 'package:uni/view/schedule/widgets/schedule_slot.dart'; class SchedulePage extends StatefulWidget { @@ -46,13 +47,10 @@ class SchedulePageView extends StatefulWidget { final int weekDay = DateTime.now().weekday; - static final List daysOfTheWeek = - TimeString.getWeekdaysStrings(includeWeekend: false); - static List> groupLecturesByDay(List schedule) { final aggLectures = >[]; - for (var i = 0; i < daysOfTheWeek.length; i++) { + for (var i = 0; i < 5; i++) { final lectures = {}; for (var j = 0; j < schedule.length; j++) { if (schedule[j].startTime.weekday - 1 == i) lectures.add(schedule[j]); @@ -75,11 +73,9 @@ class SchedulePageViewState extends GeneralPageViewState super.initState(); tabController = TabController( vsync: this, - length: SchedulePageView.daysOfTheWeek.length, + length: 5, ); - final offset = (widget.weekDay > 5) - ? 0 - : (widget.weekDay - 1) % SchedulePageView.daysOfTheWeek.length; + final offset = (widget.weekDay > 5) ? 0 : (widget.weekDay - 1) % 5; tabController?.animateTo(tabController!.index + offset); } @@ -98,7 +94,11 @@ class SchedulePageViewState extends GeneralPageViewState ListView( shrinkWrap: true, children: [ - PageTitle(name: DrawerItem.navSchedule.title), + PageTitle( + name: S.of(context).nav_title( + DrawerItem.navSchedule.title, + ), + ), TabBar( controller: tabController, isScrollable: true, @@ -110,8 +110,11 @@ class SchedulePageViewState extends GeneralPageViewState Expanded( child: TabBarView( controller: tabController, - children: - createSchedule(context, widget.lectures, widget.scheduleStatus), + children: createSchedule( + context, + widget.lectures, + widget.scheduleStatus, + ), ), ) ], @@ -121,17 +124,20 @@ class SchedulePageViewState extends GeneralPageViewState /// Returns a list of widgets empty with tabs for each day of the week. List createTabs(MediaQueryData queryData, BuildContext context) { final tabs = []; - for (var i = 0; i < SchedulePageView.daysOfTheWeek.length; i++) { + final workWeekDays = Provider.of(context) + .getWeekdaysWithLocale() + .sublist(0, 5); + workWeekDays.asMap().forEach((index, day) { tabs.add( SizedBox( - width: queryData.size.width * 1 / 4, + width: (queryData.size.width * 1) / 4, child: Tab( - key: Key('schedule-page-tab-$i'), - text: SchedulePageView.daysOfTheWeek[i], + key: Key('schedule-page-tab-$index'), + text: day, ), ), ); - } + }); return tabs; } @@ -141,7 +147,7 @@ class SchedulePageViewState extends GeneralPageViewState RequestStatus? scheduleStatus, ) { final tabBarViewContent = []; - for (var i = 0; i < SchedulePageView.daysOfTheWeek.length; i++) { + for (var i = 0; i < 5; i++) { tabBarViewContent .add(createScheduleByDay(context, i, lectures, scheduleStatus)); } @@ -190,6 +196,8 @@ class SchedulePageViewState extends GeneralPageViewState List lectures, RequestStatus? scheduleStatus, ) { + final weekday = + Provider.of(context).getWeekdaysWithLocale()[day]; final aggLectures = SchedulePageView.groupLecturesByDay(lectures); return RequestDependentWidgetBuilder( status: scheduleStatus ?? RequestStatus.none, @@ -198,7 +206,7 @@ class SchedulePageViewState extends GeneralPageViewState onNullContent: Center( child: ImageLabel( imagePath: 'assets/images/schedule.png', - label: 'Não possui aulas à ${SchedulePageView.daysOfTheWeek[day]}.', + label: '${S.of(context).no_classes_on} $weekday.', labelTextStyle: const TextStyle(fontSize: 15), ), ), diff --git a/uni/lib/view/useful_info/useful_info.dart b/uni/lib/view/useful_info/useful_info.dart index 1989b73d2..0fae6b457 100644 --- a/uni/lib/view/useful_info/useful_info.dart +++ b/uni/lib/view/useful_info/useful_info.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/general.dart'; import 'package:uni/view/useful_info/widgets/academic_services_card.dart'; @@ -37,7 +39,9 @@ class UsefulInfoPageViewState extends GeneralPageViewState { Container _getPageTitle() { return Container( padding: const EdgeInsets.only(bottom: 6), - child: const PageTitle(name: 'Úteis'), + child: PageTitle( + name: S.of(context).nav_title(DrawerItem.navUsefulInfo.title), + ), ); } diff --git a/uni/lib/view/useful_info/widgets/academic_services_card.dart b/uni/lib/view/useful_info/widgets/academic_services_card.dart index 452725278..212b670e3 100644 --- a/uni/lib/view/useful_info/widgets/academic_services_card.dart +++ b/uni/lib/view/useful_info/widgets/academic_services_card.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/generic_expansion_card.dart'; import 'package:uni/view/useful_info/widgets/text_components.dart'; @@ -9,12 +11,16 @@ class AcademicServicesCard extends GenericExpansionCard { Widget buildCardContent(BuildContext context) { return Column( children: [ - h1('Horário', context, initial: true), - h2('Atendimento presencial', context), + h1( + S.of(context).nav_title(DrawerItem.navSchedule.title), + context, + initial: true, + ), + h2(S.of(context).personal_assistance, context), infoText('11:00h - 16:00h', context), - h2('Atendimento telefónico', context), + h2(S.of(context).tele_assistance, context), infoText('9:30h - 12:00h | 14:00h - 16:00h', context), - h1('Telefone', context), + h1(S.of(context).telephone, context), infoText( '+351 225 081 977', context, @@ -26,5 +32,5 @@ class AcademicServicesCard extends GenericExpansionCard { } @override - String getTitle() => 'Serviços Académicos'; + String getTitle(BuildContext context) => S.of(context).academic_services; } diff --git a/uni/lib/view/useful_info/widgets/copy_center_card.dart b/uni/lib/view/useful_info/widgets/copy_center_card.dart index 8a48026f7..63804215b 100644 --- a/uni/lib/view/useful_info/widgets/copy_center_card.dart +++ b/uni/lib/view/useful_info/widgets/copy_center_card.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/generic_expansion_card.dart'; import 'package:uni/view/useful_info/widgets/text_components.dart'; @@ -9,10 +11,14 @@ class CopyCenterCard extends GenericExpansionCard { Widget buildCardContent(BuildContext context) { return Column( children: [ - h1('Horário', context, initial: true), - h2('Piso -1 do edifício B | Edifício da AEFEUP', context), + h1( + S.of(context).nav_title(DrawerItem.navSchedule.title), + context, + initial: true, + ), + h2(S.of(context).copy_center_building, context), infoText('9:00h - 11:30h | 12:30h - 18:00h', context), - h1('Telefone', context), + h1(S.of(context).telephone, context), h2('FEUP ', context), infoText('+351 220 994 122', context, link: 'tel:220 994 122'), h2('AEFEUP ', context), @@ -29,5 +35,5 @@ class CopyCenterCard extends GenericExpansionCard { } @override - String getTitle() => 'Centro de Cópias'; + String getTitle(BuildContext context) => S.of(context).copy_center; } diff --git a/uni/lib/view/useful_info/widgets/dona_bia_card.dart b/uni/lib/view/useful_info/widgets/dona_bia_card.dart index f7b2f5408..bb4c9561b 100644 --- a/uni/lib/view/useful_info/widgets/dona_bia_card.dart +++ b/uni/lib/view/useful_info/widgets/dona_bia_card.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/generic_expansion_card.dart'; import 'package:uni/view/useful_info/widgets/text_components.dart'; @@ -9,10 +11,14 @@ class DonaBiaCard extends GenericExpansionCard { Widget buildCardContent(BuildContext context) { return Column( children: [ - h1('Horário', context, initial: true), - h2('Piso -1 do edifício B (B -142)', context), + h1( + S.of(context).nav_title(DrawerItem.navSchedule.title), + context, + initial: true, + ), + h2(S.of(context).dona_bia_building, context), infoText('8:30h - 12:00h | 13:30h - 19:00h', context), - h1('Telefone', context), + h1(S.of(context).telephone, context), infoText('+351 225 081 416', context, link: 'tel:225 081 416'), h1('Email', context), infoText( @@ -26,5 +32,5 @@ class DonaBiaCard extends GenericExpansionCard { } @override - String getTitle() => 'Papelaria D. Beatriz'; + String getTitle(BuildContext context) => S.of(context).dona_bia; } diff --git a/uni/lib/view/useful_info/widgets/infodesk_card.dart b/uni/lib/view/useful_info/widgets/infodesk_card.dart index 7574239b4..b921c0309 100644 --- a/uni/lib/view/useful_info/widgets/infodesk_card.dart +++ b/uni/lib/view/useful_info/widgets/infodesk_card.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/generic_expansion_card.dart'; import 'package:uni/view/useful_info/widgets/text_components.dart'; @@ -9,10 +11,14 @@ class InfoDeskCard extends GenericExpansionCard { Widget buildCardContent(BuildContext context) { return Column( children: [ - h1('Horário', context, initial: true), - h2('Atendimento presencial e telefónico', context), + h1( + S.of(context).nav_title(DrawerItem.navSchedule.title), + context, + initial: true, + ), + h2(S.of(context).tele_personal_assistance, context), infoText('9:30h - 13:00h | 14:00h - 17:30h', context), - h1('Telefone', context), + h1(S.of(context).telephone, context), infoText('+351 225 081 400', context, link: 'tel:225 081 400'), h1('Email', context), infoText( @@ -26,5 +32,5 @@ class InfoDeskCard extends GenericExpansionCard { } @override - String getTitle() => 'Infodesk'; + String getTitle(BuildContext context) => 'Infodesk'; } diff --git a/uni/lib/view/useful_info/widgets/multimedia_center_card.dart b/uni/lib/view/useful_info/widgets/multimedia_center_card.dart index d0bdc7e72..fef087645 100644 --- a/uni/lib/view/useful_info/widgets/multimedia_center_card.dart +++ b/uni/lib/view/useful_info/widgets/multimedia_center_card.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/common_widgets/generic_expansion_card.dart'; import 'package:uni/view/useful_info/widgets/text_components.dart'; @@ -9,10 +11,14 @@ class MultimediaCenterCard extends GenericExpansionCard { Widget buildCardContent(BuildContext context) { return Column( children: [ - h1('Horário', context, initial: true), - h2('Sala B123', context), + h1( + S.of(context).nav_title(DrawerItem.navSchedule.title), + context, + initial: true, + ), + h2('${S.of(context).room} B123', context), infoText('9:00h - 12:30h | 14:30h - 17:00h', context), - h1('Telefone', context), + h1(S.of(context).telephone, context), infoText('+351 225 081 466', context, link: 'tel:225 081 466'), h1('Email', context), infoText( @@ -26,5 +32,5 @@ class MultimediaCenterCard extends GenericExpansionCard { } @override - String getTitle() => 'Centro de Multimédia'; + String getTitle(BuildContext context) => S.of(context).multimedia_center; } diff --git a/uni/lib/view/useful_info/widgets/other_links_card.dart b/uni/lib/view/useful_info/widgets/other_links_card.dart index a1a43bf48..47ae82046 100644 --- a/uni/lib/view/useful_info/widgets/other_links_card.dart +++ b/uni/lib/view/useful_info/widgets/other_links_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/view/common_widgets/generic_expansion_card.dart'; import 'package:uni/view/useful_info/widgets/link_button.dart'; @@ -11,7 +12,7 @@ class OtherLinksCard extends GenericExpansionCard { Widget buildCardContent(BuildContext context) { return const Column( children: [ - // LinkButton(title: 'Impressão', link: 'https://print.up.pt'), + // LinkButton(title: S.of(context).print, link: 'https://print.up.pt'), // TODO(Process-ing): Get fixed link LinkButton( title: 'Consultas SASUP', @@ -22,5 +23,5 @@ class OtherLinksCard extends GenericExpansionCard { } @override - String getTitle() => 'Outros Links'; + String getTitle(BuildContext context) => S.of(context).other_links; } diff --git a/uni/lib/view/useful_info/widgets/sigarra_links_card.dart b/uni/lib/view/useful_info/widgets/sigarra_links_card.dart index c5e7dcd5b..caec46c16 100644 --- a/uni/lib/view/useful_info/widgets/sigarra_links_card.dart +++ b/uni/lib/view/useful_info/widgets/sigarra_links_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/view/common_widgets/generic_expansion_card.dart'; import 'package:uni/view/useful_info/widgets/link_button.dart'; @@ -9,32 +10,32 @@ class SigarraLinksCard extends GenericExpansionCard { @override Widget buildCardContent(BuildContext context) { - return const Column( + return Column( children: [ LinkButton( - title: 'Notícias', + title: S.of(context).news, link: 'https://sigarra.up.pt/feup/pt/noticias_geral.lista_noticias', ), - LinkButton( + const LinkButton( title: 'Erasmus', link: 'https://sigarra.up.pt/feup/pt/web_base.gera_pagina?P_pagina=257769', ), LinkButton( - title: 'Inscrição Geral', + title: S.of(context).geral_registration, link: 'https://sigarra.up.pt/feup/pt/ins_geral.inscricao', ), LinkButton( - title: 'Inscrição de Turmas', + title: S.of(context).class_registration, link: 'https://sigarra.up.pt/feup/pt/it_geral.ver_insc', ), LinkButton( - title: 'Inscrição para Melhoria', + title: S.of(context).improvement_registration, link: 'https://sigarra.up.pt/feup/pt/inqueritos_geral.inqueritos_list', ), LinkButton( - title: 'Calendário Escolar', + title: S.of(context).school_calendar, link: 'https://sigarra.up.pt/feup/pt/web_base.gera_pagina?p_pagina=p%c3%a1gina%20est%c3%a1tica%20gen%c3%a9rica%20106', ) @@ -43,5 +44,5 @@ class SigarraLinksCard extends GenericExpansionCard { } @override - String getTitle() => 'Links Sigarra'; + String getTitle(BuildContext context) => 'Links Sigarra'; } diff --git a/uni/pubspec.yaml b/uni/pubspec.yaml index b9b44c113..d1c137447 100644 --- a/uni/pubspec.yaml +++ b/uni/pubspec.yaml @@ -7,15 +7,12 @@ publish_to: 'none' # We do not publish to pub.dev # To change it manually, override the value in app_version.txt. # The app version code is automatically also bumped by CI. # Do not change it manually. -version: 1.5.62+180 +version: 1.5.64+182 environment: sdk: '>=3.0.0 <4.0.0' flutter: 3.10.6 -# Dependencies specify other packages that the application needs in order to work. -# Major versions and critical security upgrades are managed by dependabot, and -# should not be changed manually without a good reason. dependencies: add_2_calendar: ^2.1.3 cached_network_image: ^3.2.3 @@ -27,10 +24,12 @@ dependencies: email_validator: ^2.0.1 encrypt: ^5.0.0-beta.1 expansion_tile_card: ^3.0.0 - flutter: + flutter: sdk: flutter flutter_dotenv: ^5.0.2 flutter_local_notifications: ^15.1.0+1 + flutter_localizations: + sdk: flutter flutter_map: ^4.0.0 flutter_map_marker_popup: ^5.0.0 flutter_markdown: ^0.6.0 @@ -39,7 +38,7 @@ dependencies: html: ^0.15.0 http: ^0.13.0 image: ^4.0.13 - intl: ^0.18.1 + intl: ^0.18.0 latlong2: ^0.8.1 logger: ^1.1.0 material_design_icons_flutter: ^7.0.7296 @@ -57,7 +56,6 @@ dependencies: url_launcher: ^6.0.2 workmanager: ^0.5.1 - dev_dependencies: build_runner: ^2.4.6 flutter_launcher_icons: ^0.13.1 @@ -72,20 +70,15 @@ dev_dependencies: test: any very_good_analysis: ^4.0.0+1 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: + generate: true uses-material-design: true - assets: - assets/ - assets/images/ - assets/text/ - assets/text/locations/ - assets/meal-icons/ - fonts: - family: Raleway fonts: @@ -110,9 +103,13 @@ flutter: - family: LocationIcons fonts: - asset: assets/fonts/LocationIcons.ttf + flutter_icons: android: "ic_launcher" ios: true image_path: "assets/icon/icon.png" adaptive_icon_background: "#75171E" adaptive_icon_foreground: "assets/icon/android_icon_foreground.png" + +flutter_intl: + enabled: true diff --git a/uni/test/integration/src/schedule_page_test.dart b/uni/test/integration/src/schedule_page_test.dart index 823690a33..e775c86b2 100644 --- a/uni/test/integration/src/schedule_page_test.dart +++ b/uni/test/integration/src/schedule_page_test.dart @@ -58,10 +58,11 @@ void main() { final providers = [ ChangeNotifierProvider(create: (_) => scheduleProvider), - ChangeNotifierProvider(create: (_) => sessionProvider), + ChangeNotifierProvider(create: (_) => sessionProvider), ]; await tester.pumpWidget(testableWidget(widget, providers: providers)); + await tester.pump(); const scheduleSlotTimeKey1 = 'schedule-slot-time-11:00-13:00'; const scheduleSlotTimeKey2 = 'schedule-slot-time-14:00-16:00'; diff --git a/uni/test/mocks/integration/src/schedule_page_test.mocks.dart b/uni/test/mocks/integration/src/schedule_page_test.mocks.dart index c3259a73a..e5e3b4548 100644 --- a/uni/test/mocks/integration/src/schedule_page_test.mocks.dart +++ b/uni/test/mocks/integration/src/schedule_page_test.mocks.dart @@ -493,6 +493,7 @@ class MockSessionProvider extends _i1.Mock implements _i7.SessionProvider { ); @override _i4.Future postAuthentication( + _i10.BuildContext? context, String? username, String? password, List? faculties, { @@ -502,6 +503,7 @@ class MockSessionProvider extends _i1.Mock implements _i7.SessionProvider { Invocation.method( #postAuthentication, [ + context, username, password, faculties, diff --git a/uni/test/test_widget.dart b/uni/test/test_widget.dart index 3171ad692..b710b524a 100644 --- a/uni/test/test_widget.dart +++ b/uni/test/test_widget.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; -import 'package:uni/model/providers/startup/profile_provider.dart'; -import 'package:uni/model/providers/startup/session_provider.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/model/entities/app_locale.dart'; +import 'package:uni/view/locale_notifier.dart'; Widget testableWidget( Widget widget, { @@ -9,8 +11,9 @@ Widget testableWidget( }) { return MultiProvider( providers: [ - ChangeNotifierProvider(create: (context) => SessionProvider()), - ChangeNotifierProvider(create: (context) => ProfileProvider()), + ChangeNotifierProvider( + create: (_) => LocaleNotifier(AppLocale.pt), + ), ...providers ], child: wrapWidget(widget), @@ -19,6 +22,14 @@ Widget testableWidget( Widget wrapWidget(Widget widget) { return MaterialApp( + localizationsDelegates: const [ + S.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + locale: const Locale('pt'), + supportedLocales: S.delegate.supportedLocales, home: Scaffold( body: widget, ), diff --git a/uni/test/unit/view/Pages/exams_page_view_test.dart b/uni/test/unit/view/Pages/exams_page_view_test.dart index 119e408c2..d4e3a227f 100644 --- a/uni/test/unit/view/Pages/exams_page_view_test.dart +++ b/uni/test/unit/view/Pages/exams_page_view_test.dart @@ -48,6 +48,7 @@ void main() { final providers = [ChangeNotifierProvider(create: (_) => examProvider)]; await tester.pumpWidget(testableWidget(widget, providers: providers)); + await tester.pump(); expect(find.byKey(Key(firstExam.toString())), findsOneWidget); expect(find.byKey(Key('$firstExam-exam')), findsOneWidget); @@ -90,6 +91,7 @@ void main() { final providers = [ChangeNotifierProvider(create: (_) => examProvider)]; await tester.pumpWidget(testableWidget(widget, providers: providers)); + await tester.pump(); expect( find.byKey(Key(examList.map((ex) => ex.toString()).join())), @@ -135,6 +137,8 @@ void main() { final providers = [ChangeNotifierProvider(create: (_) => examProvider)]; await tester.pumpWidget(testableWidget(widget, providers: providers)); + await tester.pump(); + expect(find.byKey(Key(firstExam.toString())), findsOneWidget); expect(find.byKey(Key(secondExam.toString())), findsOneWidget); expect(find.byKey(Key('$firstExam-exam')), findsOneWidget); @@ -202,6 +206,8 @@ void main() { final providers = [ChangeNotifierProvider(create: (_) => examProvider)]; await tester.pumpWidget(testableWidget(widget, providers: providers)); + await tester.pump(); + expect(find.byKey(Key(firstDayKey)), findsOneWidget); expect(find.byKey(Key(secondDayKey)), findsOneWidget); expect(find.byKey(Key('$firstExam-exam')), findsOneWidget); diff --git a/uni/test/unit/view/Widgets/exam_row_test.dart b/uni/test/unit/view/Widgets/exam_row_test.dart index 47ea0bac1..02b547631 100644 --- a/uni/test/unit/view/Widgets/exam_row_test.dart +++ b/uni/test/unit/view/Widgets/exam_row_test.dart @@ -35,6 +35,8 @@ void main() { ChangeNotifierProvider(create: (_) => ExamProvider()) ]; await tester.pumpWidget(testableWidget(widget, providers: providers)); + await tester.pump(); + final roomsKey = '$subject-$rooms-$beginTime-$endTime'; expect( @@ -56,6 +58,8 @@ void main() { ]; await tester.pumpWidget(testableWidget(widget, providers: providers)); + await tester.pump(); + final roomsKey = '$subject-$rooms-$beginTime-$endTime'; expect( diff --git a/uni/test/unit/view/Widgets/schedule_slot_test.dart b/uni/test/unit/view/Widgets/schedule_slot_test.dart index 2a85c7c3a..4b06fdacc 100644 --- a/uni/test/unit/view/Widgets/schedule_slot_test.dart +++ b/uni/test/unit/view/Widgets/schedule_slot_test.dart @@ -29,6 +29,7 @@ void main() { ); await tester.pumpWidget(testableWidget(widget)); + await tester.pump(); testScheduleSlot(subject, beginText, endText, rooms, typeClass, teacher); });