Translating your Android app is crucial for reaching a global audience. Android provides excellent built-in support for localization through resource files, making it straightforward to support multiple languages. This guide will walk you through the process of translating an Android app using Kotlin.
Understanding Android Localization
Android localization uses string resources stored in XML files within res/values directories. Each language gets its own values-xx folder (e.g., values-es for Spanish, values-fr for French). Android automatically loads the correct strings based on the user’s device language settings.
Step 1: Create String Resources
Start by creating string resources in your res/values/strings.xml file:
res/values/strings.xml (English - default):
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">My App</string>
<string name="welcome_message">Welcome to our app!</string>
<string name="button_submit">Submit</string>
<string name="error_network">Network error. Please try again.</string>
<string name="items_count">%1$d items</string>
</resources>
Step 2: Add Translations for Other Languages
Create language-specific folders and string files:
res/values-es/strings.xml (Spanish):
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Mi Aplicación</string>
<string name="welcome_message">¡Bienvenido a nuestra aplicación!</string>
<string name="button_submit">Enviar</string>
<string name="error_network">Error de red. Por favor, inténtalo de nuevo.</string>
<string name="items_count">%1$d elementos</string>
</resources>
res/values-fr/strings.xml (French):
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Mon Application</string>
<string name="welcome_message">Bienvenue dans notre application!</string>
<string name="button_submit">Soumettre</string>
<string name="error_network">Erreur réseau. Veuillez réessayer.</string>
<string name="items_count">%1$d éléments</string>
</resources>
Step 3: Use String Resources in Kotlin
Replace hardcoded strings with resource references:
Before:
val welcomeTextView = findViewById<TextView>(R.id.welcomeText)
welcomeTextView.text = "Welcome to our app!"
After:
val welcomeTextView = findViewById<TextView>(R.id.welcomeText)
welcomeTextView.text = getString(R.string.welcome_message)
Or in XML layouts:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/welcome_message" />
Step 4: Handle String Formatting with Variables
Use getString() with format arguments for strings with variables:
strings.xml:
<string name="items_count">%1$d items</string>
<string name="user_greeting">Hello, %1$s! You have %2$d messages.</string>
Kotlin:
val itemCount = 5
val message = getString(R.string.items_count, itemCount)
// English: "5 items"
// Spanish: "5 elementos"
val userName = "John"
val messageCount = 3
val greeting = getString(R.string.user_greeting, userName, messageCount)
// English: "Hello, John! You have 3 messages."
Step 5: Pluralization
Android supports pluralization using plurals resources:
res/values/strings.xml:
<plurals name="items_count">
<item quantity="zero">No items</item>
<item quantity="one">%d item</item>
<item quantity="other">%d items</item>
</plurals>
res/values-es/strings.xml:
<plurals name="items_count">
<item quantity="zero">No hay elementos</item>
<item quantity="one">%d elemento</item>
<item quantity="other">%d elementos</item>
</plurals>
Kotlin usage:
val count = 1
val message = resources.getQuantityString(R.plurals.items_count, count, count)
// English: "1 item" (singular)
// English: "5 items" (plural)
// Spanish: "1 elemento" (singular)
// Spanish: "5 elementos" (plural)
Step 6: Localize Images and Drawables
You can provide different images for different languages:
-
Create language-specific drawable folders:
res/drawable/(default)res/drawable-es/(Spanish)res/drawable-fr/(French)
-
Place localized images in each folder with the same name
-
Reference them normally:
imageView.setImageResource(R.drawable.flag_icon)
// Android automatically selects the correct image based on language
Step 7: Format Dates and Numbers
Use Locale and formatting classes for localized formatting:
Dates:
import java.text.SimpleDateFormat
import java.util.*
val date = Date()
val formatter = SimpleDateFormat("EEEE, MMMM dd, yyyy", Locale.getDefault())
val localizedDate = formatter.format(date)
// English: "Wednesday, March 20, 2025"
// Spanish: "miércoles, 20 de marzo de 2025"
Numbers:
import java.text.NumberFormat
import java.util.*
val number = 1234.56
val formatter = NumberFormat.getNumberInstance(Locale.getDefault())
val localizedNumber = formatter.format(number)
// English: "1,234.56"
// Spanish: "1.234,56"
Currency:
val amount = 1234.56
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
val localizedCurrency = formatter.format(amount)
// English (US): "$1,234.56"
// Spanish (ES): "1.234,56 €"
Step 8: Handle Right-to-Left (RTL) Languages
Android automatically handles RTL layouts when you use:
startandendinstead ofleftandrightin XMLpaddingStart/paddingEndinstead ofpaddingLeft/paddingRightmarginStart/marginEndinstead ofmarginLeft/marginRight
XML:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textAlignment="viewStart" />
Kotlin:
textView.paddingStart = 16.dp
textView.paddingEnd = 16.dp
Step 9: Test Your Localization
Using Android Studio:
- Go to Run > Edit Configurations
- Under General, set Language to the language you want to test
- Run your app
Using Device Settings:
- Go to Settings > System > Languages & input
- Add or change the device language
- Launch your app
Programmatically (for testing):
import android.content.res.Configuration
import android.content.res.Resources
import java.util.*
fun setAppLocale(context: Context, language: String) {
val locale = Locale(language)
Locale.setDefault(locale)
val config = Configuration()
config.setLocale(locale)
context.resources.updateConfiguration(config, context.resources.displayMetrics)
}
Best Practices
1. Use Descriptive String Names
Bad:
<string name="s1">Submit</string>
Good:
<string name="button_submit">Submit</string>
2. Avoid Hardcoding Strings
Bad:
Toast.makeText(this, "Error occurred", Toast.LENGTH_SHORT).show()
Good:
Toast.makeText(this, getString(R.string.error_occurred), Toast.LENGTH_SHORT).show()
3. Use String Arrays for Lists
res/values/strings.xml:
<string-array name="countries">
<item>United States</item>
<item>Canada</item>
<item>Mexico</item>
</string-array>
Kotlin:
val countries = resources.getStringArray(R.array.countries)
4. Handle HTML in Strings
If you need HTML formatting:
strings.xml:
<string name="formatted_text"><![CDATA[This is <b>bold</b> and <i>italic</i> text.]]></string>
Kotlin:
val htmlText = getString(R.string.formatted_text)
textView.text = Html.fromHtml(htmlText, Html.FROM_HTML_MODE_LEGACY)
5. Test String Lengths
Some languages are longer than others. Design your UI to accommodate:
- German and Finnish: 30-50% longer than English
- Asian languages: May need more vertical space
- Arabic and Hebrew: RTL layout considerations
6. Use Context-Specific Strings
If the same word has different meanings in different contexts:
strings.xml:
<string name="button_cancel">Cancel</string>
<string name="action_cancel_order">Cancel Order</string>
Common Pitfalls
1. Forgetting to Localize All Strings
Make sure to localize:
- Button labels
- Error messages
- Toast messages
- Dialog titles and messages
- Menu items
- Notification text
2. Hardcoding Format Strings
Bad:
val price = String.format("$%.2f", amount)
Good:
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
val price = formatter.format(amount)
3. Not Handling Missing Translations
If a translation is missing, Android falls back to the default language (usually English). Always provide fallbacks:
val text = try {
getString(R.string.some_string)
} catch (e: Resources.NotFoundException) {
getString(R.string.default_string)
}
4. Ignoring RTL Support
Always use start/end instead of left/right to support RTL languages like Arabic and Hebrew.
Advanced: Dynamic Language Switching
To allow users to change language within the app:
import android.content.Context
import android.content.res.Configuration
import java.util.*
class LocaleHelper {
companion object {
fun setLocale(context: Context, language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val config = Configuration(context.resources.configuration)
config.setLocale(locale)
return context.createConfigurationContext(config)
}
fun getPersistedLanguage(context: Context): String {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
return prefs.getString("language", "en") ?: "en"
}
fun persistLanguage(context: Context, language: String) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
prefs.edit().putString("language", language).apply()
}
}
}
Usage in Activity:
class MainActivity : AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
val language = LocaleHelper.getPersistedLanguage(newBase)
super.attachBaseContext(LocaleHelper.setLocale(newBase, language))
}
fun changeLanguage(language: String) {
LocaleHelper.persistLanguage(this, language)
recreate() // Restart activity to apply new language
}
}
Using Jetpack Compose
If you’re using Jetpack Compose for UI:
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
@Composable
fun WelcomeScreen() {
val context = LocalContext.current
Text(text = context.getString(R.string.welcome_message))
}
// With formatting
@Composable
fun ItemCount(count: Int) {
val context = LocalContext.current
Text(text = context.getString(R.string.items_count, count))
}
Conclusion
Localizing your Android app with Kotlin is straightforward when you follow these steps:
- Create
strings.xmlfiles for each language inres/values-xx/folders - Use
getString()instead of hardcoded strings - Handle pluralization with
pluralsresources - Format dates and numbers using
Locale - Support RTL languages with
start/endattributes - Test thoroughly in all supported languages
By following these practices, you’ll create an app that provides a native experience for users worldwide, significantly expanding your potential user base.
Streamline Your Android Localization Workflow
Managing translations for multiple languages can become complex as your app grows. Consider using a translation management platform to:
- Collaborate with translators
- Keep translations in sync with your codebase
- Automate the translation workflow
- Maintain consistency across all languages
- Integrate with your CI/CD pipeline
Ready to take your Android app global? Explore AZbox’s localization platform and streamline your translation workflow: