From c291b5055a639251d38de8b6b6dc72364aec58fb Mon Sep 17 00:00:00 2001 From: Milad Emami <74170652+milad-emami@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:20:48 +0330 Subject: [PATCH 1/3] fix: correct typos in localization keys, comments, and button labels (#500) Corrected spelling mistakes in various localization keys, comments, and button labels. - Changed 'Comfirm' to 'Confirm' in alert titles and confirmations - Updated localization keys: - 'LOGOUT_ALERT.TITLE' - 'DELETE_ACCOUNT.CONFIRM' - Fixed spelling in StyledButton calls and localization fallback text - Updated the Ukrainian translation for DELETE_ACCOUNT.CONFIRM - Corrected comments in the codebase These changes improve clarity and maintain consistency across the codebase. --- Core/Core/View/Base/AlertView.swift | 2 +- .../Presentation/DeleteAccount/DeleteAccountView.swift | 4 ++-- Profile/Profile/SwiftGen/Strings.swift | 6 +++--- Profile/Profile/en.lproj/Localizable.strings | 4 ++-- Profile/Profile/uk.lproj/Localizable.strings | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Core/Core/View/Base/AlertView.swift b/Core/Core/View/Base/AlertView.swift index 286fdd2e7..6b754e564 100644 --- a/Core/Core/View/Base/AlertView.swift +++ b/Core/Core/View/Base/AlertView.swift @@ -481,7 +481,7 @@ struct AlertView_Previews: PreviewProvider { .previewLayout(.sizeThatFits) .background(Color.gray) - AlertView(alertTitle: "Comfirm log out", + AlertView(alertTitle: "Confirm log out", alertMessage: "Are you sure you want to log out?", positiveAction: "Yes", onCloseTapped: {}, diff --git a/Profile/Profile/Presentation/DeleteAccount/DeleteAccountView.swift b/Profile/Profile/Presentation/DeleteAccount/DeleteAccountView.swift index 044e4eb18..4e7b1e272 100644 --- a/Profile/Profile/Presentation/DeleteAccount/DeleteAccountView.swift +++ b/Profile/Profile/Presentation/DeleteAccount/DeleteAccountView.swift @@ -99,7 +99,7 @@ public struct DeleteAccountView: View { maxWidth: .infinity, alignment: .topLeading) - // MARK: Comfirmation button + // MARK: Confirmation button if viewModel.isShowProgress { ProgressBar(size: 40, lineWidth: 8) .padding(.top, 20) @@ -107,7 +107,7 @@ public struct DeleteAccountView: View { .accessibilityIdentifier("progress_bar") } else { StyledButton( - ProfileLocalization.DeleteAccount.comfirm, + ProfileLocalization.DeleteAccount.confirm, action: { Task { try await viewModel.deleteAccount(password: viewModel.password) diff --git a/Profile/Profile/SwiftGen/Strings.swift b/Profile/Profile/SwiftGen/Strings.swift index d83527f97..0bcf37eec 100644 --- a/Profile/Profile/SwiftGen/Strings.swift +++ b/Profile/Profile/SwiftGen/Strings.swift @@ -177,7 +177,7 @@ public enum ProfileLocalization { /// Back to profile public static let backToProfile = ProfileLocalization.tr("Localizable", "DELETE_ACCOUNT.BACK_TO_PROFILE", fallback: "Back to profile") /// Yes, delete account - public static let comfirm = ProfileLocalization.tr("Localizable", "DELETE_ACCOUNT.COMFIRM", fallback: "Yes, delete account") + public static let confirm = ProfileLocalization.tr("Localizable", "DELETE_ACCOUNT.CONFIRM", fallback: "Yes, delete account") /// To confirm this action, please enter your account password. public static let description = ProfileLocalization.tr("Localizable", "DELETE_ACCOUNT.DESCRIPTION", fallback: "To confirm this action, please enter your account password.") /// The password is incorrect. Please try again. @@ -236,8 +236,8 @@ public enum ProfileLocalization { public enum LogoutAlert { /// Are you sure you want to log out? public static let text = ProfileLocalization.tr("Localizable", "LOGOUT_ALERT.TEXT", fallback: "Are you sure you want to log out?") - /// Comfirm log out - public static let title = ProfileLocalization.tr("Localizable", "LOGOUT_ALERT.TITLE", fallback: "Comfirm log out") + /// Confirm log out + public static let title = ProfileLocalization.tr("Localizable", "LOGOUT_ALERT.TITLE", fallback: "Confirm log out") } public enum Options { /// Show relative dates like “Tomorrow” and “Yesterday” diff --git a/Profile/Profile/en.lproj/Localizable.strings b/Profile/Profile/en.lproj/Localizable.strings index d94e1152c..2a680ddb2 100644 --- a/Profile/Profile/en.lproj/Localizable.strings +++ b/Profile/Profile/en.lproj/Localizable.strings @@ -28,7 +28,7 @@ "FULL_PROFILE" = "full profile"; "LIMITED_PROFILE" = "limited profile"; -"LOGOUT_ALERT.TITLE" = "Comfirm log out"; +"LOGOUT_ALERT.TITLE" = "Confirm log out"; "LOGOUT_ALERT.TEXT" = "Are you sure you want to log out?"; "DELETE_ALERT.TITLE" = "Warning!"; @@ -57,7 +57,7 @@ "DELETE_ACCOUNT.DESCRIPTION" = "To confirm this action, please enter your account password."; "DELETE_ACCOUNT.PASSWORD" = "Password"; "DELETE_ACCOUNT.PASSWORD_DESCRIPTION" = "Enter password"; -"DELETE_ACCOUNT.COMFIRM" = "Yes, delete account"; +"DELETE_ACCOUNT.CONFIRM" = "Yes, delete account"; "DELETE_ACCOUNT.BACK_TO_PROFILE" = "Back to profile"; "DELETE_ACCOUNT.INCORRECT_PASSWORD" = "The password is incorrect. Please try again."; diff --git a/Profile/Profile/uk.lproj/Localizable.strings b/Profile/Profile/uk.lproj/Localizable.strings index da932c17e..c56fff65c 100644 --- a/Profile/Profile/uk.lproj/Localizable.strings +++ b/Profile/Profile/uk.lproj/Localizable.strings @@ -56,7 +56,7 @@ "DELETE_ACCOUNT.DESCRIPTION" = "Для підтвердження цієї дії необхідно ввести пароль свого облікового запису."; "DELETE_ACCOUNT.PASSWORD" = "Пароль"; "DELETE_ACCOUNT.PASSWORD_DESCRIPTION" = "Введіть пароль"; -"DELETE_ACCOUNT.COMFIRM" = "Так, видалити акаунт"; +"DELETE_ACCOUNT.CONFIRM" = "Так, видалити акаунт"; "DELETE_ACCOUNT.BACK_TO_PROFILE" = "Повернутись до профілю"; "DELETE_ACCOUNT.INCORRECT_PASSWORD" = "Пароль неправильний. Будь ласка спробуйте ще раз."; From 73e6175ce1c73c6687eecac0fa9dd197776ec986 Mon Sep 17 00:00:00 2001 From: RawanMatar89 <41669180+RawanMatar89@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:24:47 +0300 Subject: [PATCH 2/3] fix: RTL localization for assets and strings (#441) * fix: rtl for "arrowRight16" image in Core/Assets * fix: rtl for "chevron.right" system image in many views * fix: rtl for "CompletionStatus" Enum in CourseDates * fix: flip image to support rtl issue * fix: "CompletionStatus" in course dates localized var issue --- .../arrowRight16.imageset/Contents.json | 3 ++- Core/Core/Domain/Model/CourseDates.swift | 17 +++++++++++++++++ Core/Core/SwiftGen/Strings.swift | 12 ++++++++++++ Core/Core/View/Base/CourseButton.swift | 1 + Core/Core/en.lproj/Localizable.strings | 7 +++++++ .../Presentation/Dates/CourseDatesView.swift | 5 ++++- .../Presentation/Handouts/HandoutsView.swift | 4 +++- .../CourseStructure/CourseStructureView.swift | 0 .../CourseVertical/CourseVerticalView.swift | 1 + .../DiscussionTopics/DiscussionTopicsView.swift | 1 + .../Subviews/ProfileSupportInfoView.swift | 2 ++ .../Presentation/Settings/SettingsView.swift | 2 ++ 12 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 Course/Course/Presentation/Outline/CourseStructure/CourseStructureView.swift diff --git a/Core/Core/Assets.xcassets/arrowRight16.imageset/Contents.json b/Core/Core/Assets.xcassets/arrowRight16.imageset/Contents.json index 2d22dfa63..bbe56d546 100644 --- a/Core/Core/Assets.xcassets/arrowRight16.imageset/Contents.json +++ b/Core/Core/Assets.xcassets/arrowRight16.imageset/Contents.json @@ -2,7 +2,8 @@ "images" : [ { "filename" : "arrowRight16.svg", - "idiom" : "universal" + "idiom" : "universal", + "language-direction" : "left-to-right" } ], "info" : { diff --git a/Core/Core/Domain/Model/CourseDates.swift b/Core/Core/Domain/Model/CourseDates.swift index 5b1c6436c..f3fbcdd9a 100644 --- a/Core/Core/Domain/Model/CourseDates.swift +++ b/Core/Core/Domain/Model/CourseDates.swift @@ -331,6 +331,23 @@ public enum CompletionStatus: String { case thisWeek = "This Week" case nextWeek = "Next Week" case upcoming = "Upcoming" + + public var localized: String { + switch self { + case .completed: + return CoreLocalization.CourseDates.completed + case .pastDue: + return CoreLocalization.CourseDates.pastDue + case .today: + return CoreLocalization.CourseDates.today + case .thisWeek: + return CoreLocalization.CourseDates.thisWeek + case .nextWeek: + return CoreLocalization.CourseDates.nextWeek + case .upcoming: + return CoreLocalization.CourseDates.upcoming + } + } } public extension Array { diff --git a/Core/Core/SwiftGen/Strings.swift b/Core/Core/SwiftGen/Strings.swift index 8cdf97b6e..ab42f4176 100644 --- a/Core/Core/SwiftGen/Strings.swift +++ b/Core/Core/SwiftGen/Strings.swift @@ -79,6 +79,18 @@ public enum CoreLocalization { } } public enum CourseDates { + /// Completed + public static let completed = CoreLocalization.tr("Localizable", "COURSE_DATES.COMPLETED", fallback: "Completed") + /// Next week + public static let nextWeek = CoreLocalization.tr("Localizable", "COURSE_DATES.NEXT_WEEK", fallback: "Next week") + /// Past due + public static let pastDue = CoreLocalization.tr("Localizable", "COURSE_DATES.PAST_DUE", fallback: "Past due") + /// This week + public static let thisWeek = CoreLocalization.tr("Localizable", "COURSE_DATES.THIS_WEEK", fallback: "This week") + /// Today + public static let today = CoreLocalization.tr("Localizable", "COURSE_DATES.TODAY", fallback: "Today") + /// Upcoming + public static let upcoming = CoreLocalization.tr("Localizable", "COURSE_DATES.UPCOMING", fallback: "Upcoming") public enum ResetDate { /// Your dates could not be shifted. Please try again. public static let errorMessage = CoreLocalization.tr("Localizable", "COURSE_DATES.RESET_DATE.ERROR_MESSAGE", fallback: "Your dates could not be shifted. Please try again.") diff --git a/Core/Core/View/Base/CourseButton.swift b/Core/Core/View/Base/CourseButton.swift index 9ff48554a..cfc999d5b 100644 --- a/Core/Core/View/Base/CourseButton.swift +++ b/Core/Core/View/Base/CourseButton.swift @@ -40,6 +40,7 @@ public struct CourseButton: View { .foregroundColor(Theme.Colors.textPrimary) Spacer() Image(systemName: "chevron.right") + .flipsForRightToLeftLayoutDirection(true) .padding(.vertical, 8) .foregroundColor(Theme.Colors.accentXColor) } diff --git a/Core/Core/en.lproj/Localizable.strings b/Core/Core/en.lproj/Localizable.strings index b4ca1bc64..1f8389f1f 100644 --- a/Core/Core/en.lproj/Localizable.strings +++ b/Core/Core/en.lproj/Localizable.strings @@ -139,3 +139,10 @@ "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_DATES.TODAY" = "Today"; +"COURSE_DATES.COMPLETED" = "Completed"; +"COURSE_DATES.PAST_DUE" = "Past due"; +"COURSE_DATES.THIS_WEEK" = "This week"; +"COURSE_DATES.NEXT_WEEK" = "Next week"; +"COURSE_DATES.UPCOMING" = "Upcoming"; diff --git a/Course/Course/Presentation/Dates/CourseDatesView.swift b/Course/Course/Presentation/Dates/CourseDatesView.swift index fba76f5fe..5ec069ea5 100644 --- a/Course/Course/Presentation/Dates/CourseDatesView.swift +++ b/Course/Course/Presentation/Dates/CourseDatesView.swift @@ -241,7 +241,7 @@ struct CompletedBlocks: View { }) { HStack { VStack(alignment: .leading) { - Text(CompletionStatus.completed.rawValue) + Text(CompletionStatus.completed.localized) .font(Theme.Fonts.titleSmall) .foregroundColor(Theme.Colors.textPrimary) @@ -289,6 +289,8 @@ struct CompletedBlocks: View { if block.canShowLink && !block.firstComponentBlockID.isEmpty { Image(systemName: "chevron.right") .resizable() + .flipsForRightToLeftLayoutDirection(true) + .scaledToFit() .frame(width: 6.55, height: 11.15) .labelStyle(.iconOnly) @@ -328,6 +330,7 @@ struct BlockStatusView: View { if block.canShowLink && !block.firstComponentBlockID.isEmpty { Image(systemName: "chevron.right") .resizable() + .flipsForRightToLeftLayoutDirection(true) .scaledToFit() .frame(width: 6.55, height: 11.15) .labelStyle(.iconOnly) diff --git a/Course/Course/Presentation/Handouts/HandoutsView.swift b/Course/Course/Presentation/Handouts/HandoutsView.swift index c9a2ed6cf..f08075fb5 100644 --- a/Course/Course/Presentation/Handouts/HandoutsView.swift +++ b/Course/Course/Presentation/Handouts/HandoutsView.swift @@ -205,7 +205,9 @@ struct HandoutsItemCell: View { .font(Theme.Fonts.labelSmall) } Spacer() - Image(systemName: "chevron.right").resizable() + Image(systemName: "chevron.right") + .resizable() + .flipsForRightToLeftLayoutDirection(true) .frame(width: 7, height: 12) .foregroundColor(Theme.Colors.accentColor) } diff --git a/Course/Course/Presentation/Outline/CourseStructure/CourseStructureView.swift b/Course/Course/Presentation/Outline/CourseStructure/CourseStructureView.swift new file mode 100644 index 000000000..e69de29bb diff --git a/Course/Course/Presentation/Outline/CourseVertical/CourseVerticalView.swift b/Course/Course/Presentation/Outline/CourseVertical/CourseVerticalView.swift index ccfc6f9b7..cd0c8d174 100644 --- a/Course/Course/Presentation/Outline/CourseVertical/CourseVerticalView.swift +++ b/Course/Course/Presentation/Outline/CourseVertical/CourseVerticalView.swift @@ -125,6 +125,7 @@ public struct CourseVerticalView: View { } } Image(systemName: "chevron.right") + .flipsForRightToLeftLayoutDirection(true) .padding(.vertical, 8) } .padding(.horizontal, 36) diff --git a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsView.swift b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsView.swift index ad84d6a00..5964e41ab 100644 --- a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsView.swift +++ b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsView.swift @@ -263,6 +263,7 @@ public struct TopicCell: View { .multilineTextAlignment(.leading) Spacer() Image(systemName: "chevron.right") + .flipsForRightToLeftLayoutDirection(true) .foregroundColor(Theme.Colors.accentColor) } }) diff --git a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift index 5b8f74713..29ff3c17a 100644 --- a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift +++ b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift @@ -130,6 +130,7 @@ struct ProfileSupportInfoView: View { .foregroundColor(Theme.Colors.textPrimary) Spacer() Image(systemName: "chevron.right") + .flipsForRightToLeftLayoutDirection(true) } } .simultaneousGesture(TapGesture().onEnded { @@ -187,6 +188,7 @@ struct ProfileSupportInfoView: View { .font(Theme.Fonts.titleMedium) Spacer() Image(systemName: "chevron.right") + .flipsForRightToLeftLayoutDirection(true) } } .foregroundColor(.primary) diff --git a/Profile/Profile/Presentation/Settings/SettingsView.swift b/Profile/Profile/Presentation/Settings/SettingsView.swift index 590afa784..e827d005e 100644 --- a/Profile/Profile/Presentation/Settings/SettingsView.swift +++ b/Profile/Profile/Presentation/Settings/SettingsView.swift @@ -150,6 +150,7 @@ public struct SettingsView: View { .font(Theme.Fonts.titleMedium) Spacer() Image(systemName: "chevron.right") + .flipsForRightToLeftLayoutDirection(true) } }) .accessibilityIdentifier("video_settings_button") @@ -183,6 +184,7 @@ public struct SettingsView: View { .font(Theme.Fonts.titleMedium) Spacer() Image(systemName: "chevron.right") + .flipsForRightToLeftLayoutDirection(true) } }) .accessibilityIdentifier("video_settings_button") From 157ded7df2ae608e8a76beffce247ff7d149e00e Mon Sep 17 00:00:00 2001 From: Omar Al-Ithawi Date: Mon, 19 Aug 2024 10:23:01 +0300 Subject: [PATCH 3/3] feat: remove uk translations and sync xcode project after `pull_translations` (#465) --- .github/workflows/validate-translations.yml | 49 +++ .../Authorization.xcodeproj/project.pbxproj | 2 - .../uk.lproj/Localizable.strings | 49 --- Core/Core.xcodeproj/project.pbxproj | 2 - Core/Core/uk.lproj/Localizable.strings | 127 -------- Course/Course.xcodeproj/project.pbxproj | 6 +- Course/Course/uk.lproj/Localizable.strings | 115 ------- Dashboard/Dashboard.xcodeproj/project.pbxproj | 2 - .../Dashboard/uk.lproj/Localizable.strings | 45 --- Discovery/Discovery.xcodeproj/project.pbxproj | 2 - .../Discovery/uk.lproj/Localizable.strings | 37 --- .../Discussion.xcodeproj/project.pbxproj | 2 - .../Discussion/uk.lproj/Localizable.strings | 59 ---- Makefile | 9 +- OpenEdX.xcodeproj/project.pbxproj | 8 +- OpenEdX/uk.lproj/Localizable.strings | 7 - Profile/Profile.xcodeproj/project.pbxproj | 2 - Profile/Profile/uk.lproj/Localizable.strings | 148 --------- README.md | 9 +- WhatsNew/WhatsNew.xcodeproj/project.pbxproj | 2 - .../WhatsNew/uk.lproj/Localizable.strings | 12 - i18n_scripts/requirements.txt | 5 +- i18n_scripts/translation.py | 298 ++++++++++++++++-- 23 files changed, 338 insertions(+), 659 deletions(-) create mode 100644 .github/workflows/validate-translations.yml delete mode 100644 Authorization/Authorization/uk.lproj/Localizable.strings delete mode 100644 Core/Core/uk.lproj/Localizable.strings delete mode 100644 Course/Course/uk.lproj/Localizable.strings delete mode 100644 Dashboard/Dashboard/uk.lproj/Localizable.strings delete mode 100644 Discovery/Discovery/uk.lproj/Localizable.strings delete mode 100644 Discussion/Discussion/uk.lproj/Localizable.strings delete mode 100644 OpenEdX/uk.lproj/Localizable.strings delete mode 100644 Profile/Profile/uk.lproj/Localizable.strings delete mode 100644 WhatsNew/WhatsNew/uk.lproj/Localizable.strings 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__":