diff --git a/.github/workflows/validate-translations.yml b/.github/workflows/validate-translations.yml new file mode 100644 index 000000000..8bb49d670 --- /dev/null +++ b/.github/workflows/validate-translations.yml @@ -0,0 +1,49 @@ +name: Test Makefile + +on: + workflow_dispatch: + + push: + branches: [ develop ] + + pull_request: + +jobs: + translations: + name: "${{ matrix.case.name }}" + runs-on: macos-14 + + strategy: + matrix: + case: + - name: clean_translations + command: | + make clean_translations; + + - name: extract_translations + command: | + make extract_translations; + echo "Ensure combined localization file exists"; + test -f I18N/I18N/en.lproj/Localizable.strings; + + - name: pull_translations + command: + make pull_translations; + echo "Files are split properly"; + test -f Authorization/Authorization/uk.lproj/Localizable.strings; + + steps: + - uses: nschloe/action-cached-lfs-checkout@v1.2.1 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Use Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Install translations requirements + run: make translation_requirements + + - name: "${{ matrix.case.name }}" + run: "${{ matrix.case.command }}" diff --git a/Authorization/Authorization.xcodeproj/project.pbxproj b/Authorization/Authorization.xcodeproj/project.pbxproj index acde4b3e3..9e9990972 100644 --- a/Authorization/Authorization.xcodeproj/project.pbxproj +++ b/Authorization/Authorization.xcodeproj/project.pbxproj @@ -53,7 +53,6 @@ 025F40E129D360E20064C183 /* ResetPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordViewModel.swift; sourceTree = ""; }; 02A2ACDA2A4B016100FBBBBB /* AuthorizationAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationAnalytics.swift; sourceTree = ""; }; 02E0618329DC2373006E9024 /* ResetPasswordViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordViewModelTests.swift; sourceTree = ""; }; - 02ED50CC29A64B90008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02F3BFE4292533720051930C /* AuthorizationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationRouter.swift; sourceTree = ""; }; 071009C628D1DA4F00344290 /* SignInViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewModel.swift; sourceTree = ""; }; 07169454296D913300E3DED6 /* AuthorizationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AuthorizationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -528,7 +527,6 @@ isa = PBXVariantGroup; children = ( 0770DE6C28D0C035006D8A5D /* en */, - 02ED50CC29A64B90008341CD /* uk */, ); name = Localizable.strings; sourceTree = ""; diff --git a/Authorization/Authorization/uk.lproj/Localizable.strings b/Authorization/Authorization/uk.lproj/Localizable.strings deleted file mode 100644 index 00ab874ce..000000000 --- a/Authorization/Authorization/uk.lproj/Localizable.strings +++ /dev/null @@ -1,49 +0,0 @@ -/* - Localizable.strings - Authorization - - Created by Vladimir Chekyrta on 13.09.2022. - -*/ - -"SIGN_IN.LOG_IN_TITLE" = "Увійти"; -"SIGN_IN.WELCOME_BACK" = "Welcome back! Sign in to access your courses."; -"SIGN_IN.EMAIL" = "Пошта"; -"SIGN_IN.PASSWORD" = "Пароль"; -"SIGN_IN.FORGOT_PASS_BTN" = "Забули пароль?"; -"SIGN_IN.AGREEMENT" = "By signing in to this app, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data in -accordance with the [Privacy Policy.](%@)"; - -"ERROR.INVALID_EMAIL_ADDRESS" = "невірна адреса електронної пошти"; -"ERROR.INVALID_PASSWORD_LENGHT" = "Пароль занадто короткий або занадто довгий"; -"ERROR.ACCOUNT_NOT_REGISTERED" = "This %@ account is not linked with any %@ account. Please register."; -"ERROR.DISABLED_ACCOUNT" = "Your account is disabled. Please contact customer support for assistance."; - -"SIGN_UP.SUBTITLE" = "Create an account to start learning today!"; -"SIGN_UP.CREATE_ACCOUNT_BTN" = "Створити акаунт"; -"SIGN_UP.HIDE_FIELDS" = "Приховати необовʼязкові поля"; -"SIGN_UP.SHOW_FIELDS" = "Показати необовʼязкові поля"; -"SIGN_UP.SUCCESS_SIGNIN_LABEL" = "You've successfully signed in."; -"SIGN_UP.SUCCESS_SIGNIN_SUBLABEL" = "We just need a little more information before you start learning."; -"SIGN_UP.AGREEMENT" = "By creating an account, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data inaccordance with the [Privacy Policy.](%@)"; -"SIGN_UP.MARKETING_EMAIL_TITLE" = "I agree that %@ may send me marketing messages."; - -"FORGOT.TITLE"= "Відновлення паролю"; -"FORGOT.DESCRIPTION" = "Будь ласка, введіть свою адресу електронної пошти для входу або відновлення нижче, і ми надішлемо вам електронний лист з інструкціями."; -"FORGOT.REQUEST" = "Відновити пароль"; -"FORGOT.CHECK_TITLE" = "Перевірте свою електронну пошту"; -"FORGOT.CHECK_Description" = "Ми надіслали інструкції щодо відновлення пароля на вашу електронну пошту "; - -"SIGN_IN_WITH" = "Sign in with"; -"REGISTER_WITH" = "Register with"; -"APPLE" = "Apple"; -"GOOGLE" = "Google"; -"FACEBOOK" = "Facebook"; -"MICROSOFT" = "Microsoft"; -"OR" = "Or"; - -"STARTUP.INFO_MESSAGE" = "Courses and programs from the world's best universities in your pocket."; -"STARTUP.SEARCH_TITLE" = "What do you want to learn?"; -"STARTUP.SEARCH_PLACEHOLDER" = "Search our 3000+ courses"; -"STARTUP.EXPLORE_ALL_COURSES" = "Explore all courses"; -"STARTUP.TITLE" = "Start"; diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 0b4400836..5e0328b90 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -289,7 +289,6 @@ 02EBC7562C19DCDB00BE182C /* SyncStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStatus.swift; sourceTree = ""; }; 02EBC7582C19DE1100BE182C /* CalendarManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarManagerProtocol.swift; sourceTree = ""; }; 02EBC75A2C19DE3D00BE182C /* CourseForSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseForSync.swift; sourceTree = ""; }; - 02ED50CB29A64B84008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02F164362902A9EB0090DDEF /* StringExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; 02F6EF3A28D9B8EC00835477 /* CourseCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseCellView.swift; sourceTree = ""; }; 02F6EF4928D9F0A700835477 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = ""; }; @@ -1293,7 +1292,6 @@ isa = PBXVariantGroup; children = ( 0770DE5C28D0B209006D8A5D /* en */, - 02ED50CB29A64B84008341CD /* uk */, ); name = Localizable.strings; sourceTree = ""; diff --git a/Core/Core/uk.lproj/Localizable.strings b/Core/Core/uk.lproj/Localizable.strings deleted file mode 100644 index f907e02f0..000000000 --- a/Core/Core/uk.lproj/Localizable.strings +++ /dev/null @@ -1,127 +0,0 @@ -/* - Localizable.strings - Core - - Created by Vladimir Chekyrta on 13.09.2022. - -*/ - -"MAINSCREEN.DISCOVERY" = "Всі курси"; -"MAINSCREEN.DASHBOARD" = "Мої курси"; -"MAINSCREEN.IN_DEVELOPING" = "В розробці"; -"MAINSCREEN.PROGRAMS" = "Програми"; -"MAINSCREEN.PROFILE" = "Профіль"; -"MAINSCREEN.LEARN" = "Навчання"; - -"VIEW.SNACKBAR.TRY_AGAIN_BTN" = "Спробувати ще"; - -"ERROR.INVALID_CREDENTIALS" = "Недійсні дані авторизації"; -"ERROR.SLOW_OR_NO_INTERNET_CONNECTION" = "Повільне або відсутнє з’єднання з Інтернетом"; -"ERROR.NO_CACHED_DATA" = "Немає збережених даних для автономного режиму"; -"ERROR.USER_NOT_ACTIVE" = "Обліковий запис користувача не активовано. Будь ласка, спочатку активуйте свій обліковий запис."; -"ERROR.UNKNOWN_ERROR" = "Щось пішло не так"; -"ERROR.WIFI" = "Завантажувати файли можна лише через Wi-Fi. Ви можете змінити це в налаштуваннях."; - -"ERROR.INTERNET.NO_INTERNET_TITLE" = "Немає підключення до Інтернету"; -"ERROR.INTERNET.NO_INTERNET_DESCRIPTION" = "Будь ласка, підключіться до Інтернету, щоб переглянути цей вміст."; - -"COURSEWARE.COURSE_CONTENT" = "Зміст курсу"; -"COURSEWARE.COURSE_CONTENT_NOT_AVAILABLE" = "This interactive component isn't yet available on mobile."; -"COURSEWARE.COURSE_UNITS" = "Модулі"; -"COURSEWARE.NEXT" = "Далі"; -"COURSEWARE.PREVIOUS" = "Назад"; -"COURSEWARE.FINISH" = "Завершити"; -"COURSEWARE.GOOD_WORK" = "Гарна робота!"; -"COURSEWARE.BACK_TO_OUTLINE" = "Повернутись до модуля"; -"COURSEWARE.SECTION_COMPLETED" = "Ви завершили “%@”."; -"COURSEWARE.CONTINUE" = "Продовжити"; -"COURSEWARE.RESUME" = "Resume"; -"COURSEWARE.RESUME_WITH" = "Продовжити далі:"; -"COURSEWARE.NEXT_SECTION" = "Наступний розділ"; - -"COURSEWARE.NEXT_SECTION_DESCRIPTION_FIRST" = "Щоб перейти до “"; -"COURSEWARE.NEXT_SECTION_DESCRIPTION_LAST" = "” натисніть “Наступний розділ”."; - -"ERROR.RELOAD" = "Перезавантажити"; - -"DATE.ENDED" = "Кінець"; -"DATE.START" = "Початок"; -"DATE.STARTED" = "Почався"; -"DATE.JUST_NOW" = "Прямо зараз"; - -"ALERT.ACCEPT" = "ТАК"; -"ALERT.CANCEL" = "СКАСУВАТИ"; -"ALERT.LOGOUT" = "Вийти"; -"ALERT.LEAVE" = "Покинути"; -"ALERT.KEEP_EDITING" = "Залишитись"; -"ALERT.ADD" = "Add"; -"ALERT.REMOVE" = "Remove"; -"ALERT.CALENDAR_SHIFT_PROMPT_REMOVE_COURSE_CALENDAR"="Remove course calendar"; - -"NO_INTERNET.OFFLINE" = "Офлайн режим"; -"NO_INTERNET.DISMISS" = "Сховати"; -"NO_INTERNET.RELOAD" = "Перезавантажити"; - -"DATE_FORMAT.MMMM_dd" = "dd MMMM"; -"DATE_FORMAT.MMM_DD_YYYY" = "dd MMMM yyyy"; - -"DOWNLOAD_MANAGER.DOWNLOAD" = "Скачати"; -"DOWNLOAD_MANAGER.DOWNLOADED" = "Скачано"; -"DOWNLOAD_MANAGER.COMPLETED" = "Завершено"; - -"SETTINGS.VIDEO_DOWNLOAD_QUALITY_TITLE" = "Video download quality"; -"SETTINGS.DOWNLOAD_QUALITY_AUTO_TITLE" = "Auto"; -"SETTINGS.DOWNLOAD_QUALITY_AUTO_DESCRIPTION" = "Recommended"; -"SETTINGS.DOWNLOAD_QUALITY_360_TITLE" = "360p"; -"SETTINGS.DOWNLOAD_QUALITY_360_DESCRIPTION" = "Lower data usage"; -"SETTINGS.DOWNLOAD_QUALITY_540_TITLE" = "540p"; -"SETTINGS.DOWNLOAD_QUALITY_720_TITLE" = "720p"; -"SETTINGS.DOWNLOAD_QUALITY_720_DESCRIPTION" = "Best quality"; - -"DONE" = "Зберегти"; - -"PICKER.SEARCH" = "Знайти"; -"PICKER.ACCEPT" = "Прийняти"; - -"WEBVIEW.ALERT.OK" = "Так"; -"WEBVIEW.ALERT.CANCEL" = "Скасувати"; -"WEBVIEW.ALERT.CONTINUE" = "Continue"; - - -"REVIEW.VOTE_TITLE" = "Вам подобається Open edX?"; -"REVIEW.VOTE_DESCRIPTION" = "Ваш відгук важливий для нас. Можливо, ви візьмете хвилинку, щоб оцінити додаток, натиснувши на зірку нижче? Дякуємо за вашу підтримку!"; -"REVIEW.FEEDBACK_TITLE" = "Залиште відгук"; -"REVIEW.FEEDBACK_DESCRIPTION" = "Нам шкода чути, що ваше навчання мало деякі проблеми. Ми вдячні за будь-який відгук."; -"REVIEW.THANKS_FOR_VOTE_TITLE" = "Дякуємо"; -"REVIEW.THANKS_FOR_VOTE_DESCRIPTION" = "Дякуємо, що поділилися своїми враженнями з нами. Бажаєте залишити свій відгук про цей додаток для інших користувачів в магазині додатків?"; -"REVIEW.THANKS_FOR_FEEDBACK_TITLE" = "Дякуємо"; -"REVIEW.THANKS_FOR_FEEDBACK_DESCRIPTION" = "Ми отримали ваш відгук і використовуватимемо його для покращення вашого навчального досвіду в майбутньому!"; -"REVIEW.BETTER" = "Що можна було б зробити краще?"; -"REVIEW.NOT_NOW" = "Не зараз"; - -"REVIEW.BUTTON.SUBMIT" = "Надіслати"; -"REVIEW.BUTTON.SHARE_FEEDBACK" = "Поділитися відгуком"; -"REVIEW.BUTTON.RATE_US" = "Оцінити нас"; -"REVIEW.EMAIL.TITLE" = "Виберіть поштового клієнта:"; - -"SOCIAL_SIGN_CANCELED" = "The user canceled the sign-in flow."; -"AUTHORIZATION_FAILED" = "Authorization failed."; - -"SIGN_IN.LOG_IN_BTN" = "Увійти"; -"REGISTER" = "Реєстрація"; - -"TOMORROW" = "Завтра"; -"YESTERDAY" = "Учора"; -"OPEN_IN_BROWSER"="Переглянути в Safari"; -"COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.BODY" = "Не хвилюйтеся - перенесіть наш запропонований розклад, щоб виконати прострочені завдання без втрати прогресу."; -"COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.BUTTON" = "Зміщення термінів виконання"; -"COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.HEADER" = "Пропустив деякі терміни?"; -"COURSE_DATES.RESET_DATE.TAB_INFO_BANNER.BODY" = "Ми склали запропонований розклад, щоб допомогти вам не відставати від курсу. Але не хвилюйтеся – він гнучкий, тож ви можете навчатися у своєму власному темпі. Якщо трапиться, що ви відстаєте, ви будете мати можливість коригувати дати, щоб тримати себе в курсі."; -"COURSE_DATES.RESET_DATE.TAB_INFO_BANNER.HEADER" = ""; -"COURSE_DATES.RESET_DATE.UPGRADE_TO_COMPLETE_GRADED_BANNER.BODY" = "Щоб виконати оцінені завдання в рамках цього курсу, ви можете оновити сьогодні."; "COURSE_DATES.RESET_DATE.UPGRADE_TO_COMPLETE_GRADED_BANNER.BUTTON" = ""; -"COURSE_DATES.RESET_DATE.UPGRADE_TO_COMPLETE_GRADED_BANNER.HEADER" = ""; -"COURSE_DATES.RESET_DATE.UPGRADE_TO_RESET_BANNER.BODY" = "Ви перевіряєте цей курс, що означає, що ви не можете брати участь у оцінюваних завданнях. Схоже, що ви пропустили деякі важливі терміни згідно з нашим запропонованим розкладом. Щоб виконати оцінені завдання в рамках цей курс і перенести прострочені завдання в майбутнє, ви можете оновити сьогодні."; -"COURSE_DATES.RESET_DATE.UPGRADE_TO_RESET_BANNER.BUTTON" = ""; -"COURSE_DATES.RESET_DATE.UPGRADE_TO_RESET_BANNER.HEADER" = ""; -"COURSE_DATES.RESET_DATE.ERROR_MESSAGE" = "Ваші дати не можуть бути зміщені. Спробуйте ще раз."; -"COURSE_DATES.RESET_DATE.SUCCESS_MESSAGE" = "Ваші дати успішно перенесено."; "COURSE_DATES.RESET_DATE.TITLE" = "Дати курсу"; diff --git a/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index 33c43eeaf..2ec932caa 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -154,7 +154,6 @@ 02D4FC2D2BBD7C9C00C47748 /* MessageSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSectionView.swift; sourceTree = ""; }; 02E3803D2BFF9F0A00815AFA /* CourseProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseProgressView.swift; sourceTree = ""; }; 02EBC7542C19CFCF00BE182C /* CalendarSyncStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarSyncStatusView.swift; sourceTree = ""; }; - 02ED50CF29A64BB6008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02F0144E28F46474002E513D /* CourseContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseContainerView.swift; sourceTree = ""; }; 02F0145628F4A2FF002E513D /* CourseContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseContainerViewModel.swift; sourceTree = ""; }; 02F3BFDC29252E900051930C /* CourseRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseRouter.swift; sourceTree = ""; }; @@ -309,8 +308,8 @@ 02B6B3B828E1D12900232911 /* Data */, 02B6B3B528E1D10700232911 /* Domain */, 02EAE2CA28E1F0A700529644 /* Presentation */, - 97CA95212B875EA200A9EDEA /* Views */, - 97EA4D822B84EFA900663F58 /* Managers */, + 97CA95212B875EA200A9EDEA, + 97EA4D822B84EFA900663F58, 02B6B3B428E1C49400232911 /* Localizable.strings */, 02C355372C08DCD700501342 /* Localizable.stringsdict */, ); @@ -937,7 +936,6 @@ isa = PBXVariantGroup; children = ( 02B6B3B328E1C49400232911 /* en */, - 02ED50CF29A64BB6008341CD /* uk */, ); name = Localizable.strings; sourceTree = ""; diff --git a/Course/Course/uk.lproj/Localizable.strings b/Course/Course/uk.lproj/Localizable.strings deleted file mode 100644 index 76d23b095..000000000 --- a/Course/Course/uk.lproj/Localizable.strings +++ /dev/null @@ -1,115 +0,0 @@ -/* - Localizable.strings - Course - - Created by  Stepanok Ivan on 26.09.2022. - -*/ - -"OUTLINE.PASSED_THE_COURSE" = "Вітаємо, ви отримали сертифікат курсу “%@\.“"; -"OUTLINE.VIEW_CERTIFICATE" = "Переглянути сертифікат"; -"OUTLINE.CERTIFICATE" = "Сертифікат"; -"OUTLINE.COURSE_VIDEOS" = "Відео з курсу"; - -"COURSEWARE.COURSE_CONTENT" = "Зміст курсу"; -"COURSEWARE.COURSE_UNITS" = "Модулі"; -"COURSEWARE.NEXT" = "Далі"; -"COURSEWARE.PREVIOUS" = "Назад"; -"COURSEWARE.FINISH" = "Завершити"; -"COURSEWARE.GOOD_WORK" = "Гарна робота!"; -"COURSEWARE.BACK_TO_OUTLINE" = "Повернутись до модуля"; -"COURSEWARE.SECTION" = "Секція “"; -"COURSEWARE.IS_FINISHED" = "“ завершена."; -"COURSEWARE.CONTINUE" = "Продовжити"; -"COURSEWARE.RESUME_WITH" = "Продовжити далі:"; - -"ERROR.NO_INTERNET" = "Ви не підключені до Інтернету. Перевірте підключення до Інтернету і спробуйте ще."; -"ERROR.RELOAD" = "Перезавантажити"; -"ERROR.COMPONENT_NOT_FOUNT" = "Course component not found, please reload"; -"ERROR.NO_HANDOUTS" = "There are currently no handouts for this course"; - -"ALERT.ROTATE_DEVICE" = "Поверніть пристрій, щоб переглянути це відео на весь екран."; -"ALERT.ACCEPT" = "Accept"; -"ALERT.DELETE_ALL_VIDEOS" = "Are you sure you want to delete all video(s) for"; -"ALERT.DELETE_VIDEOS" = "Are you sure you want to delete video(s) for"; -"ALERT.STOP_DOWNLOADING" = "Turning off the switch will stop downloading and delete all downloaded videos for"; -"ALERT.WARNING" = "Warning"; - -"COURSE_CONTAINER.COURSE" = "Курс"; -"COURSE_CONTAINER.VIDEOS" = "Всі відео"; -"COURSE_CONTAINER.DATES" = "Dates"; -"COURSE_CONTAINER.DISCUSSIONS" = "Дискусії"; -"COURSE_CONTAINER.HANDOUTS" = "Матеріали"; -"COURSE_CONTAINER.HANDOUTS_IN_DEVELOPING" = "Матеріали в процесі розробки"; - -"HANDOUTS_CELL_HANDOUTS.TITLE" = "Нотатки"; -"HANDOUTS_CELL_ANNOUNCEMENTS.TITLE" = "Оголошення"; -"HANDOUTS_CELL_HANDOUTS.DESCRIPTION" = "Знайдіть важливу інформацію про курс"; -"HANDOUTS_CELL_ANNOUNCEMENTS.DESCRIPTION" = "Будьте в курсі останніх новин"; - -"NOT_AVALIABLE.TITLE" = "Цей інтерактивний компонент не доступний"; -"NOT_AVALIABLE.DESCRIPTION" = "Досліджуйте інші частини цього курсу або перегляньте цю в Браузері."; -"NOT_AVALIABLE.BUTTON" = "Відкрити в браузері"; - -"SUBTITLES.TITLE" = "Субтитри"; - -"ACCESSIBILITY.DOWNLOAD" = "Скачати"; -"ACCESSIBILITY.CANCEL_DOWNLOAD" = "Скасувати завантаження"; -"ACCESSIBILITY.DELETE_DOWNLOAD" = "Видалити файл"; - -"DOWNLOAD.DOWNLOADS" = "Downloads"; -"DOWNLOAD.DOWNLOAD" = "Download"; -"DOWNLOAD.ALL_VIDEOS_DOWNLOADED" = "All videos downloaded"; -"DOWNLOAD.DOWNLOADING_VIDEOS" = "Downloading videos..."; -"DOWNLOAD.DOWNLOAD_TO_DEVICE" = "Download to device"; -"DOWNLOAD.VIDEOS" = "Videos"; -"DOWNLOAD.REMAINING" = "Remaining"; -"DOWNLOAD.UNTITLED"= "Untitled"; -"DOWNLOAD.TOTAL"= "Total"; - -"DOWNLOAD.CHANGE_QUALITY_ALERT" = "You cannot change the download video quality when all videos are downloading"; -"DOWNLOAD.DOWNLOAD_LARGE_FILE_MESSAGE" = "The videos you've selected are larger than 1 GB. Do you want to download these videos?"; -"DOWNLOAD.NO_WIFI_MESSAGE" = "Your current download settings only allow downloads over Wi-Fi.\nPlease connect to a Wi-Fi network or change your download settings."; - -"COURSE_DATES.TODAY" = "Today"; -"COURSE_DATES.COMPLETED" = "Completed"; -"COURSE_DATES.PAST_DUE" = "Past due"; -"COURSE_DATES.DUE_NEXT" = "Due next"; -"COURSE_DATES.UNRELEASED" = "Unreleased"; -"COURSE_DATES.VERIFIED_ONLY" = "Verified Only"; -"COURSE_DATES.ITEMS_HIDDEN" = "Items Hidden"; -"COURSE_DATES.ITEM_HIDDEN" = "Item Hidden"; -"COURSE_DATES.TOAST_SUCCESS_TITLE" = "Due dates shifted"; -"COURSE_DATES.TOAST_SUCCESS_MESSAGE" = "Your due dates have been successfully shifted to help you stay on track."; -"COURSE_DATES.VIEW_ALL_DATES" = "View all dates"; -"COURSE_DATES.SYNC_TO_CALENDAR" = "Sync to calendar"; -"COURSE_DATES.SYNC_TO_CALENDAR_MESSAGE" = "Automatically sync all deadlines and due dates for this course to your calendar."; -"COURSE_DATES.ADD_CALENDAR_TITLE"="Add calendar"; -"COURSE_DATES.REMOVE_CALENDAR_TITLE"="Remove calendar"; -"COURSE_DATES.ADD_CALENDAR_PROMPT"="Would you like to add the %@ calendar \"%@\" ? \n You can edit or remove the course calendar any time in Calendar or Settings"; -"COURSE_DATES.REMOVE_CALENDAR_PROMPT"="Would you like to remove the %@ calendar \"%@\" ?"; -"COURSE_DATES.DATES_ADDED_ALERT_MESSAGE" = "\"%@\" has been added to your calendar."; -"COURSE_DATES.CALENDAR_SYNC_MESSAGE"="Syncing calendar..."; -"COURSE_DATES.CALENDAR_VIEW_EVENTS"="View Events"; -"COURSE_DATES.CALENDAR_EVENTS_ADDED"="Your course calendar has been added."; -"COURSE_DATES.CALENDAR_EVENTS_REMOVED"="Your course calendar has been removed."; -"COURSE_DATES.CALENDAR_EVENTS"="Calendar events"; -"COURSE_DATES.CALENDAR_OUT_OF_DATE"="Your course calendar is out of date"; -"COURSE_DATES.CALENDAR_SHIFT_MESSAGE"="Your course dates have been shifted and your course calendar is no longer up to date with your new schedule."; -"COURSE_DATES.CALENDAR_SHIFT_PROMPT_UPDATE_NOW"="Update now"; -"COURSE_DATES.CALENDAR_EVENTS_UPDATED"="Your course calendar has been updated."; -"COURSE_DATES.CALENDAR_PERMISSION_NOT_DETERMINED"="%@ does not have calendar permission. Please go to settings and give calender permission."; -"COURSE_DATES.OPEN_SETTINGS"="Open Settings"; -"COURSE_DATES.SETTINGS" = "Settings"; - -"COURSE_DATES.RESET_DATE.ERROR_MESSAGE" = "Your dates could not be shifted. Please try again."; -"COURSE_DATES.RESET_DATE.SUCCESS_MESSAGE" = "Your dates have been successfully shifted."; -"COURSE_DATES.RESET_DATE.TITLE" = "Course Dates"; - -"COURSE.DUE_TODAY" = "Закінчується сьогодні"; -"COURSE.DUE_TOMORROW" = "Закінчується завтра"; - -"COURSE.PROGRESS_COMPLETED" = "%@ з %@ завдань виконано"; -"CALENDAR_SYNC_STATUS.SYNCED" = "Синхронізовано з календарем"; -"CALENDAR_SYNC_STATUS.FAILED" = "Помилка синхронізації календаря"; -"CALENDAR_SYNC_STATUS.OFFLINE" = "Офлайн"; diff --git a/Dashboard/Dashboard.xcodeproj/project.pbxproj b/Dashboard/Dashboard.xcodeproj/project.pbxproj index c6e7e4e52..817ab758a 100644 --- a/Dashboard/Dashboard.xcodeproj/project.pbxproj +++ b/Dashboard/Dashboard.xcodeproj/project.pbxproj @@ -70,7 +70,6 @@ 02A9A9082978194100B55797 /* DashboardTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DashboardTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 02A9A90A2978194100B55797 /* DashboardViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardViewModelTests.swift; sourceTree = ""; }; 02A9A92829781A4D00B55797 /* DashboardMock.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashboardMock.generated.swift; sourceTree = ""; }; - 02ED50CD29A64B9B008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02EF39E728D89F560058F6BD /* Dashboard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Dashboard.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02F175322A4DABBF0019CD70 /* DashboardAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardAnalytics.swift; sourceTree = ""; }; 02F3BFE029252FCB0051930C /* DashboardRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardRouter.swift; sourceTree = ""; }; @@ -532,7 +531,6 @@ isa = PBXVariantGroup; children = ( 02F6EF4428D9ECC500835477 /* en */, - 02ED50CD29A64B9B008341CD /* uk */, ); name = Localizable.strings; sourceTree = ""; diff --git a/Dashboard/Dashboard/uk.lproj/Localizable.strings b/Dashboard/Dashboard/uk.lproj/Localizable.strings deleted file mode 100644 index e02337c90..000000000 --- a/Dashboard/Dashboard/uk.lproj/Localizable.strings +++ /dev/null @@ -1,45 +0,0 @@ -/* - Localizable.strings - Dashboard - - Created by  Stepanok Ivan on 20.09.2022. - -*/ - -"TITLE" = "Мої курси"; -"HEADER.COURSES" = "Курси"; -"HEADER.WELCOME_BACK" = "З поверненням. Давайте продовжимо вчитись."; - -"SEARCH" = "Пошук"; - -"EMPTY.TITLE" = "Нічого немає"; -"EMPTY.SUBTITLE" = "Ви не підписані на жодний курс."; - -"LEARN.TITLE" = "Навчання"; -"LEARN.VIEW_ALL" = "Переглянути все (%@)"; -"LEARN.ALL_COURSES" = "Усі курси"; - -"LEARN.PRIMARY_CARD.ONE_PAST_ASSIGNMENT" = "1 прострочене завдання"; -"LEARN.PRIMARY_CARD.VIEW_ASSIGNMENTS" = "Переглянути завдання"; -"LEARN.PRIMARY_CARD.PAST_ASSIGNMENTS" = "%@ Прострочені завдання"; -"LEARN.PRIMARY_CARD.FUTURE_ASSIGNMENTS" = "%@ Завданнь %@"; -"LEARN.PRIMARY_CARD.DUE_DAYS" = "%@ Оплата через %@ днів"; -"LEARN.PRIMARY_CARD.RESUME" = "Відновити курс"; -"LEARN.PRIMARY_CARD.START_COURSE" = "Розпочати курс"; - -"LEARN.DROPDOWN_MENU.COURSES" = "Курси"; -"LEARN.DROPDOWN_MENU.PROGRAMS" = "Програми"; - -"LEARN.CATEGORY.ALL" = "Усі"; -"LEARN.CATEGORY.IN_PROGRESS" = "Виконується"; -"LEARN.CATEGORY.COMPLETED" = "Завершено"; -"LEARN.CATEGORY.EXPIRED" = "Закінчився"; - -"LEARN.NO_COURSES_VIEW.NO_COURSES" = "Немає курсів"; -"LEARN.NO_COURSES_VIEW.NO_COURSES_IN_PROGRESS" = "Немає поточних курсів"; -"LEARN.NO_COURSES_VIEW.NO_COMPLETED_COURSES" = "Немає завершених курсів"; -"LEARN.NO_COURSES_VIEW.NO_EXPIRED_COURSES" = "Немає прострочених курсів"; - -"LEARN.NO_COURSES_VIEW.NO_COURSES_DESCRIPTION" = "Наразі ви не зареєстровані на жодному курсі, бажаєте переглянути каталог?"; - -"LEARN.NO_COURSES_VIEW.FIND_A_COURSE" = "Знайти курс"; diff --git a/Discovery/Discovery.xcodeproj/project.pbxproj b/Discovery/Discovery.xcodeproj/project.pbxproj index 769376097..c8958d0c0 100644 --- a/Discovery/Discovery.xcodeproj/project.pbxproj +++ b/Discovery/Discovery.xcodeproj/project.pbxproj @@ -67,7 +67,6 @@ 029242EA2AE6AB7B00A940EC /* UpdateNotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateNotificationView.swift; sourceTree = ""; }; 0297373F2949FB070051696B /* DiscoveryCoreModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DiscoveryCoreModel.xcdatamodel; sourceTree = ""; }; 029737412949FB3B0051696B /* DiscoveryPersistenceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryPersistenceProtocol.swift; sourceTree = ""; }; - 02ED50C729A649C9008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02ED50C829A649C9008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = ""; }; 02EF39D028D867690058F6BD /* swiftgen.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = swiftgen.yml; sourceTree = ""; }; 02EF39D828D86A380058F6BD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -591,7 +590,6 @@ isa = PBXVariantGroup; children = ( 02EF39D828D86A380058F6BD /* en */, - 02ED50C729A649C9008341CD /* uk */, ); name = Localizable.strings; sourceTree = ""; diff --git a/Discovery/Discovery/uk.lproj/Localizable.strings b/Discovery/Discovery/uk.lproj/Localizable.strings deleted file mode 100644 index 25f73bf53..000000000 --- a/Discovery/Discovery/uk.lproj/Localizable.strings +++ /dev/null @@ -1,37 +0,0 @@ -/* - Localizable.strings - Discovery - - Created by  Stepanok Ivan on 19.09.2022. - -*/ - -"TITLE" = "Всі курси"; -"SEARCH" = "Пошук"; -"HEADER.TITLE_1" = "Всі курси"; -"HEADER.TITLE_2" = "Давайте знайдемо нові курси для вас"; - -"SEARCH.TITLE" = "Результати пошуку"; -"SEARCH.EMPTY_DESCRIPTION" = "Почніть вводити текст, щоб знайти курс"; - -"UPDATE_REQUIRED_TITLE" = "Потрібне оновлення додатка"; -"UPDATE_REQUIRED_DESCRIPTION" = "Ця версія додатка OpenEdX застаріла. Щоб продовжити навчання та отримати останні функції та виправлення, оновіться до останньої версії."; -"UPDATE_WHY_NEED" = "Чому я маю оновити програму?"; -"UPDATE_DEPRECATED_APP" = "Застаріла версія додатка"; -"UPDATE_BUTTON" = "Оновити"; -"UPDATE_ACCOUNT_SETTINGS" = "Налаштування"; - -"UPDATE_NEEDED_TITLE" = "Оновлення додатку"; -"UPDATE_NEEDED_DESCRIPTION" = "Ми рекомендуємо вам оновити додаток до останньої версії. Оновіть зараз, щоб отримати нові функції та виправлення."; -"UPDATE_NEEDED_NOT_NOW" = "Не зараз"; - -"UPDATE_NEW_AVALIABLE" = "Доступне нове оновлення! Оновіть зараз, щоб отримати найновіші функції та виправлення"; - -"ALERT.LEAVING_APP_TITLE" = "Leaving the app"; -"ALERT.LEAVING_APP_MESSAGE" = "You are now leaving the app and opening a browser"; - -"DETAILS.TITLE" = "Деталі курсу"; -"DETAILS.VIEW_COURSE" = "Переглянути курс"; -"DETAILS.ENROLL_NOW" = "Зареєструватися"; -"DETAILS.ENROLLMENT_DATE_IS_OVER" = "Ви не можете зареєструватися на цей курс, оскільки дата реєстрації закінчилася."; -"DETAILS.ENROLLMENT_NO_INTERNET" = "Щоб зареєструватися на цьому курсі, переконайтеся, що ви підключені до Інтернету."; diff --git a/Discussion/Discussion.xcodeproj/project.pbxproj b/Discussion/Discussion.xcodeproj/project.pbxproj index 2f63c3d88..657642ca2 100644 --- a/Discussion/Discussion.xcodeproj/project.pbxproj +++ b/Discussion/Discussion.xcodeproj/project.pbxproj @@ -107,7 +107,6 @@ 02D1267528F76F5D00C8E689 /* DiscussionTopicsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscussionTopicsView.swift; sourceTree = ""; }; 02D1267728F76FF200C8E689 /* DiscussionTopicsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscussionTopicsViewModel.swift; sourceTree = ""; }; 02E4F18029A8C2FD00F31684 /* DiscussionSearchTopicsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscussionSearchTopicsViewModelTests.swift; sourceTree = ""; }; - 02ED50D029A64BBF008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02ED50D129A64BBF008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = ""; }; 02F175382A4DD5AA0019CD70 /* DiscussionAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscussionAnalytics.swift; sourceTree = ""; }; 02F28A5D28FF23E700AFDE1B /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = ""; }; @@ -733,7 +732,6 @@ isa = PBXVariantGroup; children = ( 0218196128F734CD00202564 /* en */, - 02ED50D029A64BBF008341CD /* uk */, ); name = Localizable.strings; sourceTree = ""; diff --git a/Discussion/Discussion/uk.lproj/Localizable.strings b/Discussion/Discussion/uk.lproj/Localizable.strings deleted file mode 100644 index fc5223c14..000000000 --- a/Discussion/Discussion/uk.lproj/Localizable.strings +++ /dev/null @@ -1,59 +0,0 @@ -/* - Localizable.strings - Discussion - - Created by  Stepanok Ivan on 12.10.2022. - -*/ - -"TITLE" = "Дискусії"; -"BANNER.DISCUSSIONS_IS_DISABLED" = "Posting in discussions is disabled by the course team"; - -"TOPICS.SEARCH" = "Пошук по всім постам"; -"TOPICS.ALL_POSTS" = "Всі пости"; -"TOPICS.POST_IM_FOLLOWING" = "Улюблені пости"; -"TOPICS.MAIN_CATEGORIES" = "Основні категорії"; -"TOPICS.UNNAMED" = "Unnamed subcategory"; - -"POSTS.SORT.RECENT_ACTIVITY" = "Остання активність"; -"POSTS.SORT.MOST_ACTIVITY" = "Найактивниші"; -"POSTS.SORT.MOST_VOTES" = "Найбільше голосів"; - -"POSTS.FILTER.ALL_POSTS" = "Всі пости"; -"POSTS.FILTER.UNREAD" = "Непрочитаних"; -"POSTS.FILTER.UNANSWERED" = "Без відповіді"; -"POSTS.NO_DISCUSSION.TITLE" = "Ще немає дискусій"; -"POSTS.NO_DISCUSSION.DESCRIPTION" = "Натисніть кнопку нижче, щоб створити свою першу дискусію."; -"POSTS.NO_DISCUSSION.CREATEBUTTON" = "Створити дискусію"; -"POSTS.NO_DISCUSSION.ADD_POST" = "Add a post"; - -"POSTS.CREATE_NEW_POST" = "Створити новий пост"; -"POSTS.ALERT.MAKE_SELECTION" = "Оберіть"; - -"POST.LAST_POST" = "Останній пост:"; - -"THREAD.ALERT.COMMENT_ADDED" = "Коментарій додано"; -"THREAD.ADD_RESPONSE" = "Додати відповідь"; - -"CREATE_THREAD.NEW_POST" = "Створити новий пост"; -"CREATE_THREAD.SELECT_POST_TYPE" = "Обрати тип посту"; -"CREATE_THREAD.TOPIC" = "Тема"; -"CREATE_THREAD.TITLE" = "Назва"; -"CREATE_THREAD.FOLLOW_DISCUSSION" = "Підписатись на дискусію"; -"CREATE_THREAD.FOLLOW_QUESTION" = "Підписатись на питання"; -"CREATE_THREAD.CREATE_DISCUSSION" = "Створити нову дискусію"; -"CREATE_THREAD.CREATE_QUESTION" = "Створити нове питання"; - -"COMMENT.REPORT" = "Поскаржитись"; -"COMMENT.UNREPORT" = "Зняти скаргу"; -"COMMENT.FOLLOW" = "Слідкувати"; -"COMMENT.UNFOLLOW" = "Не слідкувати"; - -"RESPONSE.COMMENTS_RESPONSES" = "Коментарій"; -"RESPONSE.ALERT.COMMENT_ADDED" = "Коментарій додано"; -"RESPONSE.ADD_COMMENT" = "Додати коментарій"; - -"POST_TYPE.QUESTION" = "питання"; -"POST_TYPE.DISCUSSION" = "дискусія"; - -"ANONYMOUS" = "Анонім"; diff --git a/Makefile b/Makefile index 5f97f7c59..0d88ddcfc 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,13 @@ -clean_translations_temp_directory: +clean_translations: rm -rf I18N/ + python3 i18n_scripts/translation.py --clean translation_requirements: pip3 install -r i18n_scripts/requirements.txt -pull_translations: clean_translations_temp_directory +pull_translations: clean_translations atlas pull $(ATLAS_OPTIONS) translations/openedx-app-ios/I18N:I18N - python3 i18n_scripts/translation.py --split --replace-underscore + python3 i18n_scripts/translation.py --split --replace-underscore --add-xcode-files -extract_translations: clean_translations_temp_directory +extract_translations: clean_translations python3 i18n_scripts/translation.py --combine diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 02d6d8680..653283d3c 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -114,7 +114,6 @@ 0293A2082A6FCDE50090A336 /* DashboardPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPersistence.swift; sourceTree = ""; }; 0298DF2F2A4EF7230023A257 /* AnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsManager.swift; sourceTree = ""; }; 02B6B3C428E1E61400232911 /* CourseDetails.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CourseDetails.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 02ED50CA29A64AAA008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02ED50D529A6554E008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = uk; path = "uk.lproj/сountries.json"; sourceTree = ""; }; 02ED50D729A65554008341CD /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = Base; path = "Base.lproj/сountries.json"; sourceTree = ""; }; 02ED50D929A66007008341CD /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = Base; path = Base.lproj/languages.json; sourceTree = ""; }; @@ -675,7 +674,6 @@ isa = PBXVariantGroup; children = ( 0770DE6528D0BCC7006D8A5D /* en */, - 02ED50CA29A64AAA008341CD /* uk */, ); name = Localizable.strings; sourceTree = ""; @@ -1281,8 +1279,8 @@ requirement = { kind = upToNextMajorVersion; minimumVersion = 10.26.0; - }; - }; + }; + }; 14D912D12C25483F0077CCCE /* XCRemoteSwiftPackageReference "fullstory-swift-package-ios" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/fullstorydev/fullstory-swift-package-ios"; @@ -1335,7 +1333,7 @@ isa = XCSwiftPackageProductDependency; package = 0780ABE12BFBA2E40093A4A6 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseMessaging; - }; + }; 14D912D22C25483F0077CCCE /* FullStory */ = { isa = XCSwiftPackageProductDependency; package = 14D912D12C25483F0077CCCE /* XCRemoteSwiftPackageReference "fullstory-swift-package-ios" */; diff --git a/OpenEdX/uk.lproj/Localizable.strings b/OpenEdX/uk.lproj/Localizable.strings deleted file mode 100644 index 8e7d62729..000000000 --- a/OpenEdX/uk.lproj/Localizable.strings +++ /dev/null @@ -1,7 +0,0 @@ -/* - Localizable.strings - OpenEdX - - Created by Vladimir Chekyrta on 13.09.2022. - -*/ diff --git a/Profile/Profile.xcodeproj/project.pbxproj b/Profile/Profile.xcodeproj/project.pbxproj index afc5ff356..0005825de 100644 --- a/Profile/Profile.xcodeproj/project.pbxproj +++ b/Profile/Profile.xcodeproj/project.pbxproj @@ -113,7 +113,6 @@ 02D0FD082AD698380020D752 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = ""; }; 02D0FD0A2AD6984D0020D752 /* UserProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileViewModel.swift; sourceTree = ""; }; 02EBC7522C19CD1700BE182C /* CalendarManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarManager.swift; sourceTree = ""; }; - 02ED50CE29A64BAD008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02F175342A4DAD030019CD70 /* ProfileAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileAnalytics.swift; sourceTree = ""; }; 02F3BFE6292539850051930C /* ProfileRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRouter.swift; sourceTree = ""; }; 02F81DDE2BF4D83E002D3604 /* CalendarDialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDialogView.swift; sourceTree = ""; }; @@ -726,7 +725,6 @@ isa = PBXVariantGroup; children = ( 021D926028DDADE600ACC565 /* en */, - 02ED50CE29A64BAD008341CD /* uk */, ); name = Localizable.strings; sourceTree = ""; diff --git a/Profile/Profile/uk.lproj/Localizable.strings b/Profile/Profile/uk.lproj/Localizable.strings deleted file mode 100644 index c56fff65c..000000000 --- a/Profile/Profile/uk.lproj/Localizable.strings +++ /dev/null @@ -1,148 +0,0 @@ -/* - Localizable.strings - Profile - - Created by  Stepanok Ivan on 23.09.2022. - -*/ - -"TITLE" = "Профіль"; -"INFO" = "Дані профілю"; -"ABOUT" = "Про Meне"; -"EDIT_PROFILE" = "Редагування"; -"YEAR_OF_BIRTH" = "Рік народження:"; -"BIO" = "Біо:"; -"SETTINGS" = "Налаштування"; -"SETTINGS_VIDEO" = "Налаштування відео"; -"SUPPORT_INFO" = "Інформація про підтримку"; -"CONTACT" = "Cлужби підтримки"; -"TERMS" = "Умови використання"; -"PRIVACY" = "Політика конфіденційності"; -"COOKIE_POLICY" = "Cookie policy"; -"DO_NOT_SELL_INFORMATION" = "Do not sell my personal information"; -"FAQ_TITLE" = "View FAQ"; -"LOGOUT" = "Вийти"; -"SWITCH_TO" = "Переключити на"; -"FULL_PROFILE" = "повний профіль"; -"LIMITED_PROFILE" = "обмежений профіль"; -"MANAGE_ACCOUNT" = "Налаштування Профілю"; - -"LOGOUT_ALERT.TITLE" = "Підтвердження виходу"; -"LOGOUT_ALERT.TEXT" = "Ви впевнені, що бажаєте вийти?"; - -"DELETE_ALERT.TITLE" = "Увага!"; -"DELETE_ALERT.TEXT" = "Ви дійсно хочете видалити свій обліковий запис?"; - -"UNSAVED_DATA_ALERT.TITLE" = "Є незбережені дані"; -"UNSAVED_DATA_ALERT.TEXT" = "Ви дійсно хочете вийти без збереження?"; - -"EDIT.TOO_YONG_USER" = "Вам має бути більше 13 років, щоб мати профіль із повним доступом до інформації."; -"EDIT.LIMITED_PROFILE_DESCRIPTION" = "В обмеженому профілі доступні лише ваше ім’я користувача."; -"EDIT.DELETE_ACCOUNT" = "Видалити акаунт"; - -"EDIT.FIELDS.YEAR_OF_BIRTH" = "Рік народження:"; -"EDIT.FIELDS.LOCATION" = "Країна"; -"EDIT.FIELDS.SPOKEN_LANGUGAE" = "Мова спілкування"; -"EDIT.FIELDS.ABOUT_ME" = "Про мене:"; - -"EDIT.BOTTOM_SHEET.TITLE" = "Змінити фото профілю"; -"EDIT.BOTTOM_SHEET.SELECT" = "Обрати із галереї"; -"EDIT.BOTTOM_SHEET.REMOVE" = "Видалити зображення"; -"EDIT.BOTTOM_SHEET.CANCEL" = "Скасувати"; - -"DELETE_ACCOUNT.TITLE" = "Видалення акаунту"; -"DELETE_ACCOUNT.ARE_YOU_SURE" = "Ви впевнені, що хочете "; -"DELETE_ACCOUNT.WANT_TO_DELETE" = "видалити свій обліковий запис?"; -"DELETE_ACCOUNT.DESCRIPTION" = "Для підтвердження цієї дії необхідно ввести пароль свого облікового запису."; -"DELETE_ACCOUNT.PASSWORD" = "Пароль"; -"DELETE_ACCOUNT.PASSWORD_DESCRIPTION" = "Введіть пароль"; -"DELETE_ACCOUNT.CONFIRM" = "Так, видалити акаунт"; -"DELETE_ACCOUNT.BACK_TO_PROFILE" = "Повернутись до профілю"; -"DELETE_ACCOUNT.INCORRECT_PASSWORD" = "Пароль неправильний. Будь ласка спробуйте ще раз."; - -"SETTINGS.VIDEO_SETTINGS_TITLE" = "Налаштування відео"; -"SETTINGS.WIFI_TITLE" = "Тільки Wi-fi"; -"SETTINGS.WIFI_DESCRIPTION" = "Завантажувати відео, лише коли Wi-Fi увімкнено"; -"SETTINGS.VIDEO_QUALITY_TITLE" = "Якість потокового відео"; -"SETTINGS.VIDEO_QUALITY_DESCRIPTION" = "Авто (Рекомендовано)"; - -"SETTINGS.QUALITY_AUTO_TITLE" = "Авто"; -"SETTINGS.QUALITY_AUTO_DESCRIPTION" = "Рекомендовано"; -"SETTINGS.QUALITY_360_TITLE" = "360p"; -"SETTINGS.QUALITY_360_DESCRIPTION" = "економія трафіку"; -"SETTINGS.QUALITY_540_TITLE" = "540p"; -"SETTINGS.QUALITY_720_TITLE" = "720p"; -"SETTINGS.QUALITY_AUTO_DESCRIPTION" = "Найкраща якість"; - -"SETTINGS.VERSION" = "Версія:"; -"SETTINGS.UP_TO_DATE" = "Оновлено"; -"SETTINGS.TAP_TO_UPDATE" = "Клацніть, щоб оновити до версії"; -"SETTINGS.TAP_TO_INSTALL" = "Клацніть, щоб встановити обов'язкове оновлення програми"; - -"ERROR.CANNOT_SEND_EMAIL" = "Cannot send email. It seems your email client is not set up."; - -"CALENDAR.NEW_CALENDAR" = "Новий календар"; -"CALENDAR.CHANGE_SYNC_OPTIONS" = "Змінити параметри синхронізації"; -"CALENDAR.ACCOUNT" = "Обліковий запис"; -"CALENDAR.CALENDAR_NAME" = "Назва календаря"; -"CALENDAR.COLOR" = "Колір"; -"CALENDAR.UPCOMING_ASSIGNMENTS" = "Майбутні завдання для активних курсів з’являться у цьому календарі"; -"CALENDAR.CANCEL" = "Скасувати"; -"CALENDAR.BEGIN_SYNCING" = "Почати синхронізацію"; - -"ASSIGNMENT_STATUS.SYNCED" = "Синхронізовано"; -"ASSIGNMENT_STATUS.FAILED" = "Синхронізація не вдалася"; -"ASSIGNMENT_STATUS.OFFLINE" = "Офлайн"; - -"CALENDAR_DIALOG.CALENDAR_ACCESS" = "Доступ до календаря"; -"CALENDAR_DIALOG.DISABLE_CALENDAR_SYNC" = "Cкасувати синхронізацію календаря"; -"CALENDAR_DIALOG.CALENDAR_ACCESS_DESCRIPTION" = "Щоб показати майбутні завдання та віхи курсу у вашому календарі, нам потрібен дозвіл на доступ до вашого календаря."; -"CALENDAR_DIALOG.DISABLE_CALENDAR_SYNC_DESCRIPTION" = "Вимкнення синхронізації календаря видалить календар “%@”. Ви можете знову увімкнути синхронізацію календаря в будь-який час."; -"CALENDAR_DIALOG.GRANT_CALENDAR_ACCESS" = "Надати доступ до календаря"; -"CALENDAR_DIALOG.DISABLE_SYNCING" = "Вимкнути синхронізацію"; -"CALENDAR_DIALOG.CANCEL" = "Скасувати"; - -"DATES_AND_CALENDAR.TITLE" = "Дати та календар"; -"CALENDAR_SYNC.TITLE" = "Синхронізація календаря"; -"CALENDAR_SYNC.DESCRIPTION" = "Налаштуйте синхронізацію календаря, щоб показувати майбутні завдання та віхи курсу у вашому календарі. Нові завдання та змінені дати курсів будуть синхронізуватися автоматично"; -"CALENDAR_SYNC.BUTTON" = "Налаштувати синхронізацію календаря"; -"OPTIONS.TITLE" = "Опції"; -"OPTIONS.USE_RELATIVE_DATES" = "Використовувати відносні дати"; -"OPTIONS.SHOW_RELATIVE_DATES" = "Показувати відносні дати, такі як “Завтра” і “Вчора”"; - -"DATES_AND_CALENDAR.TITLE" = "Дати та календарі"; -"CALENDAR_SYNC.TITLE" = "Синхронізація календаря"; -"COURSE_CALENDAR_SYNC.TITLE" = "Синхронізація календаря курсу"; -"COURSE_CALENDAR_SYNC.DESCRIPTION.RECONNECT_REQUIRED" = "Будь ласка, повторно підключіть свій календар для відновлення синхронізації"; -"COURSE_CALENDAR_SYNC.DESCRIPTION.SYNCING" = "В даний час події синхронізуються з вашим календарем"; -"COURSE_CALENDAR_SYNC.BUTTON.RECONNECT" = "Повторно підключити календар"; -"COURSE_CALENDAR_SYNC.BUTTON.CHANGE_SYNC_OPTIONS" = "Змінити параметри синхронізації"; -"COURSES_TO_SYNC.TITLE" = "Синхронізація %d курсів"; -"OPTIONS.TITLE" = "Опції"; -"OPTIONS.USE_RELATIVE_DATES" = "Використовувати відносні дати"; -"OPTIONS.SHOW_RELATIVE_DATES" = "Показувати відносні дати, такі як “Завтра” і “Вчора”"; - -"COURSES_TO_SYNC.TITLE" = "Курси для синхронізації"; -"COURSES_TO_SYNC.DESCRIPTION" = "Вимкнення синхронізації для курсу видалить усі події, пов’язані з курсом, із вашого синхронізованого календаря."; -"COURSES_TO_SYNC.HIDE_INACTIVE_COURSES" = "Приховати неактивні курси"; -"COURSES_TO_SYNC.HIDE_INACTIVE_COURSES_DESCRIPTION" = "Автоматично видаляйте події з курсів, які ви не переглядали протягом останнього місяця"; -"COURSES_TO_SYNC.INACTIVE" = "Неактивний"; - -"CALENDAR.DROPDOWN.ICLOUD" = "iCloud"; -"CALENDAR.DROPDOWN.LOCAL" = "Локальний"; - -"CALENDAR.DROPDOWN_COLOR.ACCENT" = "Акцентний"; -"CALENDAR.DROPDOWN_COLOR.RED" = "Червоний"; -"CALENDAR.DROPDOWN_COLOR.ORANGE" = "Помаранчевий"; -"CALENDAR.DROPDOWN_COLOR.YELLOW" = "Жовтий"; -"CALENDAR.DROPDOWN_COLOR.GREEN" = "Зелений"; -"CALENDAR.DROPDOWN_COLOR.BLUE" = "Синій"; -"CALENDAR.DROPDOWN_COLOR.PURPLE" = "Фіолетовий"; -"CALENDAR.DROPDOWN_COLOR.BROWN" = "Коричневий"; - -"CALENDAR.COURSE_DATES" = "%@ Дати курсу"; - -"DROP_DOWN_PICKER.SELECT" = "Оберіть"; - -"SYNC.NO_SYNCED" = "Немає синхронізованих курсів"; -"SYNC.NO_SYNCED_DESCRIPTION" = "Жоден курс зараз не синхронізується з вашим календарем."; diff --git a/README.md b/README.md index bb4b9b578..01addfd08 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,17 @@ Then, to get the latest translations for all languages use the following command ```bash make pull_translations ``` + This command runs [`atlas pull`](https://github.com/openedx/openedx-atlas) to download the latest translations files from the [openedx/openedx-translations](https://github.com/openedx/openedx-translations) repository. These files contain the latest translations for all languages. In the [openedx/openedx-translations](https://github.com/openedx/openedx-translations) repository each language's translations are saved as a single file e.g. `I18N/I18N/uk.lproj/Localization.strings` ([example](https://github.com/openedx/openedx-translations/blob/6448167e9695a921f003ff6bd8f40f006a2d6743/translations/openedx-app-ios/I18N/I18N/uk.lproj/Localizable.strings)). After these are pulled, each language's translation file is split into the App's modules e.g. `Discovery/Discovery/uk.lproj/Localization.strings`. After this command is run the application can load the translations by changing the device (or the emulator) language in the settings. +**Note:** This command modifies the XCode project files which fails the build so it's required to clean the translations files before committing using the following command: + +``` +make clean_translations +``` + ### Using custom translations By default, the command `make pull_translations` runs [`atlas pull`](https://github.com/openedx/openedx-atlas) with no arguments which pulls transaltions from the [openedx-translations repository](https://github.com/openedx/openedx-translations). @@ -55,7 +62,7 @@ Additional arguments can be passed to `atlas pull`. Refer to the [atlas document Translations are managed in the [open-edx/openedx-translations](https://app.transifex.com/open-edx/openedx-translations/dashboard/) Transifex project. -To translate the app join the [Transifex project](https://app.transifex.com/open-edx/openedx-translations/dashboard/) and add your translations `openedx-app-ios` resource: https://app.transifex.com/open-edx/openedx-translations/openedx-app-ios/ (the link will start working after the [pull request #442](https://github.com/openedx/openedx-app-ios/pull/422) is merged) +To translate the app join the [Transifex project](https://app.transifex.com/open-edx/openedx-translations/dashboard/) and add your translations to the [`openedx-app-ios`](https://app.transifex.com/open-edx/openedx-translations/openedx-app-ios/) resource. Once the resource is both 100% translated and reviewed the [Transifex integration](https://github.com/apps/transifex-integration) will automatically push it to the [openedx-translations](https://github.com/openedx/openedx-translations) repository and developers can use the translations in their app. diff --git a/WhatsNew/WhatsNew.xcodeproj/project.pbxproj b/WhatsNew/WhatsNew.xcodeproj/project.pbxproj index b78d64a7d..62b52f41d 100644 --- a/WhatsNew/WhatsNew.xcodeproj/project.pbxproj +++ b/WhatsNew/WhatsNew.xcodeproj/project.pbxproj @@ -52,7 +52,6 @@ 02B54E102AE061C100C56962 /* WhatsNewRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewRouter.swift; sourceTree = ""; }; 02E640782ADFF5920079AEDA /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewView.swift; sourceTree = ""; }; 02E6407D2ADFF6250079AEDA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - 02E6407F2ADFF6270079AEDA /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02E640802ADFFE440079AEDA /* WhatsNewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewViewModel.swift; sourceTree = ""; }; 02E640852ADFFF380079AEDA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 02E640892AE004300079AEDA /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; @@ -473,7 +472,6 @@ isa = PBXVariantGroup; children = ( 02E6407D2ADFF6250079AEDA /* en */, - 02E6407F2ADFF6270079AEDA /* uk */, ); name = Localizable.strings; sourceTree = ""; diff --git a/WhatsNew/WhatsNew/uk.lproj/Localizable.strings b/WhatsNew/WhatsNew/uk.lproj/Localizable.strings deleted file mode 100644 index a0194425c..000000000 --- a/WhatsNew/WhatsNew/uk.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* - Localizable.strings - WhatsNew - - Created by  Stepanok Ivan on 18.10.2023. - -*/ - -"TITLE" = "Що нового"; -"BUTTON_PREVIOUS" = "Назад"; -"BUTTON_NEXT" = "Далі"; -"BUTTON_DONE" = "Завершити"; diff --git a/i18n_scripts/requirements.txt b/i18n_scripts/requirements.txt index 384c433ad..197d6c5be 100644 --- a/i18n_scripts/requirements.txt +++ b/i18n_scripts/requirements.txt @@ -1,3 +1,6 @@ # Translation processing dependencies openedx-atlas==0.6.1 -localizable==0.1.3 \ No newline at end of file +localizable==0.1.3 + +# Using `pbxproj==4.2.0` with the ( https://github.com/kronenthaler/mod-pbxproj/pull/356 ) patch to support Localizable.strings files +https://github.com/kronenthaler/mod-pbxproj/archive/a9187b42dc224827f162c3e7b9b34f4c0d2654ee.zip diff --git a/i18n_scripts/translation.py b/i18n_scripts/translation.py index 5a56ca48e..8fd979591 100644 --- a/i18n_scripts/translation.py +++ b/i18n_scripts/translation.py @@ -13,7 +13,16 @@ import re import sys from collections import defaultdict +from contextlib import contextmanager +from pathlib import Path + import localizable +from pbxproj import XcodeProject +from pbxproj.pbxextensions import FileOptions + +LOCALIZABLE_FILES_TREE = '' +MAIN_MODULE_NAME = 'OpenEdX' +I18N_MODULE_NAME = 'I18N' def parse_arguments(): @@ -28,51 +37,94 @@ def parse_arguments(): help='Split translations into separate files for each module and language.') group.add_argument('--combine', action='store_true', help='Combine the English translations from all modules into a single file.') + group.add_argument('--clean', action='store_true', + help='Remove translation files and clean XCode projects.') parser.add_argument('--replace-underscore', action='store_true', - help='Replace underscores with "-r" in language directories (only with --split).') + help='Replace Transifex underscore "ar_IQ" language code with ' + 'iOS-compatible "ar-rIQ" codes (only with --split).') + parser.add_argument('--add-xcode-files', action='store_true', + help='Add the language files to the XCode project (only with --split).') return parser.parse_args() -def get_translation_file_path(modules_dir, module_name, lang_dir, create_dirs=False): +@contextmanager +def change_directory(new_dir: Path): + """ + Context manager to execute `os.chidir`. + + Usage: + + with change_directory('/some/path'): + do_stuff_here() + + :param new_dir: Path + """ + original_dir = os.getcwd() + try: + os.chdir(new_dir) + yield + finally: + os.chdir(original_dir) + + +def get_modules_dir(override: Path = None) -> Path: + """ + Gets the modeles directory (repository root directory). + """ + if override: + return override + + return Path(__file__).absolute().parent.parent + + +def get_translation_file_path(modules_dir: Path, module_name, lang_dir, create_dirs=False): """ Retrieves the path of the translation file for a specified module and language directory. Parameters: - modules_dir (str): The path to the base directory containing all the modules. + modules_dir (Path): The path to the base directory containing all the modules. module_name (str): The name of the module for which the translation path is being retrieved. lang_dir (str): The name of the language directory within the module's directory. create_dirs (bool): If True, creates the parent directories if they do not exist. Defaults to False. Returns: - str: The path to the module's translation file (Localizable.strings). + Path: The path to the module's translation file (Localizable.strings). """ try: - lang_dir_path = os.path.join(modules_dir, module_name, module_name, lang_dir, 'Localizable.strings') + if module_name == MAIN_MODULE_NAME: + # The main project structure is located into `OpenEdX` rather than `OpenEdX/OpenEdX` + module_path = modules_dir / module_name + else: + # Rest of modules such as Core, Course, Dashboard, etc follow the `Dashboard/Dashboard` structure + module_path = modules_dir / module_name / module_name + + lang_dir_path = module_path / lang_dir if create_dirs: - os.makedirs(os.path.dirname(lang_dir_path), exist_ok=True) - return lang_dir_path + lang_dir_path.mkdir(parents=True, exist_ok=True) + return lang_dir_path / 'Localizable.strings' except Exception as e: print(f"Error creating directory path: {e}", file=sys.stderr) raise -def get_modules_to_translate(modules_dir): +def get_modules_to_translate(modules_dir: Path): """ Retrieve the names of modules that have translation files for a specified language. Parameters: - modules_dir (str): The path to the directory containing all the modules. + modules_dir (Path): The path to the directory containing all the modules. Returns: list of str: A list of module names that have translation files for the specified language. """ try: modules_list = [ - directory for directory in os.listdir(modules_dir) + module_dir for module_dir in os.listdir(modules_dir) if ( - os.path.isdir(os.path.join(modules_dir, directory)) - and os.path.isfile(get_translation_file_path(modules_dir, directory, 'en.lproj')) - and directory != 'I18N' + (modules_dir / module_dir).is_dir() + and os.path.isfile(get_translation_file_path(modules_dir, module_dir, 'en.lproj')) + and module_dir != I18N_MODULE_NAME + and module_dir != MAIN_MODULE_NAME ) ] return modules_list @@ -84,12 +136,12 @@ def get_modules_to_translate(modules_dir): raise -def get_translations(modules_dir): +def get_translations(modules_dir: Path): """ Retrieve the translations from all modules in the modules_dir. Parameters: - modules_dir (str): The directory containing the modules. + modules_dir (Path): The directory containing the modules. Returns: dict: A dict containing a list of dictionaries containing the 'key', 'value', and 'comment' for each @@ -114,7 +166,7 @@ def get_translations(modules_dir): print(f"Error retrieving translations: {e}", file=sys.stderr) raise - return {'I18N': translations} + return {I18N_MODULE_NAME: translations} def combine_translation_files(modules_dir=None): @@ -122,8 +174,7 @@ def combine_translation_files(modules_dir=None): Combine translation files from different modules into a single file. """ try: - if not modules_dir: - modules_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + modules_dir = get_modules_dir(override=modules_dir) translation = get_translations(modules_dir) write_translations_to_modules(modules_dir, 'en.lproj', translation) except Exception as e: @@ -131,19 +182,19 @@ def combine_translation_files(modules_dir=None): raise -def get_languages_dirs(modules_dir): +def get_languages_dirs(modules_dir: Path): """ Retrieve directories containing language files for translation. Args: - modules_dir (str): The directory containing all the modules. + modules_dir (Path): The directory containing all the modules. Returns: list: A list of directories containing language files for translation. Each directory represents a specific language and ends with the '.lproj' extension. """ try: - lang_parent_dir = os.path.join(modules_dir, 'I18N', 'I18N') + lang_parent_dir = modules_dir / I18N_MODULE_NAME / I18N_MODULE_NAME languages_dirs = [ directory for directory in os.listdir(lang_parent_dir) if directory.endswith('.lproj') and directory != "en.lproj" @@ -173,7 +224,7 @@ def get_translations_from_file(modules_dir, lang_dir): """ translations = defaultdict(list) try: - translations_file_path = get_translation_file_path(modules_dir, 'I18N', lang_dir) + translations_file_path = get_translation_file_path(modules_dir, I18N_MODULE_NAME, lang_dir) lang_list = localizable.parse_strings(filename=translations_file_path) for translation_entry in lang_list: module_name, key_remainder = translation_entry['key'].split('.', maxsplit=1) @@ -189,7 +240,7 @@ def get_translations_from_file(modules_dir, lang_dir): return translations -def write_translations_to_modules(modules_dir, lang_dir, modules_translations): +def write_translations_to_modules(modules_dir: Path, lang_dir, modules_translations): """ Write translations to language files for each module. @@ -211,6 +262,12 @@ def write_translations_to_modules(modules_dir, lang_dir, modules_translations): print(f"Error writing translations to file.\n Module: {module}\n Error: {e}", file=sys.stderr) raise + # The main project structure is located into `OpenEdX` rather than `OpenEdX/OpenEdX` + # Empty files are added, so iOS knows which languages are supported in this app + main_translation_file_path = get_translation_file_path(modules_dir, MAIN_MODULE_NAME, lang_dir, create_dirs=True) + with open(main_translation_file_path, 'w') as f: + f.write(f'/* Empty {lang_dir}/Localizable.strings: Created by i18n_scripts/translation.py */') + def _escape(s): """ @@ -249,8 +306,7 @@ def split_translation_files(modules_dir=None): None """ try: - if not modules_dir: - modules_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + modules_dir = get_modules_dir(override=modules_dir) languages_dirs = get_languages_dirs(modules_dir) for lang_dir in languages_dirs: translations = get_translations_from_file(modules_dir, lang_dir) @@ -260,21 +316,197 @@ def split_translation_files(modules_dir=None): raise -def replace_underscores(modules_dir=None): +def get_project_path(modules_dir: Path, module_name: str) -> Path: + """ + Using a module_name return the pbxproj path. + + :param modules_dir: + :param module_name: + :return: Path + """ + if module_name == MAIN_MODULE_NAME: + project_file_path = modules_dir / f'{module_name}.xcodeproj/project.pbxproj' + else: + project_file_path = modules_dir / module_name / f'{module_name}.xcodeproj/project.pbxproj' + + return project_file_path + + +def get_xcode_project(modules_dir: Path, module_name: str) -> XcodeProject: + """ + Initialize an XCode project instance for a given module. + """ + xcode_project = XcodeProject.load(get_project_path(modules_dir, module_name)) + return xcode_project + + +def list_translation_files(module_path: Path) -> [Path]: + """ + List translaiton files in a given path. + + This method doesn't return the `en.lproj` translation source strings. + """ + for localizable_abs_path in module_path.rglob('**/Localizable.strings'): + if localizable_abs_path.parent.name != 'en.lproj': + yield localizable_abs_path + + +def get_xcode_projects(modules_dir: Path) -> [{Path, XcodeProject}]: + """ + Return a list of module_name, xcode_project pairs. + """ + for module_name in get_modules_to_translate(modules_dir): + xcode_project = get_xcode_project(modules_dir, module_name) + yield module_name, xcode_project + + +def add_localizable(xcode_project: XcodeProject, localizable_relative_path: Path): + """ + Add localizable file properly to the PBXVariantGroup. + + This function depends on the https://github.com/kronenthaler/mod-pbxproj/pull/356 implementation. + + TODO: Refactor to use the `master` version once either of the following issues is closed: + - Issue by st3fan: https://github.com/kronenthaler/mod-pbxproj/issues/113 + - Proposal by OmarIthawi for Axim: https://github.com/kronenthaler/mod-pbxproj/pull/356 + + :param xcode_project: XcodeProject + :param localizable_relative_path: Path + :return: + """ + language, _rest = str(localizable_relative_path).split('.lproj') # e.g. `ar` or `fr-ca` + print(f' - Adding "{localizable_relative_path}" for the "{language}" language.') + localizable_groups = xcode_project.get_groups_by_name(name='Localizable.strings', + section='PBXVariantGroup') + if len(localizable_groups) != 1: + # We need a single group. If many are found then, it's a problem. + raise Exception(f'Error: Cannot find the Localizable.strings group, please add the English ' + f'source translation strings with the name Localizable.strings. ' + f'Results: "{localizable_groups}"') + localizable_group = localizable_groups[0] + + xcode_project.add_file( + str(localizable_relative_path), + name=language, + parent=localizable_group, + force=False, + tree=LOCALIZABLE_FILES_TREE, + file_options=FileOptions( + create_build_files=False, + ), + ) + + +def add_translation_files_to_xcode(modules_dir: Path = None): + """ + Add Localizable.strings files pulled from Transifex to XCode. + """ + try: + modules_dir = get_modules_dir(override=modules_dir) + for module_name, xcode_project in get_xcode_projects(modules_dir): + print(f'## Entering project: {module_name}') + module_path = modules_dir / module_name + project_files_path = module_path / module_name # e.g. openedx-app-ios/Authorization/Authorization + + with change_directory(project_files_path): + for localizable_abs_path in list_translation_files(module_path): + add_localizable( + xcode_project=xcode_project, + localizable_relative_path=localizable_abs_path.relative_to(project_files_path), + ) + xcode_project.save() + + # This project is used to specify which languages are supported by the app for iOS + print(f'## Entering project: {MAIN_MODULE_NAME}') + main_xcode_project = get_xcode_project(modules_dir, module_name=MAIN_MODULE_NAME) + main_xcode_project_module_path = modules_dir / MAIN_MODULE_NAME + with change_directory(main_xcode_project_module_path): + # The main project structure is located into `OpenEdX` rather than `OpenEdX/OpenEdX` + for localizable_abs_path in list_translation_files(main_xcode_project_module_path): + add_localizable( + xcode_project=main_xcode_project, + localizable_relative_path=localizable_abs_path.relative_to(main_xcode_project_module_path), + ) + main_xcode_project.save() + + except Exception as e: + print(f"Error: An unexpected error occurred in add_translation_files_to_xcode: {e}", file=sys.stderr) + raise + + +def remove_xcode_localizable_variants(xcode_project: XcodeProject) -> None: + """ + Remove all non-English localizable files from the XCode project. + + :param xcode_project: XcodeProject + :return: + """ + for file_ref in xcode_project.objects.get_objects_in_section('PBXFileReference'): + if ( + not file_ref.path.startswith('en.lproj') + and re.match(r'\w+.lproj', file_ref.path) + and file_ref.sourceTree == LOCALIZABLE_FILES_TREE + and getattr(file_ref, 'lastKnownFileType', None) == 'text.plist.strings' + ): + path = file_ref.path + language, _rest = str(path).split('.lproj') # e.g. `ar` or `fr-ca` + print(f' - Removing "{path}" from project resources for the "{language}" language.') + xcode_project.remove_files_by_path(file_ref.path, tree=LOCALIZABLE_FILES_TREE, target_name=language) + + +def delete_translation_files(module_path: Path, xcode_project_path_base: Path): + """ + Delete the files from the file system. + + :param module_path: Path + :param xcode_project_path_base: Path + :return: + """ + for localizable_abs_path in list_translation_files(module_path): + localizable_relative_path = localizable_abs_path.relative_to(xcode_project_path_base) + print(f' - Removing "{localizable_relative_path}" file from file system') + localizable_abs_path.unlink() + + +def clean_translation_files(modules_dir: Path = None): + """ + Remove translation files from both file system and XCode project files. + """ try: - if not modules_dir: - modules_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + modules_dir = get_modules_dir(override=modules_dir) + for module_name, xcode_project in get_xcode_projects(modules_dir): + print(f'## Entering project: {module_name}') + module_path = modules_dir / module_name + delete_translation_files(module_path, xcode_project_path_base=module_path/module_name) + remove_xcode_localizable_variants(xcode_project) + xcode_project.save() + + # This project is used to specify which languages are supported by the app for iOS + print(f'## Entering project: {MAIN_MODULE_NAME}') + main_xcode_project = get_xcode_project(modules_dir, module_name=MAIN_MODULE_NAME) + main_xcode_project_module_path = modules_dir / MAIN_MODULE_NAME + # The main project structure is located into `OpenEdX` rather than `OpenEdX/OpenEdX` + delete_translation_files(main_xcode_project_module_path, main_xcode_project_module_path) + remove_xcode_localizable_variants(main_xcode_project) + main_xcode_project.save() + except Exception as e: + print(f"Error: An unexpected error occurred in clean_translation_files: {e}", file=sys.stderr) + raise + +def replace_underscores(modules_dir=None): + try: + modules_dir = get_modules_dir(override=modules_dir) languages_dirs = get_languages_dirs(modules_dir) for lang_dir in languages_dirs: + lang_old_path = os.path.dirname(get_translation_file_path(modules_dir, I18N_MODULE_NAME, lang_dir)) try: pattern = r'_(\w\w.lproj$)' if re.search(pattern, lang_dir): replacement = r'-\1' new_name = re.sub(pattern, replacement, lang_dir) - lang_old_path = os.path.dirname(get_translation_file_path(modules_dir, 'I18N', lang_dir)) - lang_new_path = os.path.dirname(get_translation_file_path(modules_dir, 'I18N', new_name)) + lang_new_path = os.path.dirname(get_translation_file_path(modules_dir, I18N_MODULE_NAME, new_name)) os.rename(lang_old_path, lang_new_path) print(f"Renamed {lang_old_path} to {lang_new_path}") @@ -286,7 +518,7 @@ def replace_underscores(modules_dir=None): print(f"Error: Permission denied while renaming {lang_old_path}: {e}", file=sys.stderr) raise except Exception as e: - print(f"Error: An unexpected error occurred while renaming {lang_old_path} to {lang_new_path}: {e}", + print(f"Error: An unexpected error occurred while renaming {lang_old_path}: {e}", file=sys.stderr) raise @@ -301,8 +533,12 @@ def main(): if args.replace_underscore: replace_underscores() split_translation_files() + if args.add_xcode_files: + add_translation_files_to_xcode() elif args.combine: combine_translation_files() + elif args.clean: + clean_translation_files() if __name__ == "__main__":