From 234614e4401b2d7a3821610c6391151c1dc77d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Kuchy=C5=88ka=20=28Anty=29?= Date: Mon, 9 Feb 2026 12:53:05 +0100 Subject: [PATCH] feat: expose available languages and add developer tooling Add availableLocales and availableLocaleTags properties to the Tolgee class, allowing apps to dynamically query which languages are available from the CDN manifest without hardcoding them. Also consolidate AI agent guidelines into AGENTS.md, add CLAUDE.md, and create a /release command for streamlined version publishing. Co-Authored-By: Claude Opus 4.6 --- .claude/commands/release.md | 109 ++++++++++++ .junie/guidelines.md | 158 ------------------ AGENTS.md | 119 +++++++++++++ CLAUDE.md | 1 + core/api/android/core.api | 2 + core/api/jvm/core.api | 2 + .../src/commonMain/kotlin/io/tolgee/Tolgee.kt | 31 ++++ 7 files changed, 264 insertions(+), 158 deletions(-) create mode 100644 .claude/commands/release.md delete mode 100644 .junie/guidelines.md create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/.claude/commands/release.md b/.claude/commands/release.md new file mode 100644 index 0000000..3c99014 --- /dev/null +++ b/.claude/commands/release.md @@ -0,0 +1,109 @@ +--- +allowed-tools: Bash(grep:*), Bash(git describe:*), Bash(git branch:*), Bash(git log:*), Bash(git diff:*), Bash(git status:*), Bash(git tag:*), Bash(git add:*), Bash(git commit:*), Bash(git reset:*), Bash(gh repo view:*), Bash(gh release view:*), Bash(gh run:*), Bash(gh run list:*), Bash(gh run watch:*), Bash(gh run rerun:*), Bash(gh workflow run:*), Bash(./gradlew:*), Edit, Read +description: Release a new version - bump version, commit, tag, push, publish to Maven Central, create GitHub release +argument-hint: "[major|minor|patch|]" +--- + +# Release + +## Current State + +- Current version: !`grep '^library' gradle/libs.versions.toml` +- Latest git tag: !`git describe --tags --abbrev=0 2>/dev/null || echo "(no tags)"` +- Current branch: !`git branch --show-current` +- GitHub repo: !`gh repo view --json nameWithOwner -q .nameWithOwner` + +## Instructions + +Follow these steps precisely to create a new release. + +### Step 1: Preflight checks + +1. Verify we are on the `master` branch. If not, **stop** and warn the user. +2. Verify the working tree is clean (`git status --porcelain`). If not, **stop** and warn the user. +3. Pull latest: `git pull --ff-only`. If this fails, **stop** and warn the user. + +### Step 2: Determine version bump + +The current version uses pre-release identifiers (e.g., `1.0.0-alpha03`). Version bumping rules: + +If `$ARGUMENTS` is one of `major`, `minor`, or `patch`: +- **patch**: increment the last numeric segment (e.g., `1.0.0-alpha03` → `1.0.0-alpha04`, or `1.2.3` → `1.2.4`) +- **minor**: `X.Y.Z` → `X.Y+1.0` (drops any pre-release suffix) +- **major**: `X.Y.Z` → `X+1.0.0` (drops any pre-release suffix) — always confirm with user first + +If `$ARGUMENTS` is an explicit version string (e.g., `1.0.0-beta01` or `1.0.0`), use it directly. + +Otherwise, analyze the commits since the last tag and suggest an appropriate bump. Present the proposed new version to the user and ask them to confirm using `AskUserQuestion`. + +### Step 3: Summarize changes + +List the commits since the last tag using `git log ..HEAD --oneline`. Write concise, user-facing bullet points for each meaningful change. These will be used in the GitHub release body alongside the auto-generated changelog. + +### Step 4: Execute the release + +Do all of the following in order: + +1. Edit `gradle/libs.versions.toml` to update the `library` version on line 9 to the new version +2. Build to verify nothing is broken: + ``` + ./gradlew apiDump + ./gradlew :core:build + ``` + If the build fails, **stop** and help the user fix the issue before continuing. +3. Stage and commit: + ``` + git add gradle/libs.versions.toml core/api/ + git commit -m "release: vX.Y.Z" + ``` +4. Tag: `git tag vX.Y.Z` +5. Push: `git push && git push --tags` + +### Step 5: Wait for CI + +After pushing, monitor the CI workflows triggered by the push: + +1. Wait a moment for workflows to trigger, then use `gh run list --branch master --limit 5` to find the runs +2. Use `gh run watch ` on the test workflow run + +If all checks pass, proceed to Step 6. + +If any check fails: +- **Flaky/random failure**: Rerun with `gh run rerun --failed`, then wait again +- **Real failure**: We need to roll back and fix the issue: + 1. Delete the remote tag: `git push --delete origin vX.Y.Z` + 2. Delete the local tag: `git tag -d vX.Y.Z` + 3. Revert the release commit: `git reset --hard HEAD~1 && git push --force` + 4. Help the user fix the issue, then re-release + +### Step 6: Create GitHub release + +Create the release on GitHub using the auto-generated changelog: + +``` +gh release create vX.Y.Z --title "vX.Y.Z" --generate-notes +``` + +This will: +- Create a GitHub release with auto-generated PR-based changelog +- Automatically trigger the `apple-binaries.yml` workflow (attaches XCFramework archives to the release) + +### Step 7: Trigger Maven Central publish + +Trigger the publish workflow manually: + +``` +gh workflow run publish.yml +``` + +### Step 8: Monitor post-release workflows + +1. Use `gh run list --limit 5` to find the publish and apple-binaries workflow runs +2. Watch both with `gh run watch ` +3. If the publish workflow fails, inform the user — they may need to check Maven Central / Sonatype credentials +4. If apple-binaries fails, it can be re-triggered: `gh workflow run apple-binaries.yml` + +Once all workflows complete successfully, report: +- Link to the GitHub release +- Confirm Maven Central publish status +- Confirm Apple XCFramework archives attached to release diff --git a/.junie/guidelines.md b/.junie/guidelines.md deleted file mode 100644 index ad20187..0000000 --- a/.junie/guidelines.md +++ /dev/null @@ -1,158 +0,0 @@ -# Development Guidelines for Tolgee Mobile Kotlin SDK - -This document provides essential information for developers working on the Tolgee Mobile Kotlin SDK project. - -## Build/Configuration Instructions - -### Project Structure - -The project consists of several modules: - -- **core**: The Kotlin Multiplatform library providing runtime support for Tolgee translations -- **compose**: Compose Multiplatform integration for Tolgee -- **compiler-plugin**: Kotlin compiler plugin for Tolgee -- **gradle-plugin**: Gradle plugin for integrating Tolgee into projects -- **demo**: Demo applications showcasing Tolgee usage - -### Building the Project - -1. **Prerequisites**: - - JDK 11 or higher - - Kotlin 1.9.0 or higher - - Android SDK (for Android targets) - - Xcode (for Apple targets) - -2. **Building from the command line**: - ```bash - ./gradlew build - ``` - -3. **Building specific modules**: - ```bash - ./gradlew :core:build - ./gradlew :compose:build - ./gradlew :gradle-plugin:build - ./gradlew :compiler-plugin:build - ``` - -4. **Publishing to Maven Local** (for local testing): - ```bash - ./gradlew publishToMavenLocal - ``` - You may need to disable signing temporarily as it is enforced by default. - -## Testing Information - -Testing infrastructure is in a relatively ok state, but there are no tests at the moment. - -### Running Tests - -Tests can be run using the Gradle test task: - -```bash -# Run all tests -./gradlew test - -# Run tests for a specific module -./gradlew :gradle-plugin:test -./gradlew :core:test -./gradlew :compose:test -./gradlew :compiler-plugin:test - -# Run a specific test class -./gradlew :gradle-plugin:test --tests "TolgeeTest" - -# Run a specific test method -./gradlew :gradle-plugin:test --tests "TolgeeTest.tolgee cli version check" -``` - -### Adding New Tests - -1. **Create a test file** in the appropriate module's test directory: - - For JVM modules: `/src/test/kotlin/` - - For Android modules: `/src/androidTest/kotlin/` - - For multiplatform modules: `/src/commonTest/kotlin/` (or platform-specific test directories) - -2. **Test structure example**: - -```kotlin -class MyTest { - - @Test - fun `test some functionality`() { - // Test implementation - assertEquals(expected, actual, "Error message") - } -} -``` - -3. **Running your new test**: - ```bash - ./gradlew ::test --tests "MyTest" - ``` - -### Test Example - -Here's a simple test example that was created and verified to work: - -```kotlin -class SimpleTest { - - @Test - fun `simple addition test`() { - // A simple test to demonstrate testing in this project - assertEquals(4, 2 + 2, "Basic addition should work correctly") - } -} -``` - -## Additional Development Information - -### Code Style - -- The project follows Kotlin coding conventions -- Use 4 spaces for indentation -- The maximum line length is 120 characters (not fully enforced at the moment) -- Use trailing commas in parameter lists and collection literals that span multiple lines - -### Multiplatform Considerations - -- Common code should be placed in `commonMain` source sets -- Platform-specific code should be placed in the appropriate platform-specific source sets: - - `androidMain` for Android - - `jvmMain` for JVM - - `appleMain` for Apple platforms (iOS, macOS, etc.) - - `jsMain` for JavaScript - - etc. - -### Dependency Management - -- Dependencies are managed through Version Catalog in `gradle/libs.versions.toml` -- When adding new dependencies, add them to the Version Catalog rather than directly in build scripts - -### Publishing - -The project uses the Vanniktech Maven Publish plugin for publishing to Maven Central: - -```kotlin -mavenPublishing { - publishToMavenCentral(host = SonatypeHost.CENTRAL_PORTAL, automaticRelease = true) - signAllPublications() - - coordinates( - groupId = "io.tolgee", - artifactId = "module-name", - version = "version" - ) -} -``` - -### API Compatibility - -The project uses the Binary Compatibility Validator plugin to ensure API compatibility. After making changes to public APIs, run: - -```bash -./gradlew apiDump -``` - -This will update the API dump files in the `api` directory of each module. These files should be committed to the repository. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..66401c7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,119 @@ +# AGENTS.md + +Guidance for AI coding agents working on the Tolgee Mobile Kotlin SDK. + +Main branch: `master` + +## Project Structure + +``` +tolgee-mobile-kotlin-sdk/ +├── core/ # KMP base library (translations, caching, CDN API) +├── compose/ # Compose Multiplatform integration +├── compiler-plugin/ # Kotlin compiler plugin (currently disabled) +├── gradle-plugin/ # Gradle plugin for project integration +└── demo/ # Example apps (Android Views, Jetpack Compose, KMP Compose) +``` + +The compiler plugin is commented out in `settings.gradle.kts` (broken after Kotlin 2.2). + +## Building + +```bash +./gradlew :core:build +./gradlew :compose:build +./gradlew :gradle-plugin:build +``` + +## Testing + +Only the gradle-plugin module has tests currently: + +```bash +./gradlew :gradle-plugin:test +./gradlew :gradle-plugin:test --tests "TolgeeTest" +``` + +CI (`test.yml`) runs gradle-plugin tests on every push. Requires Tolgee CLI (`npm install --global @tolgee/cli`). + +## API Compatibility + +The project uses the Binary Compatibility Validator plugin. After any changes to public APIs, run: + +```bash +./gradlew apiDump +``` + +This updates `.api` dump files in each module's `api/` directory. These files **must be committed** with the change. The build will fail (`apiCheck`) if the dump is out of date. + +## Module Architecture + +### Core Module + +Main entry point: `Tolgee` singleton class in `core/src/commonMain/kotlin/io/tolgee/Tolgee.kt` + +Key components: +- `Tolgee` — Singleton with locale management, translation resolution, and configuration +- `TolgeeApi` — CDN communication and local caching +- `TolgeeTranslation` — Interface with ICU and sprintf formatting implementations +- `TolgeeStorageProvider` — Platform-specific persistent caching interface +- `TolgeeManifest` — Available locales metadata from CDN (internal) + +Data flow: +``` +tFlow("key", params) + → localeFlow emits locale + → loadManifest() fetches available locales from CDN + → resolveLocale() applies progressive BCP 47 fallback (zh-Hans-CN → zh-Hans → zh) + → loadTranslations() fetches from CDN or cache (LRU in-memory + persistent storage) + → Translation formatted and emitted via Flow +``` + +Thread safety: `Mutex` for translation loading, `AtomicFU` for manifest cache. + +### Compose Module + +Composable wrappers around Core with graceful fallback to default Compose resources when Tolgee is not initialized. Provides `stringResource()`, `pluralStringResource()`, `stringArrayResource()`. + +### Gradle Plugin + +Build-time configuration DSL bridging Gradle with the compiler plugin. Configures replacement of `getString()` and `stringResource()` calls. + +### Compiler Plugin (disabled) + +Kotlin IR transformations to replace standard resource calls with Tolgee calls at compile-time. Currently disabled in `settings.gradle.kts`. + +## Multiplatform + +The core module targets 20+ platforms: + +| Category | Targets | +|----------|---------| +| Android | androidTarget, androidNativeX64/X86/Arm64/Arm32 | +| JVM | jvm (toolchain 21) | +| Apple | iOS (x64, arm64, simulatorArm64), tvOS (×3), watchOS (×4), macOS (x64, arm64) | +| Other | linuxX64, linuxArm64, mingwX64, js (IR), wasmJs | + +Source set hierarchy follows `applyDefaultHierarchyTemplate()`: +- `commonMain` — All shared code +- `androidMain` — Android-specific (Views integration, storage) +- `appleMain` — Apple platforms (iOS/macOS/tvOS/watchOS) +- `jvmMain` — JVM-specific +- `jsMain` / `wasmJsMain` — JS/WASM + +## Key Design Patterns + +- **Builder pattern** for configuration: `Tolgee.Config.Builder`, `ContentDelivery.Builder`, `Network.Builder` +- **Sealed interfaces** for type safety: `Formatter` (ICU/Sprintf), `TolgeeMessageParams` (None/Indexed/Mapped) +- **Reactive Flows**: `localeFlow`, `changeFlow`, `tFlow()` returning `Flow` +- **Expect/actual** for platform-specific implementations (`platformHttpClient`, `platformStorage`, etc.) + +## Dependencies + +Managed via Version Catalog in `gradle/libs.versions.toml`. Add new dependencies there, not directly in build scripts. + +Key libraries: Ktor (HTTP), kotlinx-serialization (JSON), kotlinx-coroutines (async), i18n4k (locale handling), AtomicFU (thread safety), SKIE (Swift interop for Apple targets). + +## Publishing + +Manual trigger via `publish.yml` workflow on macOS. Publishes to Maven Central via `publishAllPublicationsToMavenCentralRepository`. Requires signing keys and Sonatype credentials. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/core/api/android/core.api b/core/api/android/core.api index c5db8f6..f3ba2e0 100644 --- a/core/api/android/core.api +++ b/core/api/android/core.api @@ -2,6 +2,8 @@ public class io/tolgee/Tolgee { public static final field Companion Lio/tolgee/Tolgee$Companion; public fun (Lio/tolgee/Tolgee$Config;)V public final fun addChangeListener (Lio/tolgee/Tolgee$ChangeListener;)V + public fun getAvailableLocaleTags ()Ljava/util/List; + public fun getAvailableLocales ()Ljava/util/List; public final fun getChangeFlow ()Lkotlinx/coroutines/flow/MutableSharedFlow; public fun getConfig ()Lio/tolgee/Tolgee$Config; public static final fun getInstance ()Lio/tolgee/TolgeeAndroid; diff --git a/core/api/jvm/core.api b/core/api/jvm/core.api index 7d0c1ab..3181477 100644 --- a/core/api/jvm/core.api +++ b/core/api/jvm/core.api @@ -2,6 +2,8 @@ public class io/tolgee/Tolgee { public static final field Companion Lio/tolgee/Tolgee$Companion; public fun (Lio/tolgee/Tolgee$Config;)V public final fun addChangeListener (Lio/tolgee/Tolgee$ChangeListener;)V + public fun getAvailableLocaleTags ()Ljava/util/List; + public fun getAvailableLocales ()Ljava/util/List; public final fun getChangeFlow ()Lkotlinx/coroutines/flow/MutableSharedFlow; public fun getConfig ()Lio/tolgee/Tolgee$Config; public static final fun getInstance ()Lio/tolgee/common/PlatformTolgee; diff --git a/core/src/commonMain/kotlin/io/tolgee/Tolgee.kt b/core/src/commonMain/kotlin/io/tolgee/Tolgee.kt index e9cc29d..b53bc6d 100644 --- a/core/src/commonMain/kotlin/io/tolgee/Tolgee.kt +++ b/core/src/commonMain/kotlin/io/tolgee/Tolgee.kt @@ -496,6 +496,37 @@ open class Tolgee( */ open fun getLocale() = localeFlow.value ?: systemLocale + /** + * The list of available locales from the manifest or configuration. + * + * Returns the locales configured via [Config.Builder.availableLocales], or if not + * manually configured, the locales fetched from the CDN manifest. + * + * Returns null if the manifest hasn't been loaded yet and no manual config was provided. + * Call [preload] first to ensure the manifest is loaded. + * + * For reactive updates, combine with [changeFlow]: + * ```kotlin + * tolgee.changeFlow.collect { + * val locales = tolgee.availableLocales + * } + * ``` + */ + open val availableLocales: List? + get() = config.availableLocales ?: cachedManifest.value?.availableLocales + + /** + * The list of available locale tags (e.g., `["en", "fr", "de"]`) from the manifest or configuration. + * + * Returns null if the manifest hasn't been loaded yet and no manual config was provided. + * Call [preload] first to ensure the manifest is loaded. + * + * @see availableLocales + */ + open val availableLocaleTags: List? + get() = config.availableLocales?.map { it.toTag("-") } + ?: cachedManifest.value?.locales + /** * Represents the configuration used for API integration and content management. *