diff --git a/general/conftest.py b/general/conftest.py new file mode 100644 index 00000000000..8b11707a8a0 --- /dev/null +++ b/general/conftest.py @@ -0,0 +1,7 @@ +"""Settings for pytest tests""" + +import os + +# The tests are written for English messages, so they would fail in a +# translated locale, e.g., when running on a system which is not English. +os.environ["LANGUAGE"] = "C" diff --git a/general/g.region/printwindow.c b/general/g.region/printwindow.c index 865e33b5ae1..9f38b94f632 100644 --- a/general/g.region/printwindow.c +++ b/general/g.region/printwindow.c @@ -108,7 +108,9 @@ void print_window(struct Cell_head *window, int print_flag, int flat_flag, /* flag.print & flag.gprint */ if (print_flag & PRINT_REG) { - prj = G_database_projection_name(); + /* machine-readable output must not depend on the locale */ + prj = format == PLAIN ? G_database_projection_name() + : G_database_projection_name_untranslated(); if (!prj && format != JSON) prj = "** unknown **"; diff --git a/imagery/conftest.py b/imagery/conftest.py new file mode 100644 index 00000000000..8b11707a8a0 --- /dev/null +++ b/imagery/conftest.py @@ -0,0 +1,7 @@ +"""Settings for pytest tests""" + +import os + +# The tests are written for English messages, so they would fail in a +# translated locale, e.g., when running on a system which is not English. +os.environ["LANGUAGE"] = "C" diff --git a/include/grass/defs/gis.h b/include/grass/defs/gis.h index 106c9801ef4..fc9be60243a 100644 --- a/include/grass/defs/gis.h +++ b/include/grass/defs/gis.h @@ -714,11 +714,13 @@ int G_projection(void); /* proj2.c */ int G_projection_units(int); const char *G_projection_name(int); +const char *G_projection_name_untranslated(int); /* proj3.c */ const char *G_database_unit_name(int); int G_database_unit(void); const char *G_database_projection_name(void); +const char *G_database_projection_name_untranslated(void); const char *G_database_datum_name(void); const char *G_database_ellipse_name(void); double G_database_units_to_meters_factor(void); @@ -846,6 +848,7 @@ void G_trim_decimal(char *); double G_meters_to_units_factor(int); double G_meters_to_units_factor_sq(int); const char *G_get_units_name(int, int, int); +const char *G_get_units_name_untranslated(int, int, int); int G_units(const char *); int G_is_units_type_spatial(int); int G_is_units_type_temporal(int); diff --git a/lib/conftest.py b/lib/conftest.py new file mode 100644 index 00000000000..8b11707a8a0 --- /dev/null +++ b/lib/conftest.py @@ -0,0 +1,7 @@ +"""Settings for pytest tests""" + +import os + +# The tests are written for English messages, so they would fail in a +# translated locale, e.g., when running on a system which is not English. +os.environ["LANGUAGE"] = "C" diff --git a/lib/gis/proj2.c b/lib/gis/proj2.c index a971ecc545f..9ece2f1b7d0 100644 --- a/lib/gis/proj2.c +++ b/lib/gis/proj2.c @@ -45,6 +45,9 @@ int G_projection_units(int n) /*! \brief Get projection name + The name is translated, so it is meant for messages and other text for + people. Use G_projection_name_untranslated() for machine-readable output. + \param n projection code \return projection name @@ -65,3 +68,30 @@ const char *G_projection_name(int n) return NULL; } } + +/*! + \brief Get projection name in English, independent of the locale + + Same as G_projection_name(), but never translated, so the result is + suitable for machine-readable output (JSON, shell style). + + \param n projection code + + \return projection name + \return NULL on error + */ +const char *G_projection_name_untranslated(int n) +{ + switch (n) { + case PROJECTION_XY: + return "x,y"; + case PROJECTION_UTM: + return "UTM"; + case PROJECTION_LL: + return "Latitude-Longitude"; + case PROJECTION_OTHER: + return "Other Projection"; + default: + return NULL; + } +} diff --git a/lib/gis/proj3.c b/lib/gis/proj3.c index 4c1491452c4..ca52b34dce4 100644 --- a/lib/gis/proj3.c +++ b/lib/gis/proj3.c @@ -132,6 +132,33 @@ const char *G_database_projection_name(void) return name; } +/*! + \brief Get projection name for the current location in English + + Same as G_database_projection_name(), but never translated, so the result + is suitable for machine-readable output (JSON, shell style). + + \return projection name + */ +const char *G_database_projection_name_untranslated(void) +{ + int n; + const char *name; + + switch (n = G_projection()) { + case PROJECTION_XY: + case PROJECTION_UTM: + case PROJECTION_LL: + return G_projection_name_untranslated(n); + } + + name = lookup_proj("name"); + if (!name) + return "Unknown projection"; + + return name; +} + /*! \brief Conversion to meters @@ -161,7 +188,8 @@ double G_database_units_to_meters_factor(void) if (buf) sscanf(buf, "%lf", &factor); if (factor <= 0.0) { - unit = G_database_unit_name(0); + /* the table has English names, so the translated name does not match */ + unit = G_get_units_name_untranslated(G_database_unit(), 0, FALSE); for (n = 0; table[n].unit; n++) if (equal(unit, table[n].unit)) { factor = table[n].factor; diff --git a/lib/gis/units.c b/lib/gis/units.c index f3256bf9cbe..004093c6538 100644 --- a/lib/gis/units.c +++ b/lib/gis/units.c @@ -178,209 +178,236 @@ int G_is_units_type_temporal(int units) return 0; } -/*! - \brief Get localized units name - - Units codes (gis.h): - - U_METERS - - U_KILOMETERS - - U_ACRES - - U_HECTARES - - U_MILES - - U_FEET - - U_USFEET - - \param units units code - \param plural plural form if true - \param square area units if true - - \return units name - \return NULL if units not found - */ -const char *G_get_units_name(int units, int plural, int square) +static const char *units_name(int units, int plural, int square, int localize) { switch (units) { case U_UNKNOWN: if (square) return plural // GTC: localized area units - ? _("square units") + ? (localize ? _("square units") : "square units") // GTC: localized area units - : _("square unit"); + : (localize ? _("square unit") : "square unit"); else return plural // GTC: localized units - ? _("units") + ? (localize ? _("units") : "units") // GTC: localized units - : _("unit"); + : (localize ? _("unit") : "unit"); break; case U_METERS: if (square) return plural // GTC: localized area units - ? _("square meters") + ? (localize ? _("square meters") : "square meters") // GTC: localized area units - : _("square meter"); + : (localize ? _("square meter") : "square meter"); else return plural // GTC: localized units - ? _("meters") + ? (localize ? _("meters") : "meters") // GTC: localized units - : _("meter"); + : (localize ? _("meter") : "meter"); break; case U_KILOMETERS: if (square) return plural // GTC: localized area units - ? _("square kilometers") + ? (localize ? _("square kilometers") + : "square kilometers") // GTC: localized area units - : _("square kilometer"); + : (localize ? _("square kilometer") + : "square kilometer"); else return plural // GTC: localized units - ? _("kilometers") + ? (localize ? _("kilometers") : "kilometers") // GTC: localized units - : _("kilometer"); + : (localize ? _("kilometer") : "kilometer"); break; case U_ACRES: if (square) return plural // GTC: localized area units - ? _("acres") + ? (localize ? _("acres") : "acres") // GTC: localized area units - : _("acre"); + : (localize ? _("acre") : "acre"); else - return G_get_units_name(G_units(G_database_unit_name(1)), plural, - square); + return units_name(G_database_unit(), plural, square, localize); break; case U_HECTARES: if (square) return plural // GTC: localized area units - ? _("hectares") + ? (localize ? _("hectares") : "hectares") // GTC: localized area units - : _("hectare"); + : (localize ? _("hectare") : "hectare"); else - return G_get_units_name(G_units(G_database_unit_name(1)), plural, - square); + return units_name(G_database_unit(), plural, square, localize); break; case U_MILES: if (square) return plural // GTC: localized area units - ? _("square miles") + ? (localize ? _("square miles") : "square miles") // GTC: localized area units - : _("square mile"); + : (localize ? _("square mile") : "square mile"); else return plural // GTC: localized units - ? _("miles") + ? (localize ? _("miles") : "miles") // GTC: localized units - : _("mile"); + : (localize ? _("mile") : "mile"); break; case U_FEET: if (square) return plural // GTC: localized area units, do not confuse with US feet - ? _("square feet") + ? (localize ? _("square feet") : "square feet") // GTC: localized area units, do not confuse with US foot - : _("square foot"); + : (localize ? _("square foot") : "square foot"); else return plural // GTC: localized units, do not confuse with US feet - ? _("feet") + ? (localize ? _("feet") : "feet") // GTC: localized units, do not confuse with US foot - : _("foot"); + : (localize ? _("foot") : "foot"); break; case U_USFEET: if (square) return plural // GTC: localized area units, do not confuse with feet - ? _("square US feet") + ? (localize ? _("square US feet") : "square US feet") // GTC: localized area units, do not confuse with foot - : _("square US foot"); + : (localize ? _("square US foot") : "square US foot"); else return plural // GTC: localized units, do not confuse with feet - ? _("US feet") + ? (localize ? _("US feet") : "US feet") // GTC: localized units, do not confuse with foot - : _("US foot"); + : (localize ? _("US foot") : "US foot"); break; case U_DEGREES: if (square) return plural // GTC: localized area units - ? _("square degrees") + ? (localize ? _("square degrees") : "square degrees") // GTC: localized area units - : _("square degree"); + : (localize ? _("square degree") : "square degree"); else return plural // GTC: localized units - ? _("degrees") + ? (localize ? _("degrees") : "degrees") // GTC: localized units - : _("degree"); + : (localize ? _("degree") : "degree"); break; case U_YEARS: return plural // GTC: localized time units - ? _("years") + ? (localize ? _("years") : "years") // GTC: localized time units - : _("year"); + : (localize ? _("year") : "year"); break; case U_MONTHS: return plural // GTC: localized time units - ? _("months") + ? (localize ? _("months") : "months") // GTC: localized time units - : _("month"); + : (localize ? _("month") : "month"); break; case U_DAYS: return plural // GTC: localized time units - ? _("days") + ? (localize ? _("days") : "days") // GTC: localized time units - : _("day"); + : (localize ? _("day") : "day"); break; case U_HOURS: return plural // GTC: localized time units - ? _("hours") + ? (localize ? _("hours") : "hours") // GTC: localized time units - : _("hour"); + : (localize ? _("hour") : "hour"); break; case U_MINUTES: return plural // GTC: localized time units - ? _("minutes") + ? (localize ? _("minutes") : "minutes") // GTC: localized time units - : _("minute"); + : (localize ? _("minute") : "minute"); break; case U_SECONDS: return plural // GTC: localized time units - ? _("seconds") + ? (localize ? _("seconds") : "seconds") // GTC: localized time units - : _("second"); + : (localize ? _("second") : "second"); break; } return NULL; } +/*! + \brief Get localized units name + + Units codes (gis.h): + - U_METERS + - U_KILOMETERS + - U_ACRES + - U_HECTARES + - U_MILES + - U_FEET + - U_USFEET + + The name is translated, so it is meant for messages and other text for + people. Use G_get_units_name_untranslated() for machine-readable output. + + \param units units code + \param plural plural form if true + \param square area units if true + + \return units name + \return NULL if units not found + */ +const char *G_get_units_name(int units, int plural, int square) +{ + return units_name(units, plural, square, 1); +} + +/*! + \brief Get units name in English, independent of the locale + + Same as G_get_units_name(), but never translated, so the result is suitable + for machine-readable output (JSON, shell style) and for comparison with + names which G_units() recognizes. + + \param units units code + \param plural plural form if true + \param square area units if true + + \return units name + \return NULL if units not found + */ +const char *G_get_units_name_untranslated(int units, int plural, int square) +{ + return units_name(units, plural, square, 0); +} + /*! \brief Get units code by name @@ -404,7 +431,7 @@ const char *G_get_units_name(int units, int plural, int square) int G_units(const char *units_name) { if (units_name == NULL) { - return G_units(G_database_unit_name(1)); + return G_database_unit(); } if (strcasecmp(units_name, "meter") == 0 || diff --git a/lib/raster3d/headerinfo.c b/lib/raster3d/headerinfo.c index d89f9ec372d..2a8729488c5 100644 --- a/lib/raster3d/headerinfo.c +++ b/lib/raster3d/headerinfo.c @@ -239,6 +239,8 @@ int Rast3d_get_vertical_unit2(RASTER3D_Map *map) * \brief * * Return the name of the unit of map. Units are defined in gis.h. + * The name is in English, whatever the locale, because it is used in + * machine-readable output and in files. * * Vertical units may have temporal type * @@ -247,7 +249,7 @@ int Rast3d_get_vertical_unit2(RASTER3D_Map *map) */ const char *Rast3d_get_vertical_unit(RASTER3D_Map *map) { - return G_get_units_name(map->vertical_unit, 1, 0); + return G_get_units_name_untranslated(map->vertical_unit, 1, 0); } /*---------------------------------------------------------------------------*/ diff --git a/misc/m.measure/main.c b/misc/m.measure/main.c index 32851e61b84..4db17f1d7c6 100644 --- a/misc/m.measure/main.c +++ b/misc/m.measure/main.c @@ -37,6 +37,7 @@ int main(int argc, char **argv) double length, area, f, sq_f; int i, npoints; const char *units_name, *sq_units_name; + int unit_id; enum OutputFormat format; G_JSON_Value *root_value = NULL; G_JSON_Object *root_object = NULL; @@ -114,14 +115,18 @@ int main(int argc, char **argv) y[i] = atof(coords->answers[2 * i + 1]); } - /* determine units */ - if (G_projection() == PROJECTION_LL && !units->answer) { - units_name = G_get_units_name(U_METERS, 1, 0); - sq_units_name = G_get_units_name(U_METERS, 1, 1); + /* determine units; only the plain output is for people, so only it gets + * translated names */ + unit_id = (G_projection() == PROJECTION_LL && !units->answer) + ? U_METERS + : G_units(units->answer); + if (format == PLAIN) { + units_name = G_get_units_name(unit_id, 1, 0); + sq_units_name = G_get_units_name(unit_id, 1, 1); } else { - units_name = G_get_units_name(G_units(units->answer), 1, 0); - sq_units_name = G_get_units_name(G_units(units->answer), 1, 1); + units_name = G_get_units_name_untranslated(unit_id, 1, 0); + sq_units_name = G_get_units_name_untranslated(unit_id, 1, 1); } f = G_meters_to_units_factor(G_units(units->answer)); sq_f = G_meters_to_units_factor_sq(G_units(units->answer)); diff --git a/python/conftest.py b/python/conftest.py new file mode 100644 index 00000000000..8b11707a8a0 --- /dev/null +++ b/python/conftest.py @@ -0,0 +1,7 @@ +"""Settings for pytest tests""" + +import os + +# The tests are written for English messages, so they would fail in a +# translated locale, e.g., when running on a system which is not English. +os.environ["LANGUAGE"] = "C" diff --git a/python/grass/gunittest/invoker.py b/python/grass/gunittest/invoker.py index 7805902c7bd..712400e722c 100644 --- a/python/grass/gunittest/invoker.py +++ b/python/grass/gunittest/invoker.py @@ -166,6 +166,8 @@ def _run_test_module( env["GISRC"] = gisrc # percentage in plain format is 0...10...20... ...100 env["GRASS_MESSAGE_FORMAT"] = "plain" + # tests check English messages, whatever the locale of the caller + env["LANGUAGE"] = "C" stdout_path = os.path.join(cwd, "stdout.txt") stderr_path = os.path.join(cwd, "stderr.txt") diff --git a/python/grass/gunittest/main.py b/python/grass/gunittest/main.py index f8458ec93d1..83986a88d92 100644 --- a/python/grass/gunittest/main.py +++ b/python/grass/gunittest/main.py @@ -107,6 +107,10 @@ def test(): # pass # TODO: add some message somewhere + # tests check English messages, whatever the locale of the caller; the + # invoker sets this too, but a test file can also be run directly + os.environ["LANGUAGE"] = "C" + # TODO: enable passing omit to exclude also gunittest or nothing program = GrassTestProgram( module="__main__", exit_at_end=False, grass_location="all" diff --git a/raster/conftest.py b/raster/conftest.py index 88b6d553686..e07a0427945 100644 --- a/raster/conftest.py +++ b/raster/conftest.py @@ -12,6 +12,11 @@ from grass.experimental import TemporaryMapsetSession +# The tests are written for English messages, so they would fail in a +# translated locale, e.g., when running on a system which is not English. +os.environ["LANGUAGE"] = "C" + + @pytest.fixture(scope="module") def session_with_raster_for_module(tmp_path_factory): """Active session with CRS and raster data""" diff --git a/scripts/conftest.py b/scripts/conftest.py index d96ac31ee97..5e241ee14c7 100644 --- a/scripts/conftest.py +++ b/scripts/conftest.py @@ -14,6 +14,11 @@ from grass.experimental import TemporaryMapsetSession +# The tests are written for English messages, so they would fail in a +# translated locale, e.g., when running on a system which is not English. +os.environ["LANGUAGE"] = "C" + + @pytest.fixture(scope="module") def monkeypatch_module() -> Generator[pytest.MonkeyPatch]: """Yield a monkeypatch context, through a module-scoped fixture""" diff --git a/temporal/conftest.py b/temporal/conftest.py new file mode 100644 index 00000000000..8b11707a8a0 --- /dev/null +++ b/temporal/conftest.py @@ -0,0 +1,7 @@ +"""Settings for pytest tests""" + +import os + +# The tests are written for English messages, so they would fail in a +# translated locale, e.g., when running on a system which is not English. +os.environ["LANGUAGE"] = "C" diff --git a/vector/conftest.py b/vector/conftest.py index c183dd0be08..1dffba74493 100644 --- a/vector/conftest.py +++ b/vector/conftest.py @@ -6,6 +6,11 @@ import grass.script as gs +# The tests are written for English messages, so they would fail in a +# translated locale, e.g., when running on a system which is not English. +os.environ["LANGUAGE"] = "C" + + @pytest.fixture def xy_dataset_session(tmp_path): """Creates a session with XY project"""