Get Started

iOS & Android Integration

LangCTL exports platform-specific formats for iOS (Localizable.strings) and Android (strings.xml) with automatic parameter conversion.

iOS Integration

Export for iOS

langctl export my-app -l en -f ios-strings -o ./Resources/en.lproj/Localizable.strings
langctl export my-app -l es -f ios-strings -o ./Resources/es.lproj/Localizable.strings
langctl export my-app -l fr -f ios-strings -o ./Resources/fr.lproj/Localizable.strings

File Structure

MyApp/
  Resources/
    en.lproj/
      Localizable.strings
    es.lproj/
      Localizable.strings
    fr.lproj/
      Localizable.strings

Using Translations in Swift

// Basic usage
let welcome = NSLocalizedString("home.welcome", comment: "Welcome message")

// With parameters
let greeting = String(
format: NSLocalizedString("home.greeting", comment: ""),
username
)

// SwiftUI
import SwiftUI

struct ContentView: View {
var body: some View {
  VStack {
    Text(LocalizedStringKey("home.welcome"))
    Text(LocalizedStringKey("home.subtitle"))
  }
}
}

LangCTL converts {{param}} to iOS format:

LangCTLiOS Output
{{username}}%@
{{count}}%@
{{username}}, {{count}}%1$@, %2$@
langctl export my-app -l en -f android-xml -o ./app/src/main/res/values/strings.xml
langctl export my-app -l es -f android-xml -o ./app/src/main/res/values-es/strings.xml
langctl export my-app -l fr -f android-xml -o ./app/src/main/res/values-fr/strings.xml

File Structure

app/
  src/
    main/
      res/
        values/
          strings.xml      (English - default)
        values-es/
          strings.xml      (Spanish)
        values-fr/
          strings.xml      (French)

Using Translations in Kotlin

// Basic usage
val welcome = getString(R.string.home_welcome)

// Set in TextView
binding.textView.text = getString(R.string.home_welcome)

// With parameters
val greeting = getString(R.string.home_greeting, username)
binding.textView.text = getString(R.string.home_greeting, username, count)

// In Compose
@Composable
fun HomeScreen() {
Column {
  Text(stringResource(R.string.home_welcome))
  Text(stringResource(R.string.home_subtitle))
}
}

LangCTL converts {{param}} to Android format:

LangCTLAndroid Output
{{username}}%1$s
{{count}}%1$d
{{username}}, {{count}}%1$s, %2$d
ℹ️
Note

Android uses positional parameters (%1$s, %2$s) for proper pluralization and RTL support.

name: iOS Build

on: [push]

jobs:
build:
  runs-on: macos-latest
  steps:
    - uses: actions/checkout@v3

    - name: Install LangCTL
      run: npm install -g @langctl/cli

    - name: Export Translations
      env:
        LANGCTL_API_KEY: ${{ secrets.LANGCTL_API_KEY }}
      run: |
        langctl export my-app -l en -f ios-strings -o ./Resources/en.lproj/Localizable.strings
        langctl export my-app -l es -f ios-strings -o ./Resources/es.lproj/Localizable.strings

    - name: Build iOS App
      run: xcodebuild -scheme MyApp -configuration Release
name: Android Build

on: [push]

jobs:
build:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v3

    - name: Set up JDK
      uses: actions/setup-java@v3
      with:
        java-version: '17'

    - name: Install LangCTL
      run: npm install -g @langctl/cli

    - name: Export Translations
      env:
        LANGCTL_API_KEY: ${{ secrets.LANGCTL_API_KEY }}
      run: |
        langctl export my-app -l en -f android-xml -o ./app/src/main/res/values/strings.xml
        langctl export my-app -l es -f android-xml -o ./app/src/main/res/values-es/strings.xml

    - name: Build Android App
      run: ./gradlew assembleRelease
lane :fetch_translations do
sh("npm install -g @langctl/cli")

["en", "es", "fr"].each do |lang|
  sh("langctl export my-app -l #{lang} -f ios-strings -o ../Resources/#{lang}.lproj/Localizable.strings")
end
end

lane :release do
fetch_translations
build_app(scheme: "MyApp")
upload_to_app_store
end
lane :fetch_translations do
sh("npm install -g @langctl/cli")

{
  "en" => "values",
  "es" => "values-es",
  "fr" => "values-fr"
}.each do |lang, dir|
  sh("langctl export my-app -l #{lang} -f android-xml -o ../app/src/main/res/#{dir}/strings.xml")
end
end

lane :release do
fetch_translations
gradle(task: "assembleRelease")
upload_to_play_store
end

Best Practices

iOS Best Practices

  1. Use String Catalogs (iOS 15+):

    • Export as Localizable.strings
    • Import into Xcode String Catalog
    • Benefit from Xcode’s translation editor
  2. Localized String Keys:

    • Use descriptive keys: home.welcome not string_001
    • Group by feature: auth.login.title
    • Add comments for context
  3. Test RTL Languages:

    • Export Arabic/Hebrew
    • Test layout in Xcode
    • Use Auto Layout constraints

Android Best Practices

  1. Resource Qualifiers:

    • Use correct directory names: values-es, values-ar
    • Follow Android conventions
    • Test with different configurations
  2. Pluralization:

    • Export plural forms separately
    • Use Android plural resources
    • Test all quantity cases
  3. String Formatting:

    • Avoid hardcoded strings
    • Use string resources everywhere
    • Format numbers/dates properly
💡
Tip

Always test translations on actual devices in different languages to catch layout and formatting issues.

Troubleshooting

iOS Issues

Strings not appearing:

  • Clean build folder (Cmd+Shift+K)
  • Check .lproj folder names match language codes
  • Verify Localizable.strings is in Copy Bundle Resources

Parameters not working:

  • Check String.format() syntax
  • Verify parameter order matches export
  • Use String(format:) correctly

Android Issues

Resources not found:

  • Check values-XX folder naming
  • Ensure strings.xml is valid XML
  • Clean and rebuild project

Parameters not formatting:

  • Verify correct parameter type (%s vs %d)
  • Check positional indicators (%1$s, %2$s)
  • Escape special characters properly

Next Steps