Managing Android Application Resources and Localization
Resource Directory Structure
Android projects organize non-code assets into specific subdirectories within the res/ folder. The build system optimizes these assets, and you access them via unique resource IDs generated in the R class.
| Directory | Resource Type |
|---|---|
animator/ | XML files defining property animations. |
anim/ | XML files defining tween animations. |
color/ | XML files defining color state lists. |
drawable/ | Bitmaps (.png, .9.png, .jpg, .gif) or XML drawables (shapes, layers, selectors). |
mipmap/ | Launcher icon assets for different densities. |
layout/ | XML files defining UI structure. |
menu/ | XML files defining application menus (options, context, submenus). |
raw/ | Arbitrary files to be opened as a raw input stream (e.g., audio, video). |
values/ | XML files containing simple values (strings, colors, dimensions, styles). Multiple resources can be defined in one file. |
xml/ | Arbitrary XML configuration files read at runtime. |
font/ | Font files (.ttf, .otf) or XML font family definitions. |
Note: Do not place resource files directly into the res/ root directory; it will cause a compilation error.
Configuration Qualifiers
Directory names can be modified with configuration qualifiers to allow the system to load the most appropriate resources based on the current device state. The naming convention follows the pattern: <resources_name>-<config_qualifier>.
| Configuration | Qualifier Examples | Description |
|---|---|---|
| MCC and MNC | mcc310mcc310-mnc004 | Mobile Country Code and Mobile Network Code from the SIM card. |
| Language and Region | enfr-rCAb+es+419 | Two-letter ISO 639-1 language code, optionally followed by a region code. Android 7.0+ supports BCP 47 tags (e.g., b+es+419). |
| Layout Direction | ldrtlldltr | ldrtl for right-to-left layouts, ldltr for left-to-right (default). |
| Smallest Width | sw600dp | The minimum width of the screen available to the layout, regardless of orientation. |
| Available Width | w720dp | The current minimum available width, changing with orientation. |
| Available Height | h1024dp | The current minimum available height, changing with orientation. |
| Screen Size | smallnormallargexlarge | Generalized screen size buckets based on physical dimensions. |
| Screen Aspect | longnotlong | Widescreen aspect ratio versus non-widescreen. |
| Screen Shape | roundnotround | Circular screens (wearables) versus standard rectangular screens. |
| Wide Color Gamut | widecgnowidecg | Display capability for wide color gamuts (P3, AdobeRGB) or standard sRGB. |
| Dynamic Range | highdrlowdr | High Dynamic Range (HDR) versus Standard Dynamic Range. |
| Orientation | portland | Portrait or landscape orientation. |
| UI Mode | cardesktelevisionwatch | The device is docked in a car, desk, or presenting on a TV, or is a wearable. |
| Night Mode | nightnotnight | Whether night mode is active. |
| Screen Density | ldpihdpixhdpixxhdpixxxhdpinodpi | Density of the screen. nodpi prevents scaling. |
| Touchscreen | notouchfinger | Absence of touch or primary finger interaction. |
| Keyboard Availability | keysexposedkeyshidden | Visibility status of a physical or software keyboard. |
| Primary Input | nokeysqwerty12key | Type of hardware keyboard available. |
| Navigation | navexposednavhidden | Availability of navigation keys. |
| Navigation Method | nonavdpadtrackball | Primary non-touch navigation method. |
| API Version | v21 | Minimum API level supported by the resource. |
Handling Configuration Changes
When configuration changes occur (such as screen rotation), the default behavior is to destroy and recreate the Activity. To persist UI data across these changes efficiently, modern Android development relies on using ViewModel objects.
Localization and Locale Detection
To adapt the application to different languages, developers can retrieve the current system locale via the Context configuration.
Java Implementation
Configuration sysConfig = context.getResources().getConfiguration();
Locale primaryLocale = sysConfig.getLocales().get(0);
String userLocale = primaryLocale.getDisplayName();
Kotlin Implementation
val primaryLocale: Locale = context.resources.configuration.locales[0]
val userLocale: String = primaryLocale.displayName
Testing with Pseudolocales
Pseudolocales allow developers to test UI layouts for potential expansion issues caused by string translations. Enable this in your build.gradle file:
android {
buildTypes {
debug {
pseudoLocalesEnabled true
}
}
}
Unicode and Internationalization (ICU)
Starting with Android 7.0 (API level 24), the framework utilizes the android.icu library for comprehensive Unicode and internationalization support. Older versions relying on com.ibm.icu should migrate to the native Android package.
| Legacy Class | Android ICU Replacement |
|---|---|
java.lang.Character | android.icu.lang.UCharacter |
java.text.BreakIterator | android.icu.text.BreakIterator |
java.text.DecimalFormat | android.icu.text.DecimalFormat |
java.util.Calendar | android.icu.util.Calendar |
android.text.format.DateFormat | android.icu.text.DateFormat |
Note that android.icu does not automatically respect the user's 12/24-hour preference. You must manually apply the skeleton:
boolean is24Hour = android.text.format.DateFormat.is24HourFormat(context);
String timeSkeleton = is24Hour ? "Hm" : "hm";
android.icu.text.DateFormat formatter = android.icu.text.DateFormat.getInstanceForSkeleton(timeSkeleton, Locale.getDefault());
String resultTime = formatter.format(new Date());
val is24Hour = android.text.format.DateFormat.is24HourFormat(context)
val timeSkeleton = if (is24Hour) "Hm" else "hm"
val formatter = android.icu.text.DateFormat.getInstanceForSkeleton(timeSkeleton, Locale.getDefault())
val resultTime = formatter.format(Date())
Transliteration
Android 10 (API level 29) introduced the Transliterator API. This utility converts text from one script to another (e.g., Latin to Cyrillic). Developers should verify available IDs using Transliterator.getAvailableIDs() before usage, as IDs vary by device manufacturer.
Resource Resolution Strategy
The Android runtime attempts to find the most specific resource match. If an exact match is not found (e.g., requesting es-MX when only es-ES exists), it may fall back to a less specific qualifier. If no match is found, it defaults to the resource set without qualifiers.
To improve matching accuracy, especially for region-agnostic languages, use BCP 47 language tags. For instance, move generic Spanish resources to values-b+es+419 (Latin American Spanish) rather than a specific country code. Furthermore, API 24+ allows querying the user's full preferred language list via LocaleList.getDefault() for more granular control.
Animated Vector Drawables
Historically, creating an animated vector drawable required three separate XML files: a vector drawable, an animator, and an animated-vector XML linking them.
Modern Android tooling allows consolidating these into a single XML file using inline resources via the aapt:attr namespace. This reduces file clutter and simplifies asset management.
Consolidated Example (AVD):
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<!-- Inline Vector Definition -->
<aapt:attr name="android:drawable">
<vector
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<group
android:name="scaleGroup"
android:pivotX="50"
android:pivotY="50">
<path
android:fillColor="#FF5722"
android:pathData="M50,50 L50,20 A30,30 0 1,1 50,80 A30,30 0 1,1 50,20 Z" />
</group>
</vector>
</aapt:attr>
<!-- Inline Animation Target -->
<target android:name="scaleGroup">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="2000"
android:propertyName="scaleX"
android:valueFrom="1"
android:valueTo="1.5"
android:valueType="floatType"
android:repeatCount="-1"
android:repeatMode="reverse" />
</aapt:attr>
</target>
</animated-vector>