From: Anton Khorev Date: Tue, 18 Mar 2025 00:48:52 +0000 (+0300) Subject: Merge branch 'pull/5811' X-Git-Tag: live~82 X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/e0434270600e46e888f8ea6d7afbc6e706a278dd?hp=7d4b705f4d9332918db0657bd8ed5d905427ab53 Merge branch 'pull/5811' --- diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8d0e9f615..4cf099637 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -27,7 +27,7 @@ jobs: - name: Prepare Database run: | docker compose run --rm web bundle exec rails db:migrate - docker compose run --rm web bundle exec rails i18n:js:export + docker compose run --rm web bundle exec i18n export docker compose run --rm web bundle exec rails assets:precompile docker compose run --rm web osmosis --rx docker/null-island.osm.xml --wd host=db database=openstreetmap user=openstreetmap password=openstreetmap validateSchemaVersion=no - name: Test Basic Website diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 841155bf6..d8a76bcb1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,10 +48,10 @@ jobs: bundle exec rails db:migrate sed -f script/normalise-structure db/structure.sql > db/structure.actual diff -uw db/structure.expected db/structure.actual - - name: Export javascript strings - run: bundle exec rails i18n:js:export - name: Install node modules run: bundle exec bin/yarn install + - name: Export javascript strings + run: bundle exec i18n export - name: Compile assets run: bundle exec rails assets:precompile - name: Create tmp/pids directory diff --git a/.gitignore b/.gitignore index 380a88047..68ea67190 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ .ruby-gemset .ruby-version .vagrant -app/assets/javascripts/i18n +app/assets/javascripts/i18n/*.js config/credentials.yml.enc config/master.key config/environments/*.local.yml @@ -14,6 +14,7 @@ config/settings.local.yml config/settings/*.local.yml coverage doc +i18n/data log node_modules public/assets diff --git a/CONFIGURE.md b/CONFIGURE.md index 29d1daad8..815cf92f6 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -143,5 +143,5 @@ If you want to deploy `openstreetmap-website` for production use, you'll need to * It's not recommended to use `rails server` in production. Our recommended approach is to use [Phusion Passenger](https://www.phusionpassenger.com/). Instructions are available for [setting it up with most web servers](https://www.phusionpassenger.com/documentation_and_support#documentation). * Passenger will, by design, use the Production environment and therefore the production database - make sure it contains the appropriate data and user accounts. * The included version of the map call is quite slow and eats a lot of memory. You should consider using [CGIMap](https://github.com/zerebubuth/openstreetmap-cgimap) instead. -* Make sure you generate the i18n files and precompile the production assets: `RAILS_ENV=production rails i18n:js:export assets:precompile` +* Make sure you generate the i18n files and precompile the production assets: `RAILS_ENV=production bundle exec i18n export; bundle exec rails assets:precompile` * Make sure the web server user as well as the rails user can read, write and create directories in `tmp/`. diff --git a/Gemfile b/Gemfile index ad6b3b3ef..bcfafd3b3 100644 --- a/Gemfile +++ b/Gemfile @@ -58,7 +58,7 @@ gem "delayed_job_active_record" gem "dry-validation" gem "frozen_record" gem "http_accept_language", "~> 2.1.1" -gem "i18n-js", "~> 3.9.2" +gem "i18n-js", "~> 4.2.3" gem "openstreetmap-deadlock_retry", ">= 1.3.1", :require => "deadlock_retry" gem "rack-cors" gem "rails-i18n", "~> 7.0.0" diff --git a/Gemfile.lock b/Gemfile.lock index 54daa5bfe..021ed2048 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -288,6 +288,7 @@ GEM git (1.19.1) addressable (~> 2.8) rchardet (~> 1.8) + glob (0.4.1) globalid (1.2.1) activesupport (>= 6.1) google-protobuf (3.25.6) @@ -299,8 +300,9 @@ GEM http_accept_language (2.1.1) i18n (1.14.7) concurrent-ruby (~> 1.0) - i18n-js (3.9.2) - i18n (>= 0.6.6) + i18n-js (4.2.3) + glob (>= 0.4.0) + i18n i18n-tasks (1.0.15) activesupport (>= 4.0.2) ast (>= 2.1.0) @@ -719,7 +721,7 @@ DEPENDENCIES gd2-ffij (>= 0.4.0) htmlentities http_accept_language (~> 2.1.1) - i18n-js (~> 3.9.2) + i18n-js (~> 4.2.3) i18n-tasks image_optim_rails image_processing diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 4991e82fb..54a2cb3f6 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -19,13 +19,13 @@ const application_data = $("head").data(); const locale = application_data.locale; - I18n.default_locale = OSM.DEFAULT_LOCALE; - I18n.locale = locale; + OSM.i18n.defaultLocale = OSM.DEFAULT_LOCALE; + OSM.i18n.locale = application_data.locale; // '-' are replaced with '_' in https://github.com/eemeli/make-plural/tree/main/packages/plurals const pluralizer = plurals[locale.replace(/\W+/g, "_")] || plurals[locale.split("-")[0]]; if (pluralizer) { - I18n.pluralization[locale] = (count) => [pluralizer(count), "other"]; + OSM.i18n.pluralization.register(locale, (_, count) => [pluralizer(count), "other"]); } OSM.preferred_editor = application_data.preferredEditor; @@ -197,5 +197,5 @@ $(function () { }); $("#edit_tab") - .attr("title", I18n.t("javascripts.site.edit_disabled_tooltip")); + .attr("title", OSM.i18n.t("javascripts.site.edit_disabled_tooltip")); }); diff --git a/app/assets/javascripts/diary_entry.js b/app/assets/javascripts/diary_entry.js index a87eb35f1..bfc3fc012 100644 --- a/app/assets/javascripts/diary_entry.js +++ b/app/assets/javascripts/diary_entry.js @@ -12,7 +12,7 @@ $(function () { } marker = L.marker(e.latlng, { icon: OSM.getUserIcon() }).addTo(map) - .bindPopup(I18n.t("diary_entries.edit.marker_text")); + .bindPopup(OSM.i18n.t("diary_entries.edit.marker_text")); } $("#usemap").click(function (e) { @@ -37,7 +37,7 @@ $(function () { if ($("#latitude").val() && $("#longitude").val()) { marker = L.marker(centre, { icon: OSM.getUserIcon() }).addTo(map) - .bindPopup(I18n.t("diary_entries.edit.marker_text")); + .bindPopup(OSM.i18n.t("diary_entries.edit.marker_text")); } map.on("click", setLocation); diff --git a/app/assets/javascripts/edit/id.js.erb b/app/assets/javascripts/edit/id.js.erb index b85dbeddb..7f111b41e 100644 --- a/app/assets/javascripts/edit/id.js.erb +++ b/app/assets/javascripts/edit/id.js.erb @@ -4,7 +4,7 @@ $(function () { if (!idData.configured) { // eslint-disable-next-line no-alert - alert(I18n.t("site.edit.id_not_configured")); + alert(OSM.i18n.t("site.edit.id_not_configured")); return; } diff --git a/app/assets/javascripts/embed.js.erb b/app/assets/javascripts/embed.js.erb index a3d26ff7d..c436e342b 100644 --- a/app/assets/javascripts/embed.js.erb +++ b/app/assets/javascripts/embed.js.erb @@ -7,13 +7,13 @@ //= require i18n/embed if (navigator.languages) { - I18n.locale = navigator.languages[0]; + OSM.i18n.locale = navigator.languages[0]; } else if (navigator.language) { - I18n.locale = navigator.language; + OSM.i18n.locale = navigator.language; } -I18n.default_locale = <%= I18n.default_locale.to_json %>; -I18n.fallbacks = true; +OSM.i18n.defaultLocale = <%= I18n.default_locale.to_json %>; +OSM.i18n.enableFallback = true; window.onload = function () { const args = Object.fromEntries(new URLSearchParams(location.search)); @@ -56,7 +56,7 @@ window.onload = function () { L.Control.OSMReportAProblem = L.Control.Attribution.extend({ options: { position: "bottomright", - prefix: `${I18n.t("javascripts.embed.report_problem")}` + prefix: `${OSM.i18n.t("javascripts.embed.report_problem")}` }, onAdd: function (map) { diff --git a/app/assets/javascripts/heatmap.js b/app/assets/javascripts/heatmap.js index 09013ae5a..723fb0017 100644 --- a/app/assets/javascripts/heatmap.js +++ b/app/assets/javascripts/heatmap.js @@ -16,7 +16,7 @@ document.addEventListener("DOMContentLoaded", () => { const colorScheme = document.documentElement.getAttribute("data-bs-theme") ?? "auto"; const rangeColors = ["#14432a", "#166b34", "#37a446", "#4dd05a"]; const startDate = new Date(Date.now() - (365 * 24 * 60 * 60 * 1000)); - const monthNames = I18n.t("date.abbr_month_names"); + const monthNames = OSM.i18n.t("date.abbr_month_names"); const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); @@ -88,13 +88,13 @@ document.addEventListener("DOMContentLoaded", () => { } function getTooltipText(date, value) { - const localizedDate = I18n.l("date.formats.long", date); + const localizedDate = OSM.i18n.l("date.formats.long", date); if (value > 0) { - return I18n.t("javascripts.heatmap.tooltip.contributions", { count: value, date: localizedDate }); + return OSM.i18n.t("javascripts.heatmap.tooltip.contributions", { count: value, date: localizedDate }); } - return I18n.t("javascripts.heatmap.tooltip.no_contributions", { date: localizedDate }); + return OSM.i18n.t("javascripts.heatmap.tooltip.no_contributions", { date: localizedDate }); } function getTheme() { diff --git a/app/assets/javascripts/i18n.js b/app/assets/javascripts/i18n.js new file mode 100644 index 000000000..05c80c623 --- /dev/null +++ b/app/assets/javascripts/i18n.js @@ -0,0 +1,7 @@ +//= require i18n-js/dist/browser/index.js + +if (typeof OSM === "undefined") { + OSM = {}; +} + +OSM.i18n = new I18n.I18n(); diff --git a/app/assets/javascripts/i18n/.gitkeep b/app/assets/javascripts/i18n/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 9220e1205..3924316c1 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -245,7 +245,7 @@ $(function () { }) .catch(() => { // eslint-disable-next-line no-alert - alert(I18n.t("site.index.remote_failed")); + alert(OSM.i18n.t("site.index.remote_failed")); }); function sendRemoteEditCommand(url) { @@ -266,7 +266,7 @@ $(function () { .removeAttr("title") .tooltip({ placement: "bottom", - title: I18n.t("javascripts.edit_help") + title: OSM.i18n.t("javascripts.edit_help") }) .tooltip("show"); @@ -280,7 +280,7 @@ $(function () { page.pushstate = page.popstate = function () { map.setSidebarOverlaid(true); - document.title = I18n.t("layouts.project_name.title"); + document.title = OSM.i18n.t("layouts.project_name.title"); }; page.load = function () { diff --git a/app/assets/javascripts/index/contextmenu.js b/app/assets/javascripts/index/contextmenu.js index 6f85a5252..4c157e6e0 100644 --- a/app/assets/javascripts/index/contextmenu.js +++ b/app/assets/javascripts/index/contextmenu.js @@ -1,6 +1,6 @@ OSM.initializeContextMenu = function (map) { map.contextmenu.addItem({ - text: I18n.t("javascripts.context.directions_from"), + text: OSM.i18n.t("javascripts.context.directions_from"), callback: function directionsFromHere(e) { const latlng = OSM.cropLocation(e.latlng, map.getZoom()); @@ -12,7 +12,7 @@ OSM.initializeContextMenu = function (map) { }); map.contextmenu.addItem({ - text: I18n.t("javascripts.context.directions_to"), + text: OSM.i18n.t("javascripts.context.directions_to"), callback: function directionsToHere(e) { const latlng = OSM.cropLocation(e.latlng, map.getZoom()); @@ -24,7 +24,7 @@ OSM.initializeContextMenu = function (map) { }); map.contextmenu.addItem({ - text: I18n.t("javascripts.context.add_note"), + text: OSM.i18n.t("javascripts.context.add_note"), callback: function addNoteHere(e) { const [lat, lon] = OSM.cropLocation(e.latlng, map.getZoom()); @@ -33,7 +33,7 @@ OSM.initializeContextMenu = function (map) { }); map.contextmenu.addItem({ - text: I18n.t("javascripts.context.show_address"), + text: OSM.i18n.t("javascripts.context.show_address"), callback: function describeLocation(e) { const [lat, lon] = OSM.cropLocation(e.latlng, map.getZoom()); @@ -42,7 +42,7 @@ OSM.initializeContextMenu = function (map) { }); map.contextmenu.addItem({ - text: I18n.t("javascripts.context.query_features"), + text: OSM.i18n.t("javascripts.context.query_features"), callback: function queryFeatures(e) { const [lat, lon] = OSM.cropLocation(e.latlng, map.getZoom()); @@ -51,7 +51,7 @@ OSM.initializeContextMenu = function (map) { }); map.contextmenu.addItem({ - text: I18n.t("javascripts.context.centre_map"), + text: OSM.i18n.t("javascripts.context.centre_map"), callback: function centreMap(e) { map.panTo(e.latlng); } diff --git a/app/assets/javascripts/index/directions-endpoint.js b/app/assets/javascripts/index/directions-endpoint.js index e3f6afa46..a4b3a928b 100644 --- a/app/assets/javascripts/index/directions-endpoint.js +++ b/app/assets/javascripts/index/directions-endpoint.js @@ -117,7 +117,7 @@ OSM.DirectionsEndpoint = function Endpoint(map, input, iconUrl, dragCallback, ch if (json.length === 0) { input.addClass("is-invalid"); // eslint-disable-next-line no-alert - alert(I18n.t("javascripts.directions.errors.no_place", { place: endpoint.value })); + alert(OSM.i18n.t("javascripts.directions.errors.no_place", { place: endpoint.value })); return; } diff --git a/app/assets/javascripts/index/directions.js b/app/assets/javascripts/index/directions.js index d2741d02b..057933453 100644 --- a/app/assets/javascripts/index/directions.js +++ b/app/assets/javascripts/index/directions.js @@ -72,11 +72,11 @@ OSM.Directions = function (map) { function formatTotalDistance(m) { if (m < 1000) { - return I18n.t("javascripts.directions.distance_m", { distance: Math.round(m) }); + return OSM.i18n.t("javascripts.directions.distance_m", { distance: Math.round(m) }); } else if (m < 10000) { - return I18n.t("javascripts.directions.distance_km", { distance: (m / 1000.0).toFixed(1) }); + return OSM.i18n.t("javascripts.directions.distance_km", { distance: (m / 1000.0).toFixed(1) }); } else { - return I18n.t("javascripts.directions.distance_km", { distance: Math.round(m / 1000) }); + return OSM.i18n.t("javascripts.directions.distance_km", { distance: Math.round(m / 1000) }); } } @@ -84,18 +84,18 @@ OSM.Directions = function (map) { if (m < 5) { return ""; } else if (m < 200) { - return I18n.t("javascripts.directions.distance_m", { distance: String(Math.round(m / 10) * 10) }); + return OSM.i18n.t("javascripts.directions.distance_m", { distance: String(Math.round(m / 10) * 10) }); } else if (m < 1500) { - return I18n.t("javascripts.directions.distance_m", { distance: String(Math.round(m / 100) * 100) }); + return OSM.i18n.t("javascripts.directions.distance_m", { distance: String(Math.round(m / 100) * 100) }); } else if (m < 5000) { - return I18n.t("javascripts.directions.distance_km", { distance: String(Math.round(m / 100) / 10) }); + return OSM.i18n.t("javascripts.directions.distance_km", { distance: String(Math.round(m / 100) / 10) }); } else { - return I18n.t("javascripts.directions.distance_km", { distance: String(Math.round(m / 1000)) }); + return OSM.i18n.t("javascripts.directions.distance_km", { distance: String(Math.round(m / 1000)) }); } } function formatHeight(m) { - return I18n.t("javascripts.directions.distance_m", { distance: Math.round(m) }); + return OSM.i18n.t("javascripts.directions.distance_m", { distance: Math.round(m) }); } function formatTime(s) { @@ -164,13 +164,13 @@ OSM.Directions = function (map) { } const distanceText = $("

").append( - I18n.t("javascripts.directions.distance") + ": " + formatTotalDistance(route.distance) + ". " + - I18n.t("javascripts.directions.time") + ": " + formatTime(route.time) + "."); + OSM.i18n.t("javascripts.directions.distance") + ": " + formatTotalDistance(route.distance) + ". " + + OSM.i18n.t("javascripts.directions.time") + ": " + formatTime(route.time) + "."); if (typeof route.ascend !== "undefined" && typeof route.descend !== "undefined") { distanceText.append( $("
"), - I18n.t("javascripts.directions.ascend") + ": " + formatHeight(route.ascend) + ". " + - I18n.t("javascripts.directions.descend") + ": " + formatHeight(route.descend) + "."); + OSM.i18n.t("javascripts.directions.ascend") + ": " + formatHeight(route.ascend) + ". " + + OSM.i18n.t("javascripts.directions.descend") + ": " + formatHeight(route.descend) + "."); } const turnByTurnTable = $("") @@ -219,18 +219,18 @@ OSM.Directions = function (map) { downloadURL = URL.createObjectURL(blob); $("#directions_content").append(`

${ - I18n.t("javascripts.directions.download") + OSM.i18n.t("javascripts.directions.download") }

`); $("#directions_content").append("

" + - I18n.t("javascripts.directions.instructions.courtesy", { link: chosenEngine.creditline }) + + OSM.i18n.t("javascripts.directions.instructions.courtesy", { link: chosenEngine.creditline }) + "

"); }).catch(function () { map.removeLayer(polyline); if (reportErrors) { - $("#directions_content").html("
" + I18n.t("javascripts.directions.errors.no_route") + "
"); + $("#directions_content").html("
" + OSM.i18n.t("javascripts.directions.errors.no_route") + "
"); } }).finally(function () { controller = null; diff --git a/app/assets/javascripts/index/directions/fossgis_osrm.js b/app/assets/javascripts/index/directions/fossgis_osrm.js index 2fb835453..b2586e68e 100644 --- a/app/assets/javascripts/index/directions/fossgis_osrm.js +++ b/app/assets/javascripts/index/directions/fossgis_osrm.js @@ -109,21 +109,21 @@ } else if (step.ref) { name = "" + step.ref + ""; } else { - name = I18n.t(instrPrefix + "unnamed"); + name = OSM.i18n.t(instrPrefix + "unnamed"); namedRoad = false; } if (step.maneuver.type.match(/^exit (rotary|roundabout)$/)) { - instText += I18n.t(template, { name: name }); + instText += OSM.i18n.t(template, { name: name }); } else if (step.maneuver.type.match(/^(rotary|roundabout)$/)) { if (step.maneuver.exit) { if (step.maneuver.exit <= 10) { - instText += I18n.t(template + "_with_exit_ordinal", { exit: I18n.t(instrPrefix + "exit_counts." + numToWord(step.maneuver.exit)), name: name }); + instText += OSM.i18n.t(template + "_with_exit_ordinal", { exit: OSM.i18n.t(instrPrefix + "exit_counts." + numToWord(step.maneuver.exit)), name: name }); } else { - instText += I18n.t(template + "_with_exit", { exit: step.maneuver.exit, name: name }); + instText += OSM.i18n.t(template + "_with_exit", { exit: step.maneuver.exit, name: name }); } } else { - instText += I18n.t(template + "_without_exit", { name: name }); + instText += OSM.i18n.t(template + "_without_exit", { name: name }); } } else if (step.maneuver.type.match(/^(on ramp|off ramp)$/)) { const params = {}; @@ -133,9 +133,9 @@ if (Object.keys(params).length > 0) { template = template + "_with_" + Object.keys(params).join("_"); } - instText += I18n.t(template, params); + instText += OSM.i18n.t(template, params); } else { - instText += I18n.t(template + "_without_exit", { name: name }); + instText += OSM.i18n.t(template + "_without_exit", { name: name }); } return [[step.maneuver.location[1], step.maneuver.location[0]], ICON_MAP[maneuver_id], instText, step.distance, step_geometry]; }) diff --git a/app/assets/javascripts/index/directions/fossgis_valhalla.js b/app/assets/javascripts/index/directions/fossgis_valhalla.js index 8bfb1908f..13fc020c2 100644 --- a/app/assets/javascripts/index/directions/fossgis_valhalla.js +++ b/app/assets/javascripts/index/directions/fossgis_valhalla.js @@ -97,7 +97,7 @@ costing: costing, directions_options: { units: "km", - language: I18n.currentLocale() + language: OSM.i18n.currentLocale() } }) }); diff --git a/app/assets/javascripts/index/directions/graphhopper.js b/app/assets/javascripts/index/directions/graphhopper.js index 14b721d23..6a1043fbf 100644 --- a/app/assets/javascripts/index/directions/graphhopper.js +++ b/app/assets/javascripts/index/directions/graphhopper.js @@ -57,7 +57,7 @@ // https://graphhopper.com/api/1/docs/routing/ const query = new URLSearchParams({ vehicle: vehicleType, - locale: I18n.currentLocale(), + locale: OSM.i18n.currentLocale(), key: "LijBPDQGfu7Iiq80w3HzwB4RUDJbMbhs6BU0dEnn", elevation: false, instructions: true, diff --git a/app/assets/javascripts/index/home.js b/app/assets/javascripts/index/home.js index 7e297b724..597b68eff 100644 --- a/app/assets/javascripts/index/home.js +++ b/app/assets/javascripts/index/home.js @@ -18,12 +18,12 @@ OSM.Home = function (map) { }); marker = L.marker(OSM.home, { icon: OSM.getUserIcon(), - title: I18n.t("javascripts.home.marker_title") + title: OSM.i18n.t("javascripts.home.marker_title") }).addTo(map); } else { $("#browse_status").html( $("
").text( - I18n.t("javascripts.home.not_set") + OSM.i18n.t("javascripts.home.not_set") ) ); } diff --git a/app/assets/javascripts/index/layers/data.js b/app/assets/javascripts/index/layers/data.js index c0fffd72d..6452c2a41 100644 --- a/app/assets/javascripts/index/layers/data.js +++ b/app/assets/javascripts/index/layers/data.js @@ -51,15 +51,15 @@ OSM.initializeDataLayer = function (map) { $("
").append( $("
").append( $("

") - .text(I18n.t("browse.start_rjs.load_data")), + .text(OSM.i18n.t("browse.start_rjs.load_data")), $("
").append( $("