mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-11-03 11:32:37 +00:00
- Extracted `modules/alert_format.py` as the single NWS alert formatter, replacing four copies of the event-type abbreviation table and two of the time compactor. `!wx alerts` and the proactive `WeatherService` broadcasts now localize from one code path, so a Russian bot no longer answers `!wx alerts` in English while its proactive alerts are Russian.
- Stopped leaking translation key paths into mesh broadcasts. `Translator.translate` returns the dotted key when a lookup misses in both the locale and the English fallback, which is right for development but reached the air in production: an unclassifiable NWS title rendered as `⚪Hazardous services.weather_service.event_types.Unknown`, an unmapped WMO code as `services.weather_service.weather_descriptions.4`, and an oddly-cased `wind_speed_unit` as `services.weather_service.wind_speed_units.KMH`. `alert_format.translate_or()` carries an English default at each site, and `WeatherService` now normalizes and validates its three `[Weather]` unit settings the way `GlobalWxCommand` already did.
- Fixed alert expiry rendering in every locale. The formatter rendered a timestamp to a string and re-parsed its own output with `(\d+)(AM|PM)` against a hardcoded English month list, so translated months took the wrong branch and truncated mid-string. Times now carry parsed parts and render through a per-locale `common.alerts.time_12h` template — the space before AM/PM was correct (Russian writes "6 дня", not "6дня"); the downstream regex was the bug.
- Restored month abbreviation. `_compact_time` iterated over abbreviations and replaced them in the string instead of mapping full names, so English stopped shortening "June 28" and Russian replaced the "Jun" inside "June", leaving a stray Latin "e" (`июнe 28`). Reuses the existing `common.date_time.month_abbreviations` rather than the duplicate `services.weather_service.months` block.
- Made `!gwx` display units follow `[Weather]` config instead of the response language. Visibility switched on `base_language != 'en'`, so `language = ru` with the default `temperature_unit = fahrenheit` printed Fahrenheit beside kilometers, and `en-GB` was forced to miles. Pressure is a locale convention rather than a metric/imperial split, so each catalog names its own via `commands.gwx.pressure_unit` — previously every non-English locale inherited mmHg from the English catalog, whose `pressure_mmhg` string contained Russian text, giving German and French users Cyrillic pressure units.
- Let localized `H`/`L` labels reach a standard install. `config.ini.example` shipped the three `temperature_*_format` keys uncommented with literal `H:`/`L:`, and a config value always beats the new locale-aware default, so a Russian bot built from the documented example still rendered `H:47°C L:33°C`. The example now uses the `{high_label}`/`{low_label}` placeholders, which were documented in the docstring but not in the file.
- Routed high/low labels through the reply's translator. `_format_high_low` passed `bot.translator`, so with `auto_detect_language` on, an English-default bot answering a Russian sender localized the rest of the line but not `H:`/`L:`. Added `BaseCommand.response_translator` for this, replacing `wx_international`'s reach into the private `_response_translator` ContextVar.
- Fixed a byte-budget overrun in `!gwx`. The guard on the extra conditions block compared a character count against a byte-derived budget while the rest of the function used `_count_display_width`; Cyrillic is two bytes per character, so the block was appended after the budget was spent.
- Reverted nine `commands.gwx` English rewordings that were not localization work, including the configuration hint in `mqtt_weather_no_subscriber` — dropping it left a mis-configured operator with no pointer to the two keys they need.
- Fixed the Russian `visibility` string, which said "км" on the miles key — the same locale/config conflation as the code bug, in the data. Shortened the Russian event-type abbreviations, which were full words consuming a quarter of the 130-byte budget at two bytes per character.
- Added `commands.wx.hourly_not_available`, missing from every catalog so `!wx hourly` printed the raw key path. Predates this branch; found while auditing every translation key the weather modules reference.
- Moved alert strings to `common.alerts.*` and wind directions to `common.wind_directions.*`, since a command and a service both read them.
95 lines
3.6 KiB
Python
95 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Unit tests for !gwx display units — driven by [Weather] config, not by locale."""
|
|
|
|
import pytest
|
|
|
|
from modules.commands.alternatives.wx_international import (
|
|
HPA_TO_MMHG,
|
|
MI_TO_KM,
|
|
VISIBILITY_CAP_KM,
|
|
VISIBILITY_CAP_MI,
|
|
GlobalWxCommand,
|
|
)
|
|
from modules.i18n import Translator
|
|
|
|
|
|
@pytest.fixture
|
|
def gwx_bot(mock_bot):
|
|
mock_bot.config.add_section("Weather")
|
|
mock_bot.config.set("Weather", "weather_provider", "openmeteo")
|
|
mock_bot.config.set("Weather", "default_country", "US")
|
|
return mock_bot
|
|
|
|
|
|
def build(gwx_bot, *, temperature_unit="fahrenheit", language="en"):
|
|
gwx_bot.config.set("Weather", "temperature_unit", temperature_unit)
|
|
gwx_bot.translator = Translator(language)
|
|
return GlobalWxCommand(gwx_bot)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestMetricDistance:
|
|
def test_fahrenheit_is_imperial(self, gwx_bot):
|
|
assert build(gwx_bot, temperature_unit="fahrenheit").metric_distance is False
|
|
|
|
def test_celsius_is_metric(self, gwx_bot):
|
|
assert build(gwx_bot, temperature_unit="celsius").metric_distance is True
|
|
|
|
def test_language_does_not_decide(self, gwx_bot):
|
|
# A Fahrenheit bot answering in Russian must not print kilometers next
|
|
# to Fahrenheit temperatures.
|
|
cmd = build(gwx_bot, temperature_unit="fahrenheit", language="ru")
|
|
assert cmd.metric_distance is False
|
|
|
|
def test_en_gb_with_celsius_gets_metric(self, gwx_bot):
|
|
cmd = build(gwx_bot, temperature_unit="celsius", language="en-GB")
|
|
assert cmd.metric_distance is True
|
|
|
|
def test_invalid_unit_falls_back_to_imperial(self, gwx_bot):
|
|
cmd = build(gwx_bot, temperature_unit="kelvin")
|
|
assert cmd.temperature_unit == "fahrenheit"
|
|
assert cmd.metric_distance is False
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestVisibilityStrings:
|
|
def test_imperial_string_says_miles_in_english(self):
|
|
assert Translator("en").translate("commands.gwx.visibility", value=12) == "👁️12mi"
|
|
|
|
def test_imperial_string_says_miles_in_russian(self):
|
|
# This key means miles in every catalog now that the unit comes from
|
|
# config; it used to say "км" in ru.
|
|
out = Translator("ru").translate("commands.gwx.visibility", value=12)
|
|
assert "км" not in out
|
|
assert "миль" in out
|
|
|
|
def test_metric_string_says_km(self):
|
|
assert Translator("en").translate("commands.gwx.visibility_km", value=12) == "👁️12km"
|
|
|
|
def test_caps_are_equivalent(self):
|
|
assert pytest.approx(VISIBILITY_CAP_KM, abs=0.3) == VISIBILITY_CAP_MI * MI_TO_KM
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPressureUnitConvention:
|
|
@pytest.mark.parametrize("language", ["en", "en-GB", "de", "fr", "fr-CA",
|
|
"es", "nl", "pl", "pt", "pt-BR"])
|
|
def test_hpa_locales(self, language):
|
|
assert Translator(language).translate("commands.gwx.pressure_unit") == "hpa"
|
|
|
|
def test_russian_uses_mmhg(self):
|
|
assert Translator("ru").translate("commands.gwx.pressure_unit") == "mmhg"
|
|
|
|
@pytest.mark.parametrize("language", ["en", "de", "fr", "es", "nl", "pl", "pt"])
|
|
def test_hpa_locales_render_no_cyrillic(self, language):
|
|
out = Translator(language).translate("commands.gwx.pressure", value=1013)
|
|
assert not any("Ѐ" <= ch <= "ӿ" for ch in out), out
|
|
|
|
def test_english_mmhg_string_is_english(self):
|
|
# The en catalog held "мм рт. ст.", which every non-ru locale inherited.
|
|
out = Translator("en").translate("commands.gwx.pressure_mmhg", value=760)
|
|
assert out == "📊760mmHg"
|
|
|
|
def test_conversion_factor(self):
|
|
assert round(1013 * HPA_TO_MMHG) == 760
|