Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/an_pr_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,4 @@ jobs:

- name: Build
run: |
./gradlew generateTwine
./gradlew :android:app:bundleDebug
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ The project uses Gradle convention plugins (located in `build-logic/convention`)
#### Android Modules
- **`android-application-compose`** - For Android application modules with Compose support
- Applies Android application plugin, Compose compiler, and Compose dependencies
- Configures build variants (alpha/production), signing, and Twine string generation
- Configures build variants (alpha/production), signing
- **`android-application-core`** - For Android application modules without Compose
- Same as above but without Compose configuration
- **`android-library-compose`** - For Android library modules with Compose support
Expand Down Expand Up @@ -460,21 +460,19 @@ The `AuthProviderImpl` will automatically use the provided `TokenRefresher` when

### Resources

#### Twine
#### Sentiary

All strings in the application are localized and shared with the iOS team
via [Twine](https://github.com/scelis/twine). Strings are stored in the `twine/strings.txt` file.
TwinePlugin then generates appropriate `strings.xml` files from the mentioned `strings.txt` file.
When modifying `strings.txt` it is required to comply with the specified syntax and to pull/push all
the changes frequently
All strings and localizations in the application are managed via the [Sentiary](https://sentiary.com/) platform.
We use the Sentiary Gradle Plugin to automatically fetch the latest translations during the build process.
Comment thread
ViktorKaderabek marked this conversation as resolved.

The plugin is configured to automatically generate two formats simultaneously whenever you build the app:
- **Compose Resources**: Generated into the `commonMain` module for Android.
- **Apple Strings**: Native iOS `.strings` files (e.g., `Localizable.strings`) exported directly into the Xcode project structure.
Comment thread
ViktorKaderabek marked this conversation as resolved.

#### Moko

Error messages are shared via [Moko Resources](https://github.com/icerockdev/moko-resources), so
that we can use the strings in the shared code and avoid duplicities when converting errors to
string messages. Error strings are stored in the `twine/errors.txt` file. Gradle task
`generateErrorsTwine` first generates `strings.xml` files from `errors.txt` and then gradle task
`generateMRCommonMain` generates `MR` class that can be used in the common code.
Strings are shared via [Moko Resources](https://github.com/icerockdev/moko-resources), so
that we can use the strings in the shared code

### UI - Compose Multiplatform

Expand Down
5 changes: 5 additions & 0 deletions build-logic/convention/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {
compileOnly(libs.kotlin.gradlePlugin)
compileOnly(libs.compose.gradlePlugin)
compileOnly(libs.ktlint.gradlePlugin)
compileOnly(libs.sentiary.gradlePlugin)
}

gradlePlugin {
Expand Down Expand Up @@ -49,6 +50,10 @@ gradlePlugin {
dependency = libs.plugins.mateeStarter.kmp.framework.library,
pluginName = "KmpFrameworkLibraryConventionPlugin",
)
plugin(
dependency = libs.plugins.mateeStarter.kmp.sentiary,
pluginName = "SentiaryConvention",
)
}
}

Expand Down
57 changes: 0 additions & 57 deletions build-logic/convention/src/main/kotlin/config/TwineConfig.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import config.configureApplicationVariants
import config.configureBuildVariants
import config.configureKotlinAndroid
import config.configureSingingConfigs
import config.configureTwine
import constants.Application
import extensions.apply
import extensions.implementation
Expand Down Expand Up @@ -59,8 +58,6 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
configureApplicationVariants()
}

configureTwine()

dependencies {
implementation(libs.material)
implementation(libs.androidX.core)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package plugin

import com.sentiary.SentiaryPluginExtension
import extensions.apply
import extensions.libs
import extensions.pluginManager
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.invoke

class SentiaryConvention : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager {
apply(libs.plugins.sentiary)
}

extensions.configure<SentiaryPluginExtension> {
defaultLanguage.set("en-GB")
}
}
}
}
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ plugins {
alias(libs.plugins.jetbrains.compose.compiler) apply false
alias(libs.plugins.versions)
alias(libs.plugins.versionCatalogUpdate)
alias(libs.plugins.sentiary) apply false
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
}

Expand Down
3 changes: 3 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ android.useAndroidX=true
kotlin.mpp.androidSourceSetLayoutVersion=2
# Moko
moko.resources.disableStaticFrameworkWarning=true
# Sentiary
sentiary.projectId=sL617ZwnlaW151ZJfDYc
sentiary.projectApiKey=tJr4F92ZaVxN4vBy/GFHICa9DhmimkhdBjETSAMXLxPn+j17ngXD8y+yQBvTgmuhhm3VvVRtv7qzpSoGvPLwNQ==
Comment thread
DavidKadlcek marked this conversation as resolved.
Comment thread
DavidKadlcek marked this conversation as resolved.
# Caches
kotlin.native.cacheKind.iosArm64=static
kotlin.native.cacheKind.iosSimulatorArm64=static
Expand Down
6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ skie = "0.10.6"
firebase = "22.5.0"
googleServices = "4.4.3"
jetbrainsKotlinJvm = "2.2.10"
sentiary = "1.0.1"

[libraries]
# Kotlin
Expand Down Expand Up @@ -131,6 +132,8 @@ molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref
firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx", version.ref = "firebase" }
# Ktlint
ktlint-gradlePlugin = { module = "org.jlleitschuh.gradle.ktlint:org.jlleitschuh.gradle.ktlint.gradle.plugin", version.ref = "ktLint" }
# Sentiary
sentiary-gradlePlugin = { module = "com.sentiary:gradle-plugin", version.ref = "sentiary" }

[bundles]
settings = [
Expand Down Expand Up @@ -163,6 +166,8 @@ skie = { id = "co.touchlab.skie", version.ref = "skie" }
google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
jetbrains-compose-plugin = { id = "org.jetbrains.compose", version.ref = "jetbrains-composePlugin" }
jetbrains-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
sentiary = { id = "com.sentiary.gradle", version.ref = "sentiary" }

# Convention plugins
mateeStarter-android-application-compose = "android-application-compose:none"
mateeStarter-android-application-core = "android-application:none"
Expand All @@ -171,4 +176,5 @@ mateeStarter-android-library-core = "android-library:none"
mateeStarter-kmp-library-core = "kmp-library:none"
mateeStarter-kmp-library-compose = "kmp-library-compose:none"
mateeStarter-kmp-framework-library = "kmp-framework-library:none"
mateeStarter-kmp-sentiary = "kmp-sentiary-library-convention:none"
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
2 changes: 1 addition & 1 deletion ios/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ DerivedData/
## KMP generated
DomainLayer/KMPShared.xcframework

## Twine generated
## Sentiary generated
Localizable.strings

## Generated code
Expand Down
4 changes: 0 additions & 4 deletions ios/MateeStarter.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
4508332A28435E7800BC9460 /* SharedDomain in Frameworks */ = {isa = PBXBuildFile; productRef = 4508332928435E7800BC9460 /* SharedDomain */; };
4508333028435E7900BC9460 /* Utilities in Frameworks */ = {isa = PBXBuildFile; productRef = 4508332F28435E7900BC9460 /* Utilities */; };
4508333728437BB700BC9460 /* UIToolkit in Frameworks */ = {isa = PBXBuildFile; productRef = 4508333628437BB700BC9460 /* UIToolkit */; };
453580EF284806FE007AEC9E /* strings.txt in Resources */ = {isa = PBXBuildFile; fileRef = 453580EE284806FE007AEC9E /* strings.txt */; };
454F6EC426CE8B3900C386B8 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 454F6EC226CE8B3500C386B8 /* InfoPlist.strings */; };
4558D30B20FCC8FF005BC325 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4558D30A20FCC8FF005BC325 /* AppDelegate.swift */; };
4575E65625F6FA7600CCC003 /* AppIcon.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4575E65525F6FA7600CCC003 /* AppIcon.xcassets */; };
Expand Down Expand Up @@ -42,7 +41,6 @@
4508333128436EFB00BC9460 /* UIToolkit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = UIToolkit; sourceTree = "<group>"; };
45238BDC2ACD7A5D0084E7E5 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
4534C1C42603D6AD0022ACF4 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
453580EE284806FE007AEC9E /* strings.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = strings.txt; path = ../../twine/strings.txt; sourceTree = "<group>"; };
453C8807283FAB4C0027BDFB /* SharedDomain */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SharedDomain; sourceTree = "<group>"; };
454F6EC326CE8B3500C386B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
454F6EC526CE8B4100C386B8 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -92,7 +90,6 @@
children = (
A3C5389B2F0EA941008A995E /* SampleFeature */,
4508333128436EFB00BC9460 /* UIToolkit */,
453580EE284806FE007AEC9E /* strings.txt */,
);
path = PresentationLayer;
sourceTree = "<group>";
Expand Down Expand Up @@ -279,7 +276,6 @@
files = (
4575E65625F6FA7600CCC003 /* AppIcon.xcassets in Resources */,
7E277A062D8C65FA0035B990 /* GoogleService-Info-Prod.plist in Resources */,
453580EF284806FE007AEC9E /* strings.txt in Resources */,
454F6EC426CE8B3900C386B8 /* InfoPlist.strings in Resources */,
7E277A082D8C663C0035B990 /* GoogleService-Info-Alpha.plist in Resources */,
4597626F20FDF0810034DE29 /* LaunchScreen.storyboard in Resources */,
Expand Down
23 changes: 9 additions & 14 deletions ios/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,19 @@

## Localization

### Twine
### Sentiary

- All strings in the application are localized and shared with the Android team
via [Twine](https://github.com/scelis/twine)
- Strings are stored in the `twine/strings.txt` file
- TwinePlugin then generates appropriate `Localizable.strings` files from the
mentioned `strings.txt` file
- When modifying `strings.txt` it is required to comply with the specified syntax and to pull/push
all the changes frequently
All strings and localizations in the application are managed via the [Sentiary](https://sentiary.com/) platform.
We use the Sentiary Gradle Plugin to automatically fetch the latest translations during the build process.

The plugin is configured to automatically generate two formats simultaneously whenever you build the app:
- **Compose Resources**: Generated into the `commonMain` module for Android.
- **Apple Strings**: Native iOS `.strings` files (e.g., `Localizable.strings`) exported directly into the Xcode project structure.
Comment thread
ViktorKaderabek marked this conversation as resolved.

### Moko

- Error messages are shared via [Moko Resources](https://github.com/icerockdev/moko-resources), so
that we can use the strings in the shared code and avoid duplicities when converting errors to
string messages
- Error strings are stored in the `twine/errors.txt` file
- Script `generate-error-messages.sh` calls needed gradle tasks (`generateErrorsTwine`
and `generateMRCommonMain`) to generate `MR` class
Strings are shared via [Moko Resources](https://github.com/icerockdev/moko-resources), so
that we can use the strings in the shared code

## Debug

Expand Down
3 changes: 0 additions & 3 deletions ios/scripts/generate-strings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,5 @@ cd "$(dirname "$0")"

cd ../..

echo "Generating Localizable files"
./gradlew generateTwine < /dev/null

echo "Generating MR resources from .xml files"
./gradlew :shared:base:generateMRcommonMain < /dev/null
2 changes: 0 additions & 2 deletions ios/scripts/rename.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,10 @@ mv ../${new_name}.xcodeproj/xcshareddata/xcschemes/${old_name}.xcscheme ../${new
sed -i '' -e "s/${old_name}/${new_name}/g" ../${new_name}.xcodeproj/xcshareddata/xcschemes/${new_name}.xcscheme

echo "Renaming support files"
sed -i '' -e "s/${old_name_lowercase}/${new_name_lowercase}/g" ../scripts/twine.sh
sed -i '' -e "s/${old_name}/${new_name}/g" ../scripts/swiftlint-analyze.sh
sed -i '' -e "s/${old_name}/${new_name}/g" ../fastlane/Fastfile
sed -i '' -e "s/${old_name_lowercase}/${new_name_lowercase}/g" ../fastlane/Fastfile
sed -i '' -e "s/${old_name}/${new_name}/g" ../.github/workflows/develop.yml
sed -i '' -e "s/${old_name}/${new_name}/g" ../scripts/setup.sh

echo "✅ Renaming successful"
echo "!!! Replace GoogleService-Info plists and ensure that twine directory exists !!!"
8 changes: 0 additions & 8 deletions ios/scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@ else
echo "✅ File header is properly set"
fi

echo "⚙️ Checking whether Twine is installed"
if command -v twine &> /dev/null; then
echo "✅ Twine is installed"
else
echo "❌ Twine is not installed"
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
Expand Down
2 changes: 1 addition & 1 deletion scripts/rename-project.sh
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ The project uses Gradle convention plugins (located in \`build-logic/convention\
#### Android Modules
- **\`android-application-compose\`** - For Android application modules with Compose support
- Applies Android application plugin, Compose compiler, and Compose dependencies
- Configures build variants (alpha/production), signing, and Twine string generation
- Configures build variants (alpha/production), signing
- **\`android-application-core\`** - For Android application modules without Compose
- Same as above but without Compose configuration
- **\`android-library-compose\`** - For Android library modules with Compose support
Expand Down
39 changes: 39 additions & 0 deletions shared/base/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import com.sentiary.config.Format

plugins {
alias(libs.plugins.mateeStarter.kmp.library.compose)
alias(libs.plugins.mateeStarter.kmp.sentiary)
}

android {
Expand All @@ -19,3 +22,39 @@ ktlint {
dependencies {
commonMainImplementation(libs.molecule.runtime)
}

tasks.named("prepareComposeResourcesTaskForCommonMain") {
dependsOn("sentiaryUpdateLocalizations")
}

sentiary {
exportPaths {
fun String.dropSubtag() = split("-").first()

create("Android") {
format.set(Format.Android)
outputDirectory.set(layout.projectDirectory.dir("src/commonMain/moko-resources"))

folderNamingStrategy { language, isDefault ->
if (isDefault) {
"base"
} else {
language.dropSubtag()
}
}
}

create("iOS") {
format.set(Format.Apple)
outputDirectory.set(rootProject.layout.projectDirectory.dir("ios/PresentationLayer/Sources/PresentationLayer/Sources/PresentationLayer/Resources/Localizable"))

folderNamingStrategy { language, isDefault ->
if (isDefault) {
"Base.lproj"
} else {
"${language.dropSubtag()}.lproj"
}
}
}
Comment thread
DavidKadlcek marked this conversation as resolved.
}
}
Loading