diff --git a/SPECS.md b/SPECS.md index e2155c8..a642041 100644 --- a/SPECS.md +++ b/SPECS.md @@ -87,7 +87,6 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does * - [ ] **EVHardware's comments are partly French.** About 120 comment lines in the vendored `EVHardware/` library (mostly `EVHardware.kt`, some tests), half-translated upstream. The app, the docs and the build files are English. -- [ ] **Move hardcoded UI strings into `strings.xml`.** Many are inline in Java and layouts. ## Done @@ -117,6 +116,9 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does * INGEST_TOKEN) into git and pushed it. Commit amended and force-pushed; both values regenerated in the now gitignored `k8s/secrets.yaml` (since moved to `k8s/secrets/secrets.yaml`, outside `kubectl apply -f k8s/`). Filled-in secrets live only there from now on. +- 2026-10-18 — App 2.14.4: every UI text is in `strings.xml` (Charging, Consumption and Settings + labels, toasts, the trip-reset dialog, charging and Wi-Fi states, the daily summary, the "—" + placeholder); lint reports no hard-coded text. Upload-log entries stay in code as log text. - 2026-10-18 — Translated the rest: `mise.toml`, the comments in `app/build.gradle`, `OTA.md`'s message table (now the updater's current English messages), the Turkish messages quoted in `CHANGELOG.md` and `OTA.md` (glossed), two test messages, the ignition-state comment in diff --git a/app/build.gradle b/app/build.gradle index 3122615..53d4044 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,8 +34,8 @@ android { applicationId "com.stinnesbeck.nilsmg4" minSdk 27 targetSdk 36 - versionCode 81 - versionName "2.14.3" + versionCode 82 + versionName "2.14.4" // Nothing about the server ships in the APK: its URL and ingest token are entered in // the app's Server tab (ServerConfig), and the database is only reachable by the server. diff --git a/app/src/main/java/com/evsuite/abrp/MainActivity.java b/app/src/main/java/com/evsuite/abrp/MainActivity.java index 2e21606..e84325a 100644 --- a/app/src/main/java/com/evsuite/abrp/MainActivity.java +++ b/app/src/main/java/com/evsuite/abrp/MainActivity.java @@ -1218,9 +1218,9 @@ public class MainActivity extends AppCompatActivity { String ssid = wifi.currentSsid(); wifiSsid.setText(ssid == null ? getString(R.string.wifi_not_connected) : ssid); if (ssid != null) { - wifiState.setText("Connected ♥"); + wifiState.setText(R.string.wifi_connected); } else if (!wifi.isWifiEnabled()) { - wifiState.setText("Wi-Fi off"); + wifiState.setText(R.string.wifi_off); } // Left alone otherwise: a message the button just set says more about what is // happening than "not connected" would. @@ -1378,11 +1378,12 @@ public class MainActivity extends AppCompatActivity { selectPeriodButton(R.id.consumption_lifetime); consumptionReset.setOnClickListener(v -> { if (consumptionPeriod != ConsumptionTracker.Period.TRIP_A && consumptionPeriod != ConsumptionTracker.Period.TRIP_B) return; - String name = consumptionPeriod == ConsumptionTracker.Period.TRIP_A ? "Trip A" : "Trip B"; - new MaterialAlertDialogBuilder(this).setTitle("Reset " + name + "?") - .setMessage("This counter's recorded consumption history will be permanently deleted.") - .setNegativeButton("Cancel", null) - .setPositiveButton("Reset", (d, w) -> { + String name = getString(consumptionPeriod == ConsumptionTracker.Period.TRIP_A + ? R.string.consumption_trip_a : R.string.consumption_trip_b); + new MaterialAlertDialogBuilder(this).setTitle(getString(R.string.trip_reset_title, name)) + .setMessage(R.string.trip_reset_message) + .setNegativeButton(R.string.pair_cancel, null) + .setPositiveButton(R.string.trip_reset_confirm, (d, w) -> { consumptionTracker.reset(consumptionPeriod); refreshConsumption(); }).show(); @@ -1409,9 +1410,9 @@ public class MainActivity extends AppCompatActivity { if (!consumptionTracker.setSohPercent(value)) throw new NumberFormatException(); consumptionSohLayout.setError(null); consumptionSohInput.setText(String.format(java.util.Locale.US, "%.1f", value)); - Toast.makeText(this, "SoH saved", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.soh_saved, Toast.LENGTH_SHORT).show(); } catch (NumberFormatException e) { - consumptionSohLayout.setError("Enter a value between 1 and 100"); + consumptionSohLayout.setError(getString(R.string.soh_invalid)); } } @@ -1469,9 +1470,9 @@ public class MainActivity extends AppCompatActivity { if (!chargeSessionTracker.setPricePerKwh(value)) throw new NumberFormatException(); chargePriceLayout.setError(null); chargePriceInput.setText(String.format(java.util.Locale.US, "%.2f", value)); - Toast.makeText(this, "Charging price saved", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.price_saved, Toast.LENGTH_SHORT).show(); } catch (NumberFormatException e) { - chargePriceLayout.setError("Enter a valid €/kWh value"); + chargePriceLayout.setError(getString(R.string.price_invalid)); } } @@ -1486,9 +1487,9 @@ public class MainActivity extends AppCompatActivity { // below — which then describe the previous DC session — be read as this charge. boolean dc = ChargingState.isDcCharging( code, s.getChargePortConnected(), s.getBatteryPowerKw(), s.getSpeedKmh()); - chargeStatus.setText(dc ? "DC fast charging" - : charging ? "AC charging (not metered)" - : (session.startedAtMs > 0 ? "Last charge" : "Not charging")); + chargeStatus.setText(dc ? R.string.charge_dc + : charging ? R.string.charge_ac_not_metered + : (session.startedAtMs > 0 ? R.string.charge_last : R.string.charging_not_charging)); chargeSoc.setText(fmt(s.getSocPercent(), "%.1f %%")); chargeStartSoc.setText(fmt(session.startSocPercent, "%.1f %%")); chargeVoltage.setText(fmt(s.getBatteryVoltageV(), "%.1f V")); @@ -1529,13 +1530,12 @@ public class MainActivity extends AppCompatActivity { ConsumptionTracker.Totals day = cloudDay(selectedConsumptionDate); if (day == null) day = consumptionTracker.totalsForDay(selectedConsumptionDate); if (day.km == 0 && day.kwh == 0 && day.hours == 0 && day.soc == 0) { - consumptionDailyHistory.setText("No records for this date"); + consumptionDailyHistory.setText(R.string.consumption_no_records); } else { String average = day.km < 0.1 ? "—" : String.format(java.util.Locale.getDefault(), "%.1f kWh/100 km", day.kwh * 100d / day.km); long dayMinutes = Math.round(day.hours * 60d); - consumptionDailyHistory.setText(String.format(java.util.Locale.getDefault(), - "Distance %.1f km\nEnergy %.2f kWh\nAverage %s\nDuration %d h %02d min\nSOC change %.1f %%", + consumptionDailyHistory.setText(getString(R.string.consumption_day_summary, day.km, day.kwh, average, dayMinutes / 60, dayMinutes % 60, day.soc)); } } @@ -1598,21 +1598,21 @@ public class MainActivity extends AppCompatActivity { * The codes are the vehicle's own — see EVHardware.PROP_VENDOR_CHARGE_STATUS. An * unrecognised code is shown as itself rather than guessed at. */ - private static String chargingLabel(EnergySnapshot s) { + private String chargingLabel(EnergySnapshot s) { Integer status = s.getChargingStatus(); if (status != null) { switch (status) { - case 1: return "AC charging ♥"; - case 10: return "DC fast charging ♥"; - case 5: return "Connecting…"; - case 7: return "Plugged in, not charging"; - case 8: return "Stopped"; - case 0: return "Not charging"; - default: return "Status " + status; + case 1: return getString(R.string.charge_status_ac); + case 10: return getString(R.string.charge_status_dc); + case 5: return getString(R.string.charge_status_connecting); + case 7: return getString(R.string.charge_status_plugged); + case 8: return getString(R.string.charge_status_stopped); + case 0: return getString(R.string.charging_not_charging); + default: return getString(R.string.charge_status_code, status); } } Boolean plugged = s.getChargePortConnected(); - if (plugged != null) return plugged ? "Connected ♥" : "Not connected"; + if (plugged != null) return getString(plugged ? R.string.charge_status_connected : R.string.charge_status_unplugged); return "—"; } @@ -1652,7 +1652,7 @@ public class MainActivity extends AppCompatActivity { consumptionCloudClient.invalidateHistory(); runOnUiThread(() -> { refreshConsumption(); - Toast.makeText(this, "Consumption refreshed", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.consumption_refreshed, Toast.LENGTH_SHORT).show(); }); }, "cloud-refresh").start(); } diff --git a/app/src/main/res/layout-w1000dp/pane_settings.xml b/app/src/main/res/layout-w1000dp/pane_settings.xml index 12005cd..6e9df62 100644 --- a/app/src/main/res/layout-w1000dp/pane_settings.xml +++ b/app/src/main/res/layout-w1000dp/pane_settings.xml @@ -15,12 +15,12 @@ - + - - - + + + @@ -28,10 +28,10 @@ - + - + @@ -39,8 +39,8 @@ - - + + diff --git a/app/src/main/res/layout-w1000dp/pane_vehicle.xml b/app/src/main/res/layout-w1000dp/pane_vehicle.xml index 6357b56..24acbed 100644 --- a/app/src/main/res/layout-w1000dp/pane_vehicle.xml +++ b/app/src/main/res/layout-w1000dp/pane_vehicle.xml @@ -4,52 +4,52 @@ - - + + - + - + - + - + - + - + - + - + - + @@ -74,18 +74,18 @@ - + - + - + - + diff --git a/app/src/main/res/layout/pane_charging.xml b/app/src/main/res/layout/pane_charging.xml index 43c34d0..02e6819 100644 --- a/app/src/main/res/layout/pane_charging.xml +++ b/app/src/main/res/layout/pane_charging.xml @@ -2,23 +2,23 @@ - + - - - - - - - - - + + + + + + + + + - + - + diff --git a/app/src/main/res/layout/pane_consumption.xml b/app/src/main/res/layout/pane_consumption.xml index 3f19a5a..1d59919 100644 --- a/app/src/main/res/layout/pane_consumption.xml +++ b/app/src/main/res/layout/pane_consumption.xml @@ -3,33 +3,33 @@ - - + + - - - - - + + + + + - - - - - - + + + + + + - + - + - - - + + + diff --git a/app/src/main/res/layout/pane_settings.xml b/app/src/main/res/layout/pane_settings.xml index 943732d..978fbd8 100644 --- a/app/src/main/res/layout/pane_settings.xml +++ b/app/src/main/res/layout/pane_settings.xml @@ -14,12 +14,12 @@ - + - - - + + + @@ -27,17 +27,17 @@ - + - + - - + + diff --git a/app/src/main/res/layout/pane_vehicle.xml b/app/src/main/res/layout/pane_vehicle.xml index 5bc5918..009564c 100644 --- a/app/src/main/res/layout/pane_vehicle.xml +++ b/app/src/main/res/layout/pane_vehicle.xml @@ -4,50 +4,50 @@ - - + + - + - + - + - + - + - + - + - + - + @@ -69,18 +69,18 @@ - + - + - + - + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dfeaeef..288bf29 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -214,4 +214,69 @@ QR code for the project repository unknown Close + + + Not charging + SOC + Start SOC + Battery voltage + Battery current + Charging power + Net energy into battery + Drawn from grid (+10%) + Charging time + Total cost + CHARGING CURVE + No charging measurements yet + CONSUMPTION PERIOD + Lifetime + Last 7 days + Last 30 days + Trip A + Trip B + Distance + Net energy + Average consumption + Average speed + Duration + SOC change + Reset selected trip + Last 7/30 days, lifetime and Trip A/B are stored permanently on the device; app and OTA updates do not erase them. + DAILY LOG · LAST 4 MONTHS + Pick a date + No daily data yet + Manual SoH (%) + % + Save SoH value + This manual value is stored on the device and sent to your server with the daily totals. + kWh + Unit price + €/kWh + Save price + Connected ♥ + Wi-Fi off + Reset %1$s? + This counter\'s recorded consumption history will be permanently deleted. + Reset + SoH saved + Enter a value between 1 and 100 + Charging price saved + Enter a valid €/kWh value + DC fast charging + AC charging (not metered) + Last charge + No records for this date + Distance %1$.1f km\nEnergy %2$.2f kWh\nAverage %3$s\nDuration %4$d h %5$02d min\nSOC change %6$.1f %% + Consumption refreshed + AC charging ♥ + DC fast charging ♥ + Connecting… + Plugged in, not charging + Stopped + Status %1$d + Connected ♥ + Not connected + + — + Refresh