Files
archived-matedroid/docs/DEVELOPMENT.md
Davide Ferrari 903e352687 feat: Add internationalization (i18n) support (#67)
* feat(i18n): add localization support for DashboardScreen

- Set up localization infrastructure with values-it, values-es, values-ca folders
- Extract all hardcoded strings from DashboardScreen to strings.xml
- Add Italian, Spanish, and Catalan translations
- Include contextual comments for translators

Supported languages: English (default), Italian, Spanish, Catalan

* feat(i18n): add localization for SettingsScreen

- Extract all hardcoded strings from SettingsScreen
- Add Italian, Spanish, and Catalan translations
- Include contextual comments for server settings, display settings,
  and advanced options

* feat(i18n): add localization for DrivesScreen

- Extract all hardcoded strings from DrivesScreen
- Add localized filter labels for date and distance filters
- Add Italian, Spanish, and Catalan translations for:
  - Screen title and navigation
  - Summary card labels
  - Drive stat labels
  - Chart titles (drives/time/distance/speed per day/week/month)
  - Filter chip labels

* feat(i18n): add localization for DriveDetailScreen

- Extract all hardcoded strings from DriveDetailScreen
- Add Italian, Spanish, and Catalan translations for:
  - Screen title and navigation
  - Route header (from/to, started/ended)
  - Stats section titles and labels
  - Chart titles (speed/power/battery/elevation profiles)

* feat(i18n): localize ChargesScreen strings

Extract and translate all user-facing strings from ChargesScreen:
- Screen title and section headers
- Summary card labels (Total Sessions, Total Energy, Total Cost, etc.)
- Charge item stat labels (Added, Duration, Cost, Battery)
- Empty state message
- Chart titles for energy/cost/charges per day/week/month
- Filter labels using helper functions for DateFilter and ChargeTypeFilter

Translations provided for Italian (it), Spanish (es), and Catalan (ca).

* feat(i18n): localize ChargeDetailScreen strings

Extract and translate all user-facing strings from ChargeDetailScreen:
- Screen title and header labels (Location, Started, Ended)
- Stats section titles (Energy, Battery, Power, Charger, Temperature, Cost)
- Stats labels (voltage/current max/min/avg, efficiency, etc.)
- Chart titles (Power Profile, Voltage Profile, Current Profile, Battery Level)
- Map marker title
- AC/DC badge text

Translations provided for Italian (it), Spanish (es), and Catalan (ca).

* feat(i18n): localize BatteryScreen strings

Extract and translate all user-facing strings from BatteryScreen:
- Screen titles (Battery Health, Battery Detail)
- Capacity section (Usable new/now, Rated)
- Degradation section (Estimated degradation, Loss kWh/%)
- Range section (Max Range new/now, Range loss)
- Current charge section
- Range information section (Estimated, Rated, Ideal ranges)
- Estimated total capacity section
- Info dialog titles and messages with explanatory content
- Common dialog button text (Got it)

Translations provided for Italian (it), Spanish (es), and Catalan (ca).

* feat(i18n): localize StatsScreen

- Add strings for stats title, sync messages, progress indicators
- Localize drives overview and charges overview sections
- Localize all record labels (longest drive, top speed, etc.)
- Add category names (Drives, Battery, Weather & Altitude, Distances)
- Localize temperature extremes and AC/DC ratio cards
- Add dialog strings for sync logs, gap records, range records
- Add translations for Italian, Spanish, and Catalan

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(i18n): localize MileageScreen

- Add strings for mileage title, chart titles (by year/month/day)
- Localize empty states (no data, no data for year)
- Add summary labels (Total, Avg/Year, Avg/Month, # of drives)
- Localize info dialog explaining average per year calculation
- Add "Recent trips" and "Drives" section headers
- Localize all "View details" content descriptions
- Add translations for Italian, Spanish, and Catalan

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(i18n): localize SoftwareVersionsScreen

- Add strings for software title, overview card labels
- Localize date filter labels (Last 6 months, Last year, All time)
- Add update history header and empty state message
- Localize version card labels (Current version, Installed, Days, Duration)
- Add strings for longest installed badge
- Localize chart title and format strings
- Add translations for Italian, Spanish, and Catalan

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(i18n): localize common components and weather card

- Add fullscreen/exit fullscreen content descriptions
- Localize WeatherAlongTheWayCard title, loading, and empty states
- Localize weather table headers (Time, Distance, Weather)
- Localize weather condition descriptions (Clear sky, Partly cloudy, etc.)
- Localize distance markers (Start, End)
- Add translations for Italian, Spanish, and Catalan

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: add localization instructions to DEVELOPMENT.md

- Document supported languages (English, Italian, Spanish, Catalan)
- Add string naming conventions (snake_case)
- Explain how to add new strings with context comments
- Document format strings with placeholders
- Add instructions for adding new languages
- Include testing tips for translations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(i18n): enable per-app language selection (Android 13+)

- Add locale_config.xml declaring supported languages (en, it, es, ca)
- Reference locale config in AndroidManifest.xml
- Users can now change app language without changing system language

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(i18n): keep AC/DC untranslated in all languages

Technical terms AC (Alternating Current) and DC (Direct Current) are
universally understood and should not be translated to CA/CC.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* ci: add lint check for hardcoded strings

- Configure Android Lint to treat HardcodedText and SetTextI18n as errors
- Add CI workflow that runs lint on every push/PR to main
- Build job depends on lint passing
- Upload lint report as artifact on failure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: add localization rules to CLAUDE.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* ci: combine lint and build into single job

Lint requires compilation anyway, so running them in one job allows
Gradle to reuse artifacts and is more efficient than two separate jobs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: increase JVM heap to prevent OOM in CI

- Increase Gradle daemon heap from 2GB to 4GB
- Add HeapDumpOnOutOfMemoryError for debugging
- Set test JVM max heap to 1GB

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:03:30 +01:00

6.5 KiB

Development

Notes on development methodology

This project was completely vibe-coded, 100%, with the help of Claude Code and Opus 4.5. There it is, I said it, the shame is now gone.

I am/was pretty skeptical on LLM-generated code and this was born as an experiment to learn how it actually was to vibe-code something from scratch.

Turns out it's pretty awesome and easy to follow the happy path for a stock, modern Android app which just displays data consumed from a JSON REST API. I'm a DevOps guy and I have zero mobile development skills, so achieving this can be considered pretty awesome in my book. But I'm pretty sure in the eyes of a skilled Kotlin Android developer, this code might induce different feelings.

At the moment, I completely depend on CC to maintain the app, but I would like to take this opportunity as an excuse to learn more about the Android development ecosystem, beside learning how to tame an LLM agent.

Project Structure

matedroid/
├── app/src/main/java/com/matedroid/
│   ├── data/           # Data layer (API, repository, local storage)
│   ├── domain/         # Domain layer (models, use cases)
│   ├── ui/             # UI layer (screens, components, theme)
│   └── di/             # Dependency injection modules
├── gradle/             # Gradle wrapper and version catalog
├── util/               # Utility scripts
├── ASSETS.md           # Tesla car image asset documentation
└── PLAN.md             # Detailed implementation plan

Tech Stack

  • Language: Kotlin
  • UI: Jetpack Compose with Material Design 3
  • Architecture: MVVM + Clean Architecture
  • DI: Hilt
  • Networking: Retrofit + OkHttp + Moshi
  • Local Storage: DataStore
  • Charts: Vico
  • Maps: osmdroid (OpenStreetMap)

Localization (i18n)

The app supports multiple languages using Android's standard resource-based localization system. Currently supported languages:

  • English (default) - res/values/strings.xml
  • Italian - res/values-it/strings.xml
  • Spanish - res/values-es/strings.xml
  • Catalan - res/values-ca/strings.xml

Adding/Modifying Translations

  1. All user-visible strings must be in string resources - never hardcode text in Kotlin files
  2. String naming convention: Use snake_case (e.g., settings_title, drive_history)
  3. Add context comments for translators above each string:
    <!-- Dialog title when user has multiple vehicles -->
    <string name="select_vehicle">Select Vehicle</string>
    

Adding a New String

  1. Add the English string to res/values/strings.xml:

    <!-- Description of what this string is for -->
    <string name="new_feature_label">Feature Name</string>
    
  2. Add translations to all locale files:

    • res/values-it/strings.xml
    • res/values-es/strings.xml
    • res/values-ca/strings.xml
  3. Use in Kotlin code:

    import androidx.compose.ui.res.stringResource
    import com.matedroid.R
    
    Text(text = stringResource(R.string.new_feature_label))
    

Format Strings

For strings with dynamic values, use placeholders:

<!-- %d is the percentage -->
<string name="charge_limit_format">Limit: %d%%</string>

<!-- %1$s is the date, %2$d is the number of days -->
<string name="avg_year_message">Since %1$s (%2$d days ago)</string>

Usage:

stringResource(R.string.charge_limit_format, chargeLimit)
stringResource(R.string.avg_year_message, formattedDate, dayCount)

Adding a New Language

  1. Create a new folder: res/values-{language_code}/
  2. Copy res/values/strings.xml to the new folder
  3. Translate all strings, keeping the same name attributes
  4. Android will automatically use the correct language based on device settings

Testing Translations

Change your device/emulator language in Settings > System > Languages to test different locales.

Utility Scripts

util/fetch_tesla_assets.py

Python script to download Tesla car 3D renders from Tesla's compositor service. Requires uv for dependency management.

# Download all car images (Model 3 & Y, various colors/wheels)
./util/fetch_tesla_assets.py

# Preview what would be downloaded
./util/fetch_tesla_assets.py --dry-run

# Custom output directory
./util/fetch_tesla_assets.py --output-dir /path/to/assets

See ASSETS.md for detailed documentation on Tesla compositor APIs, color/wheel code mappings, and troubleshooting.

Running Tests

# Unit tests
./gradlew test

# Instrumented tests (requires emulator/device)
./gradlew connectedAndroidTest

Releasing

Releases are automated via GitHub Actions. When a release is published, the workflow builds the APK and attaches it to the release.

# 1. Update version in app/build.gradle.kts (versionCode and versionName)
# 2. Update CHANGELOG.md with release notes
# 3. Commit and push

# 4. Create a release with GitHub CLI
gh release create v0.5.0 --generate-notes

# Or create a draft release to edit notes first
gh release create v0.5.0 --generate-notes --draft

Signing Configuration (Optional)

For release signing with a custom keystore, set these repository secrets:

  • KEYSTORE_BASE64: Base64-encoded keystore file (base64 -w0 your.keystore)
  • KEYSTORE_PASSWORD: Keystore password
  • KEY_ALIAS: Key alias
  • KEY_PASSWORD: Key password

Without secrets, the APK is signed with a debug keystore (fine for sideloading, not for Play Store).

Development Workflow

  1. Start your Android emulator or connect a device
  2. Build and install: make install
  3. View logs: adb logcat | grep -i matedroid

Makefile Targets

Target Description
make build Build debug APK
make install Build and install debug APK on connected device
make run Build, install, and launch the app
make clean Clean build artifacts
make test Run unit tests

Or use Android Studio:

  1. Open the project folder
  2. Wait for Gradle sync
  3. Click Run (green play button)

Configuration

On first launch, you'll be prompted to configure your TeslamateApi connection:

  1. Server URL: Your TeslamateApi instance URL (e.g., https://teslamate-api.example.com)
  2. API Token: (Optional) If your instance requires authentication