diff --git a/.github/workflows/ios-develop.yml b/.github/workflows/ios-develop.yml index e51eba6a..b1e1dc0e 100644 --- a/.github/workflows/ios-develop.yml +++ b/.github/workflows/ios-develop.yml @@ -21,7 +21,7 @@ jobs: - name: Build KMP working-directory: ios run: | - ./scripts/generate-error-messages.sh + ./scripts/generate-strings.sh ./scripts/build-kmp.sh release false true false - name: Setup tools working-directory: ios diff --git a/.github/workflows/ios-release.yml b/.github/workflows/ios-release.yml index 7d11f45a..3ba5fd73 100644 --- a/.github/workflows/ios-release.yml +++ b/.github/workflows/ios-release.yml @@ -16,7 +16,7 @@ jobs: - name: Build KMP working-directory: ios run: | - ./scripts/generate-error-messages.sh + ./scripts/generate-strings.sh ./scripts/build-kmp.sh release false true false - name: Setup tools working-directory: ios diff --git a/.github/workflows/ios_pr_build.yml b/.github/workflows/ios_pr_build.yml index 0e92b9a8..ba381f9b 100644 --- a/.github/workflows/ios_pr_build.yml +++ b/.github/workflows/ios_pr_build.yml @@ -20,10 +20,10 @@ jobs: working-directory: ios run: | ./scripts/setup.sh - - name: Generate Error Messages + - name: Generate Strings working-directory: ios run: | - ./scripts/generate-error-messages.sh + ./scripts/generate-strings.sh - name: Build KMP working-directory: ios run: | diff --git a/android/app/src/main/kotlin/kmp/android/navigation/NavBarFeature.kt b/android/app/src/main/kotlin/kmp/android/navigation/NavBarFeature.kt index ec8a60b7..23b1fd97 100644 --- a/android/app/src/main/kotlin/kmp/android/navigation/NavBarFeature.kt +++ b/android/app/src/main/kotlin/kmp/android/navigation/NavBarFeature.kt @@ -1,13 +1,14 @@ package kmp.android.navigation -import androidx.annotation.StringRes +import dev.icerock.moko.resources.desc.StringDesc +import dev.icerock.moko.resources.desc.desc import kmp.android.sample.navigation.SampleGraph import kmp.android.samplecomposemultiplatform.navigation.SampleComposeMultiplatformGraph import kmp.android.samplesharedviewmodel.navigation.SampleSharedViewModelGraph -import kmp.android.shared.R +import kmp.shared.base.MR -enum class NavBarFeature(val route: String, @StringRes val titleRes: Int) { - Sample(SampleGraph.rootPath, R.string.bottom_bar_item_1), - SampleSharedViewModel(SampleSharedViewModelGraph.rootPath, R.string.bottom_bar_item_2), - SampleComposeMultiplatform(SampleComposeMultiplatformGraph.rootPath, R.string.bottom_bar_item_3), +enum class NavBarFeature(val route: String, val titleRes: StringDesc) { + Sample(SampleGraph.rootPath, MR.strings.bottom_bar_item_1.desc()), + SampleSharedViewModel(SampleSharedViewModelGraph.rootPath, MR.strings.bottom_bar_item_2.desc()), + SampleComposeMultiplatform(SampleComposeMultiplatformGraph.rootPath, MR.strings.bottom_bar_item_3.desc()), } diff --git a/android/app/src/main/kotlin/kmp/android/ui/Root.kt b/android/app/src/main/kotlin/kmp/android/ui/Root.kt index 1c1871da..5b658d1b 100644 --- a/android/app/src/main/kotlin/kmp/android/ui/Root.kt +++ b/android/app/src/main/kotlin/kmp/android/ui/Root.kt @@ -18,12 +18,12 @@ import androidx.compose.material.primarySurface import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import dev.icerock.moko.resources.compose.localized import kmp.android.navigation.NavBarFeature import kmp.android.sample.navigation.SampleGraph import kmp.android.sample.navigation.sampleNavGraph @@ -84,7 +84,7 @@ private fun BottomBar(navController: NavHostController, modifier: Modifier = Mod ) } }, - label = { Text(stringResource(screen.titleRes)) }, + label = { Text(screen.titleRes.localized()) }, selected = currentRoute?.startsWith(screen.route + "/") ?: false, onClick = { navController.navigate(screen.route) { diff --git a/android/shared/src/main/res/values-en/generated_strings.xml b/android/shared/src/main/res/values-en/generated_strings.xml deleted file mode 100644 index c05dd63b..00000000 --- a/android/shared/src/main/res/values-en/generated_strings.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - CS - Czech - SK - Slovak - EN - English - - - Classic - Shared VMs - Compose Multiplatform - - - Done - Close - Back - Cancel - Skip - Next - Finish - Ok - Yes - No - Clear - - - Photo selection - Choose location of the photo - Camera - Library - Cancel - - - Close - diff --git a/android/shared/src/main/res/values-sk/generated_strings.xml b/android/shared/src/main/res/values-sk/generated_strings.xml deleted file mode 100644 index a93001bc..00000000 --- a/android/shared/src/main/res/values-sk/generated_strings.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - CS - Čeština - SK - Slovenčina - EN - Angličtina - - - Classic - Shared VMs - Compose Multiplatform - - - Hotovo - Zavrieť - Späť - Zrušiť - Preskočiť - Ďalší - Dokončiť - Ok - Áno - Nie - Vymazať - - - Výber fotografie - Vyberte, odkiaľ chcete fotografiu nahrať. - Fotoaparát - Knihovňa - Zrušiť - - - Zavrieť - diff --git a/android/shared/src/main/res/values/generated_strings.xml b/android/shared/src/main/res/values/generated_strings.xml deleted file mode 100644 index d7089307..00000000 --- a/android/shared/src/main/res/values/generated_strings.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - CS - Čeština - SK - Slovenština - EN - Angličtina - - - Classic - Shared VMs - Compose Multiplatform - - - Hotovo - Zavřít - Zpět - Zrušit - Přeskočit - Další - Dokončit - Ok - Ano - Ne - Vymazat - - - Výběr fotografie - Vyberte, odkud chcete fotografii nahrát. - Fotoaparát - Knihovna - Zrušit - - - Zavřít - diff --git a/build-logic/convention/src/main/kotlin/config/TwineConfig.kt b/build-logic/convention/src/main/kotlin/config/TwineConfig.kt index 3b8ce652..948cd0f6 100644 --- a/build-logic/convention/src/main/kotlin/config/TwineConfig.kt +++ b/build-logic/convention/src/main/kotlin/config/TwineConfig.kt @@ -6,17 +6,9 @@ import java.io.File fun Project.configureTwine() { tasks.register("generateTwine") { - Twine.generateAllRegularFiles( + Twine.generateAllStringFiles( project = project, twineFile = "${rootProject.file("twine").absolutePath}/strings.txt", - moduleName = "android/shared", - ) - } - - tasks.register("generateErrorsTwine") { - Twine.generateAllErrorFiles( - project = project, - twineFile = "${rootProject.file("twine").absolutePath}/errors.txt", targetPath = "${project.rootDir.absolutePath}/shared/base/src/commonMain/moko-resources", targetFileName = "strings.xml", languages = listOf("sk", "en", "cs"), @@ -27,38 +19,7 @@ fun Project.configureTwine() { private object Twine { - fun generateAllRegularFiles( - project: Project, - moduleName: String, - twineFile: String, - ) { - val script = - when { - OperatingSystem.current().isLinux || OperatingSystem.current().isMacOsX -> - "twine generate-all-localization-files $twineFile ${project.rootDir.absolutePath}/$moduleName/src/main/res/ -f android -n generated_strings.xml -d en -r" - - OperatingSystem.current().isWindows -> "twine generate-all-localization-files $twineFile ${project.rootDir.absolutePath}/$moduleName/src/main/res/ -f android -n generated_strings.xml -d en -r" - else -> "unsupported" - } - - project.exec { - // Add twine into path - // This should be also refactored - val twinePath = project.findProperty("twinePath") - if (twinePath != null) { - environment["PATH"] = - "${environment["PATH"]}${System.getProperty("path.separator")}$twinePath" - } - - if (OperatingSystem.current().isMacOsX || OperatingSystem.current().isLinux) { - this.commandLine("sh", "-c", script) - } else if (OperatingSystem.current().isWindows) { - this.commandLine("cmd", "/c", script) - } - } - } - - fun generateAllErrorFiles( + fun generateAllStringFiles( project: Project, twineFile: String, targetPath: String, diff --git a/ios/Application/MainFlowController.swift b/ios/Application/MainFlowController.swift index 2403ca28..5415e4ad 100644 --- a/ios/Application/MainFlowController.swift +++ b/ios/Application/MainFlowController.swift @@ -3,6 +3,7 @@ // Copyright © 2019 Matee. All rights reserved. // +import KMPShared import Sample import SampleComposeMultiplatform import SampleSharedViewModel @@ -28,7 +29,7 @@ final class MainFlowController: FlowController { private func setupSampleTab() -> UINavigationController { let sampleNC = BaseNavigationController(statusBarStyle: .lightContent) sampleNC.tabBarItem = UITabBarItem( - title: L10n.bottom_bar_item_1, + title: MR.strings().bottom_bar_item_1.toLocalized(), image: AppTheme.Images.person, tag: MainTab.sample.rawValue ) @@ -41,7 +42,7 @@ final class MainFlowController: FlowController { private func setupSampleSharedViewModelTab() -> UINavigationController { let sampleSharedViewModelNC = BaseNavigationController(statusBarStyle: .lightContent) sampleSharedViewModelNC.tabBarItem = UITabBarItem( - title: L10n.bottom_bar_item_2, + title: MR.strings().bottom_bar_item_2.toLocalized(), image: AppTheme.Images.personCirle, tag: MainTab.sampleSharedViewModel.rawValue ) @@ -54,7 +55,7 @@ final class MainFlowController: FlowController { private func setupSampleComposeMultiplatformTab() -> UINavigationController { let sampleComposeMultiplatformNC = BaseNavigationController(statusBarStyle: .lightContent) sampleComposeMultiplatformNC.tabBarItem = UITabBarItem( - title: L10n.bottom_bar_item_3, + title: MR.strings().bottom_bar_item_3.toLocalized(), image: AppTheme.Images.personSquare, tag: MainTab.sampleComposeMultiplatform.rawValue ) diff --git a/ios/MateeStarter.xcodeproj/xcshareddata/xcschemes/MateeStarter.xcscheme b/ios/MateeStarter.xcodeproj/xcshareddata/xcschemes/MateeStarter.xcscheme index 7cdb621f..1826c91f 100644 --- a/ios/MateeStarter.xcodeproj/xcshareddata/xcschemes/MateeStarter.xcscheme +++ b/ios/MateeStarter.xcodeproj/xcshareddata/xcschemes/MateeStarter.xcscheme @@ -9,8 +9,8 @@ + title = "Generate strings" + scriptText = "if [ -z "$CI" ]; then cd "${PROJECT_DIR}" scripts/generate-strings.sh fi "> + title = "Generate strings" + scriptText = "if [ -z "$CI" ]; then cd "${PROJECT_DIR}" scripts/generate-strings.sh fi "> + title = "Generate strings" + scriptText = "if [ -z "$CI" ]; then cd "${PROJECT_DIR}" scripts/generate-strings.sh fi "> ( get: { viewModel.state.toast }, set: { toast in viewModel.onIntent(.onToastChanged(data: toast)) } @@ -56,6 +56,7 @@ import DependencyInjectionMocks import Factory #Preview { + fixMokoResourcesForPreviews() Container.shared.registerUseCaseMocks() let vm = SampleViewModel(flowController: nil) diff --git a/ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleComposeMultiplatformView.swift b/ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleComposeMultiplatformView.swift index 76d7d6c9..f264c036 100644 --- a/ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleComposeMultiplatformView.swift +++ b/ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleComposeMultiplatformView.swift @@ -31,6 +31,6 @@ struct SampleComposeMultiplatformView: View { ) } .toastView($toastData) - .navigationTitle(L10n.bottom_bar_item_3) + .navigationTitle(MR.strings().bottom_bar_item_3.toLocalized()) } } diff --git a/ios/PresentationLayer/SampleSharedViewModel/Sources/SampleSharedViewModel/Main/SampleSharedViewModelView.swift b/ios/PresentationLayer/SampleSharedViewModel/Sources/SampleSharedViewModel/Main/SampleSharedViewModelView.swift index 3e844113..9f2420ca 100644 --- a/ios/PresentationLayer/SampleSharedViewModel/Sources/SampleSharedViewModel/Main/SampleSharedViewModelView.swift +++ b/ios/PresentationLayer/SampleSharedViewModel/Sources/SampleSharedViewModel/Main/SampleSharedViewModelView.swift @@ -36,7 +36,7 @@ struct SampleSharedViewModelView: View { } } } - .navigationTitle(L10n.bottom_bar_item_2) + .navigationTitle(MR.strings().bottom_bar_item_2.toLocalized()) .onAppear { viewModel.onIntent(intent: SampleSharedIntentOnAppeared()) } @@ -59,6 +59,7 @@ import DependencyInjectionMocks import Factory #Preview { + fixMokoResourcesForPreviews() Container.shared.registerUseCaseMocks() Container.shared.registerViewModelMocks() diff --git a/ios/PresentationLayer/UIToolkit/Package.swift b/ios/PresentationLayer/UIToolkit/Package.swift index 106f81e7..f5198a03 100644 --- a/ios/PresentationLayer/UIToolkit/Package.swift +++ b/ios/PresentationLayer/UIToolkit/Package.swift @@ -18,6 +18,7 @@ let package = Package( // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), .package(name: "Utilities", path: "../../DomainLayer/Utilities"), + .package(name: "SharedDomain", path: "../../DomainLayer/SharedDomain"), .package(url: "https://github.com/SwiftGen/SwiftGenPlugin", .upToNextMajor(from: "6.6.0")), .package(url: "https://github.com/SFSafeSymbols/SFSafeSymbols.git", .upToNextMajor(from: "5.3.0")) ], @@ -28,21 +29,16 @@ let package = Package( name: "UIToolkit", dependencies: [ .product(name: "Utilities", package: "Utilities"), + .product(name: "SharedDomain", package: "SharedDomain"), .product(name: "SFSafeSymbols", package: "SFSafeSymbols") ], exclude: [ - "swiftgen-strings.stencil", "swiftgen-xcassets.stencil", "swiftgen.yml" ], plugins: [ - "TwinePlugin", .plugin(name: "SwiftGenPlugin", package: "SwiftGenPlugin") ] - ), - .plugin( - name: "TwinePlugin", - capability: .buildTool() ) ] ) diff --git a/ios/PresentationLayer/UIToolkit/Plugins/TwinePlugin/TwinePlugin.swift b/ios/PresentationLayer/UIToolkit/Plugins/TwinePlugin/TwinePlugin.swift deleted file mode 100644 index 6b4e9371..00000000 --- a/ios/PresentationLayer/UIToolkit/Plugins/TwinePlugin/TwinePlugin.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// Created by Petr Chmelar on 02.08.2023 -// Copyright © 2023 Matee. All rights reserved. -// - -import Foundation -import PackagePlugin - -@main -struct TwinePlugin: BuildToolPlugin { - - func createBuildCommands( - context: PluginContext, - target: Target - ) throws -> [Command] { - let rootPath = context.package.directory - .removingLastComponent() - .removingLastComponent() - - return [ - .prebuildCommand( - displayName: "Run Twine", - executable: rootPath.appending(["scripts", "twine.sh"]), - arguments: [], - environment: [ - "DERIVED_SOURCES_DIR": context.pluginWorkDirectory - ], - outputFilesDirectory: context.pluginWorkDirectory - ) - ] - } -} diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/SwiftUI/UIHostingController/BaseHostingController.swift b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/SwiftUI/UIHostingController/BaseHostingController.swift index a707c7c0..9ffe9d07 100644 --- a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/SwiftUI/UIHostingController/BaseHostingController.swift +++ b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/SwiftUI/UIHostingController/BaseHostingController.swift @@ -3,6 +3,7 @@ // Copyright © 2022 Matee. All rights reserved. // +import KMPShared import OSLog import SwiftUI import Utilities @@ -39,6 +40,6 @@ public class BaseHostingController: UIHostingController where private func setupUI() { // Setup background color and back button title view.backgroundColor = UIColor(AppTheme.Colors.background) - navigationItem.backBarButtonItem = UIBarButtonItem(title: L10n.back, style: .plain, target: nil, action: nil) + navigationItem.backBarButtonItem = UIBarButtonItem(title: MR.strings().back.toLocalized(), style: .plain, target: nil, action: nil) } } diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/UIKit/UIViewController/BaseViewController.swift b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/UIKit/UIViewController/BaseViewController.swift index fe5a7e84..0c35b409 100644 --- a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/UIKit/UIViewController/BaseViewController.swift +++ b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/UIKit/UIViewController/BaseViewController.swift @@ -3,6 +3,7 @@ // Copyright © 2017 Matee. All rights reserved. // +import KMPShared import OSLog import UIKit import Utilities @@ -36,6 +37,6 @@ public class BaseViewController: UIViewController { open func setupUI() { // Setup background color and back button title view.backgroundColor = UIColor(AppTheme.Colors.background) - navigationItem.backBarButtonItem = UIBarButtonItem(title: L10n.back, style: .plain, target: nil, action: nil) + navigationItem.backBarButtonItem = UIBarButtonItem(title: MR.strings().back.toLocalized(), style: .plain, target: nil, action: nil) } } diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/UIKit/UIViewController/ImagePickerViewController.swift b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/UIKit/UIViewController/ImagePickerViewController.swift index f96d3433..7e4dd681 100644 --- a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/UIKit/UIViewController/ImagePickerViewController.swift +++ b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/UIKit/UIViewController/ImagePickerViewController.swift @@ -3,6 +3,7 @@ // Copyright © 2018 Matee. All rights reserved. // +import KMPShared import UIKit @objc public protocol ImagePickerViewControllerDelegate: AnyObject { @@ -12,8 +13,8 @@ import UIKit public final class ImagePickerViewController: BaseViewController { // MARK: Stored properties - public var imagePickerTitle: String = L10n.image_picker_title - public var imagePickerSubtitle: String = L10n.image_picker_subtitle + public var imagePickerTitle: String = MR.strings().image_picker_title.toLocalized() + public var imagePickerSubtitle: String = MR.strings().image_picker_subtitle.toLocalized() public weak var delegate: ImagePickerViewControllerDelegate? @@ -25,17 +26,17 @@ public final class ImagePickerViewController: BaseViewController { // Setup action sheet with camera/library options let actionSheetController = UIAlertController(title: imagePickerTitle, message: imagePickerSubtitle, preferredStyle: .actionSheet) - let photoLibrary = UIAlertAction(title: L10n.image_picker_library, style: .default, handler: { _ in + let photoLibrary = UIAlertAction(title: MR.strings().image_picker_library.toLocalized(), style: .default, handler: { _ in self.selectPhoto(sourceType: .photoLibrary) }) actionSheetController.addAction(photoLibrary) - let takePhotoByCamera = UIAlertAction(title: L10n.image_picker_camera, style: .default, handler: { _ in + let takePhotoByCamera = UIAlertAction(title: MR.strings().image_picker_camera.toLocalized(), style: .default, handler: { _ in self.selectPhoto(sourceType: .camera) }) actionSheetController.addAction(takePhotoByCamera) - let cancel = UIAlertAction(title: L10n.image_picker_cancel, style: .cancel, handler: nil) + let cancel = UIAlertAction(title: MR.strings().image_picker_cancel.toLocalized(), style: .cancel, handler: nil) actionSheetController.addAction(cancel) // Required for iPad diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Bundle+Extensions.swift b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Bundle+Extensions.swift deleted file mode 100644 index 99e29813..00000000 --- a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Bundle+Extensions.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by Petr Chmelar on 30.05.2022 -// Copyright © 2022 Matee. All rights reserved. -// - -import Foundation - -extension Bundle { - /// Workaround for crashing SwiftUI Previews when any of underlying modules uses .module - /// Taken from: https://developer.apple.com/forums/thread/664295 - static var myModule: Bundle = { - /* The name of your local package, prepended by "LocalPackages_" for iOS and "PackageName_" for macOS. You may have same PackageName and TargetName*/ - let bundleNameIOS = "LocalPackages_UIToolkit" - let bundleNameMacOs = "UIToolkit_UIToolkit" - let candidates = [ - /* Bundle should be present here when the package is linked into an App. */ - Bundle.main.resourceURL, - /* Bundle should be present here when the package is linked into a framework. */ - Bundle(for: BundleToken.self).resourceURL, - /* For command-line tools. */ - Bundle.main.bundleURL, - /* Bundle should be present here when running previews from a different package (this is the path to "…/Debug-iphonesimulator/"). */ - Bundle(for: BundleToken.self).resourceURL?.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent(), - Bundle(for: BundleToken.self).resourceURL?.deletingLastPathComponent().deletingLastPathComponent() - ] - - for candidate in candidates { - let bundlePathiOS = candidate?.appendingPathComponent(bundleNameIOS + ".bundle") - let bundlePathMacOS = candidate?.appendingPathComponent(bundleNameMacOs + ".bundle") - if let bundle = bundlePathiOS.flatMap(Bundle.init(url:)) { - return bundle - } else if let bundle = bundlePathMacOS.flatMap(Bundle.init(url:)) { - return bundle - } - } - fatalError("unable to find bundle") - }() -} - -private final class BundleToken {} diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Moko+Extensions.swift b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Moko+Extensions.swift new file mode 100644 index 00000000..8a9510cf --- /dev/null +++ b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Moko+Extensions.swift @@ -0,0 +1,14 @@ +// +// Created by Lukáš Matuška on 10.10.2024 +// Copyright © 2024 Matee. All rights reserved. +// + +import Foundation +import KMPShared + +public extension StringResource { + + func toLocalized() -> String { + return self.desc().localized() + } +} diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/Alerts/AlertData.swift b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/Alerts/AlertData.swift index 86de856a..74e6e658 100644 --- a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/Alerts/AlertData.swift +++ b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/Alerts/AlertData.swift @@ -4,6 +4,7 @@ // import Foundation +import KMPShared public struct AlertData: Equatable, Identifiable { @@ -17,7 +18,7 @@ public struct AlertData: Equatable, Identifiable { public init( title: String, message: String? = nil, - primaryAction: AlertData.Action = AlertData.Action(title: L10n.dialog_error_close_text, style: .default), + primaryAction: AlertData.Action = AlertData.Action(title: MR.strings().dialog_error_close_text.toLocalized(), style: .default), secondaryAction: AlertData.Action? = nil ) { self.title = title diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/MokoFix.swift b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/MokoFix.swift new file mode 100644 index 00000000..9c6cb74c --- /dev/null +++ b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/MokoFix.swift @@ -0,0 +1,21 @@ +// +// Created by Julia Jakubcova on 11/10/2024 +// Copyright © 2024 Matee. All rights reserved. +// + +import Foundation +import KMPShared + + #warning("TODO: Remove this workaround when issue [https://github.com/icerockdev/moko-resources/issues/714] is resolved") + public func fixMokoResourcesForTests() { + if ProcessInfo.processInfo.processName == "xctest" { + MokoResourcesWorkaroundKt.nsBundle = Bundle(for: KotlinBase.self) + } + } + + #warning("TODO: Remove this workaround when issue [https://github.com/icerockdev/moko-resources/issues/714] is resolved") + public func fixMokoResourcesForPreviews() { + if ProcessInfo.processInfo.processName == "XCPreviewAgent" { + MokoResourcesWorkaroundKt.nsBundle = Bundle(for: KotlinBase.self) + } + } diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/Plurals.swift b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/Plurals.swift index 52eeaedb..28e83260 100644 --- a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/Plurals.swift +++ b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/Plurals.swift @@ -22,15 +22,15 @@ public enum Plurals: String { func stringForCount(_ count: Int) -> String { if count == 0 { // swiftlint:disable:this empty_count - return String(format: NSLocalizedString("zero_\(rawValue)", bundle: .myModule, comment: ""), count) + return String(format: NSLocalizedString("zero_\(rawValue)", bundle: .module, comment: ""), count) } else if abs(count) == 1 { - return String(format: NSLocalizedString("one_\(rawValue)", bundle: .myModule, comment: ""), count) + return String(format: NSLocalizedString("one_\(rawValue)", bundle: .module, comment: ""), count) } else if abs(count) > 1 && abs(count) < 5 { - return String(format: NSLocalizedString("few_\(rawValue)", bundle: .myModule, comment: ""), count) + return String(format: NSLocalizedString("few_\(rawValue)", bundle: .module, comment: ""), count) } else if abs(count) >= 5 { - return String(format: NSLocalizedString("many_\(rawValue)", bundle: .myModule, comment: ""), count) + return String(format: NSLocalizedString("many_\(rawValue)", bundle: .module, comment: ""), count) } else { - return String(format: NSLocalizedString("other_\(rawValue)", bundle: .myModule, comment: ""), count) + return String(format: NSLocalizedString("other_\(rawValue)", bundle: .module, comment: ""), count) } } } diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen-strings.stencil b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen-strings.stencil deleted file mode 100644 index 3fb3ceae..00000000 --- a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen-strings.stencil +++ /dev/null @@ -1,115 +0,0 @@ -// Template is based on the default strings/flat-swift5.stencil -// https://github.com/SwiftGen/SwiftGen/blob/stable/Sources/SwiftGenCLI/templates/strings/flat-swift5.stencil -// Revision: 7e13d641745b56775d9a7f983a5468d2d9952ada - -// Modifications: -// - Use `.myModule` [workaround](https://developer.apple.com/forums/thread/664295) to address SwiftUI Previews crashes -// - Use snake_case instead of camelCase, this is done by changing `swiftIdentifier:"pretty"` to `swiftIdentifier` -// - Ability to change localization via Environment.localization - -// swiftlint:disable all -// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen - -{% if tables.count > 0 %} -{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} -import Foundation -import Utilities - -// swiftlint:disable superfluous_disable_command file_length implicit_return prefer_self_in_static_references - -// MARK: - Strings - -{% macro parametersBlock types %} - {%- for type in types -%} - {%- if type == "String" -%} - _ p{{forloop.counter}}: Any - {%- else -%} - _ p{{forloop.counter}}: {{type}} - {%- endif -%} - {{ ", " if not forloop.last }} - {%- endfor -%} -{% endmacro %} -{% macro argumentsBlock types %} - {%- for type in types -%} - {%- if type == "String" -%} - String(describing: p{{forloop.counter}}) - {%- elif type == "UnsafeRawPointer" -%} - Int(bitPattern: p{{forloop.counter}}) - {%- else -%} - p{{forloop.counter}} - {%- endif -%} - {{ ", " if not forloop.last }} - {%- endfor -%} -{% endmacro %} -{% macro recursiveBlock table item %} - {% for string in item.strings %} - {% if not param.noComments %} - {% for line in string.comment|default:string.translation|split:"\n" %} - /// {{line}} - {% endfor %} - {% endif %} - {% set translation string.translation|replace:'"','\"'|replace:' ','\t' %} - {% if string.types %} - {{accessModifier}} static func {{string.key|swiftIdentifier|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { - return {{enumName}}.tr("{{table}}", "{{string.key}}", {%+ call argumentsBlock string.types %}, fallback: "{{translation}}") - } - {% elif param.lookupFunction %} - {{accessModifier}} static var {{string.key|swiftIdentifier|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}", fallback: "{{translation}}") } - {% else %} - {{accessModifier}} static let {{string.key|swiftIdentifier|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}", fallback: "{{translation}}") - {% endif %} - {% endfor %} - {% for child in item.children %} - {% call recursiveBlock table child %} - {% endfor %} -{% endmacro %} -// swiftlint:disable function_parameter_count identifier_name line_length type_body_length -{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} -{{accessModifier}} enum {{enumName}} { - {% if tables.count > 1 or param.forceFileNameEnum %} - {% for table in tables %} - {{accessModifier}} enum {{table.name|swiftIdentifier|escapeReservedKeywords}} { - {% filter indent:2," ",true %}{% call recursiveBlock table.name table.levels %}{% endfilter %} - } - {% endfor %} - {% else %} - {% call recursiveBlock tables.first.name tables.first.levels %} - {% endif %} -} -// swiftlint:enable function_parameter_count identifier_name line_length type_body_length - -// MARK: - Implementation Details - -extension {{enumName}} { - private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String { - if let preview = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"], preview == "1", - let path = BundleToken.bundle.path(forResource: Environment.locale.identifier, ofType: "lproj"), let bundle = Bundle(path: path) { - let format = bundle.localizedString(forKey: key, value: nil, table: table) - return String(format: format, locale: Environment.locale, arguments: args) - } else { - {% if param.lookupFunction %} - let format = {{ param.lookupFunction }}(key, table) - {% else %} - let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table) - {% endif %} - return String(format: format, locale: Locale.current, arguments: args) - } - } -} -{% if not param.bundle and not param.lookupFunction %} - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.myModule - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type -{% endif %} -{% else %} -// No string found -{% endif %} diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen-xcassets.stencil b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen-xcassets.stencil index cbdc65c9..0797b806 100644 --- a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen-xcassets.stencil +++ b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen-xcassets.stencil @@ -3,7 +3,6 @@ // Revision: 937d05fa32ff389c63f93f0f9e2919d2c461b3c9 // Modifications: -// - Use `.myModule` [workaround](https://developer.apple.com/forums/thread/664295) to address SwiftUI Previews crashes // - Ensure SwiftUI-first behavior for colors by renaming `.color` to `.uiColor` and `.swiftUIColor` to `.color` // - Ensure SwiftUI-first behavior for images by renaming `.image` to `.uiImage` and `.swiftUIImage` to `.image` @@ -433,7 +432,7 @@ private final class BundleToken { static let bundle: Bundle = { #if SWIFT_PACKAGE - return Bundle.myModule + return Bundle.module #else return Bundle(for: BundleToken.self) #endif diff --git a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen.yml b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen.yml index aed05306..b8686e92 100644 --- a/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen.yml +++ b/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen.yml @@ -1,14 +1,5 @@ output_dir: ${DERIVED_SOURCES_DIR} -strings: - inputs: - - ${DERIVED_SOURCES_DIR}/../TwinePlugin/Base.lproj/Localizable.strings - outputs: - - templatePath: swiftgen-strings.stencil - output: Localizable.swift - params: - publicAccess: true - xcassets: inputs: - Resources/Colors.xcassets diff --git a/ios/scripts/generate-error-messages.sh b/ios/scripts/generate-strings.sh similarity index 54% rename from ios/scripts/generate-error-messages.sh rename to ios/scripts/generate-strings.sh index 87783401..417197bd 100755 --- a/ios/scripts/generate-error-messages.sh +++ b/ios/scripts/generate-strings.sh @@ -5,8 +5,8 @@ cd "$(dirname "$0")" cd ../.. -echo "Generating Localizable files for error messages in specified languages" -./gradlew generateErrorsTwine +echo "Generating Localizable files" +./gradlew generateTwine echo "Generating MR resources from .xml files" -./gradlew :shared:base:generateMRcommonMain \ No newline at end of file +./gradlew :shared:base:generateMRcommonMain diff --git a/ios/scripts/setup.sh b/ios/scripts/setup.sh index 887625df..ca9c6860 100755 --- a/ios/scripts/setup.sh +++ b/ios/scripts/setup.sh @@ -15,16 +15,6 @@ else echo "✅ File header is properly set" fi -if [ ! -f ../../shared/core/src/commonMain/resources/MR/base/strings.xml ]; then - echo "⚙️ Building Moko error strings for the first time" - ./generate-error-messages.sh -fi - -if [ ! -d ../DomainLayer/KMPShared.xcframework ]; then - echo "⚙️ Building KMP for the first time" - ./build-kmp.sh -fi - echo "⚙️ Checking whether Twine is installed" if command -v twine &> /dev/null; then echo "✅ Twine is installed" @@ -33,6 +23,16 @@ else echo "Check https://github.com/MateeDevs/wiki/blob/master/tooling/ruby.md for more info" fi +if [ ! -f ../../shared/core/src/commonMain/resources/MR/base/strings.xml ]; then + echo "⚙️ Building Moko strings for the first time" + ./generate-strings.sh +fi + +if [ ! -d ../DomainLayer/KMPShared.xcframework ]; then + echo "⚙️ Building KMP for the first time" + ./build-kmp.sh +fi + echo "⚙️ Checking whether Homebrew is installed" if command -v brew &> /dev/null; then echo "✅ Homebrew is installed" @@ -46,7 +46,6 @@ echo "⚙️ Checking whether SwiftLint is installed" if command -v swiftlint &> /dev/null; then echo "✅ SwiftLint is installed" else - echo "❌ SwiftLint is not installed" - echo "Trying to install swiftlint" + echo "❌ SwiftLint is not installed - installing now" brew install swiftlint -fi \ No newline at end of file +fi diff --git a/ios/scripts/twine.sh b/ios/scripts/twine.sh deleted file mode 100755 index dcdbc431..00000000 --- a/ios/scripts/twine.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/zsh -l - -# This ensures that relative paths are correct no matter where the script is executed -cd "$(dirname "$0")" - -echo "⚙️ Generating Localizable files for specified languages" -mkdir ${DERIVED_SOURCES_DIR}/Base.lproj -twine generate-localization-file ../../twine/strings.txt ${DERIVED_SOURCES_DIR}/Base.lproj/Localizable.strings --lang en -mkdir ${DERIVED_SOURCES_DIR}/cs.lproj -twine generate-localization-file ../../twine/strings.txt ${DERIVED_SOURCES_DIR}/cs.lproj/Localizable.strings --lang cs -mkdir ${DERIVED_SOURCES_DIR}/sk.lproj -twine generate-localization-file ../../twine/strings.txt ${DERIVED_SOURCES_DIR}/sk.lproj/Localizable.strings --lang sk -mkdir ${DERIVED_SOURCES_DIR}/en.lproj -twine generate-localization-file ../../twine/strings.txt ${DERIVED_SOURCES_DIR}/en.lproj/Localizable.strings --lang en diff --git a/shared/base/src/commonMain/moko-resources/base/strings.xml b/shared/base/src/commonMain/moko-resources/base/strings.xml index ac64f1ae..c0e7eb36 100644 --- a/shared/base/src/commonMain/moko-resources/base/strings.xml +++ b/shared/base/src/commonMain/moko-resources/base/strings.xml @@ -1,9 +1,45 @@ - + - + + CS + Czech + SK + Slovak + EN + English + + + Classic + Shared VMs + Compose Multiplatform + + + Done + Close + Back + Cancel + Skip + Next + Finish + Ok + Yes + No + Clear + + + Photo selection + Choose location of the photo + Camera + Library + Cancel + + + Close + + Unknown error User is not authorized You are not connected to the internet. diff --git a/shared/base/src/commonMain/moko-resources/cs/strings.xml b/shared/base/src/commonMain/moko-resources/cs/strings.xml index 9f412fb3..8193659f 100644 --- a/shared/base/src/commonMain/moko-resources/cs/strings.xml +++ b/shared/base/src/commonMain/moko-resources/cs/strings.xml @@ -1,9 +1,45 @@ - + - + + CS + Čeština + SK + Slovenština + EN + Angličtina + + + Classic + Shared VMs + Compose Multiplatform + + + Hotovo + Zavřít + Zpět + Zrušit + Přeskočit + Další + Dokončit + Ok + Ano + Ne + Vymazat + + + Výběr fotografie + Vyberte, odkud chcete fotografii nahrát. + Fotoaparát + Knihovna + Zrušit + + + Zavřít + + Neznámá chyba Uživatel není autorizován Nejsi připojen k internetu. diff --git a/shared/base/src/commonMain/moko-resources/sk/strings.xml b/shared/base/src/commonMain/moko-resources/sk/strings.xml index 3e4e8fe5..0ef029d5 100644 --- a/shared/base/src/commonMain/moko-resources/sk/strings.xml +++ b/shared/base/src/commonMain/moko-resources/sk/strings.xml @@ -1,9 +1,45 @@ - + - + + CS + Čeština + SK + Slovenčina + EN + Angličtina + + + Classic + Shared VMs + Compose Multiplatform + + + Hotovo + Zavrieť + Späť + Zrušiť + Preskočiť + Ďalší + Dokončiť + Ok + Áno + Nie + Vymazať + + + Výber fotografie + Vyberte, odkiaľ chcete fotografiu nahrať. + Fotoaparát + Knihovňa + Zrušiť + + + Zavrieť + + Neznáma chyba Užívateľ nie je autorizovaný Nie ste pripojení k internetu. diff --git a/shared/base/src/iosMain/kotlin/dev/icerock/moko/resources/utils/MokoResourcesWorkaround.kt b/shared/base/src/iosMain/kotlin/dev/icerock/moko/resources/utils/MokoResourcesWorkaround.kt new file mode 100644 index 00000000..9f036f3f --- /dev/null +++ b/shared/base/src/iosMain/kotlin/dev/icerock/moko/resources/utils/MokoResourcesWorkaround.kt @@ -0,0 +1,36 @@ +package dev.icerock.moko.resources.utils // must be same as in moko-resources to override method + +import platform.Foundation.NSBundle +import platform.Foundation.NSDirectoryEnumerator +import platform.Foundation.NSFileManager +import platform.Foundation.NSURL +import platform.Foundation.pathExtension + +/** + * Workaround by MAX-POLKOVNIK from https://github.com/icerockdev/moko-resources/issues/747#issuecomment-2330854244 + * Remove when https://github.com/icerockdev/moko-resources/issues/747 is resolved + */ + +var nsBundle: NSBundle = NSBundle.mainBundle // <-- this is where we should looking for resources, by default mainBundle + +fun NSBundle.Companion.loadableBundle(identifier: String): NSBundle { + val bundlePath: String = nsBundle.bundlePath // <-- path where we should search for bundle with resources + val enumerator: NSDirectoryEnumerator = requireNotNull(NSFileManager.defaultManager.enumeratorAtPath(bundlePath)) + while (true) { + val relativePath: String = enumerator.nextObject() as? String ?: break + val url = NSURL(fileURLWithPath = relativePath) + if (url.pathExtension == "bundle") { + val fullPath = "$bundlePath/$relativePath" + val foundedBundle: NSBundle? = NSBundle.bundleWithPath(fullPath) + val loadedIdentifier: String? = foundedBundle?.bundleIdentifier + + if (isBundleSearchLogEnabled) { + println("moko-resources auto-load bundle with identifier $loadedIdentifier at path $fullPath") + } + + if (foundedBundle?.bundleIdentifier == identifier) return foundedBundle + } + } + + throw IllegalArgumentException("bundle with identifier $identifier not found") +} diff --git a/twine/errors.txt b/twine/errors.txt deleted file mode 100644 index c340ea8c..00000000 --- a/twine/errors.txt +++ /dev/null @@ -1,13 +0,0 @@ -[[General]] - [unknown_error] - cs = Neznámá chyba - en = Unknown error - sk = Neznáma chyba - [not_authorized] - cs = Uživatel není autorizován - en = User is not authorized - sk = Užívateľ nie je autorizovaný - [error_no_internet_connection] - cs = Nejsi připojen k internetu. - en = You are not connected to the internet. - sk = Nie ste pripojení k internetu. diff --git a/twine/strings.txt b/twine/strings.txt index 16fec51b..c56054a7 100644 --- a/twine/strings.txt +++ b/twine/strings.txt @@ -111,3 +111,17 @@ cs = Zavřít en = Close sk = Zavrieť + +[[Errors]] + [unknown_error] + cs = Neznámá chyba + en = Unknown error + sk = Neznáma chyba + [not_authorized] + cs = Uživatel není autorizován + en = User is not authorized + sk = Užívateľ nie je autorizovaný + [error_no_internet_connection] + cs = Nejsi připojen k internetu. + en = You are not connected to the internet. + sk = Nie ste pripojení k internetu.