diff --git a/deploy/nginx/check-config.sh b/deploy/nginx/check-config.sh index 84ef86c0..db9f8886 100755 --- a/deploy/nginx/check-config.sh +++ b/deploy/nginx/check-config.sh @@ -559,6 +559,34 @@ answered GET /coupler 301 https://github.com/OpenIPC/coupler/ answered GET /wiki/some/deep/path 301 https://github.com/OpenIPC/wiki nginx answered GET /binaries 410 - nginx answered POST /binaries.json 410 - nginx +# An old `?locale=ru|zh` address goes to the page in that language, with the +# rest of its query (conf.d/openipc-locale-param.conf). Not when the address +# already names a language, not for English, not for a file, not for a write. +answered GET "/donate?locale=ru" 301 "$O/ru/donate" static +answered HEAD "/donate/?locale=zh" 301 "$O/zh/donate/" static +answered GET "/?locale=ru" 301 "$O/ru" static +answered GET "/?utm_source=x&locale=zh" 301 "$O/zh?utm_source=x" static +answered GET "/donate?locale=ru&utm_source=x" 301 "$O/ru/donate?utm_source=x" static +answered GET "/donate?a=1&locale=ru&b=2" 301 "$O/ru/donate?a=1&b=2" static +answered GET "/ru/donate?locale=ru" 200 - static +answered GET "/zh/donate?locale=ru" 404 - static +answered GET "/donate?locale=en" 200 - static +answered GET "/donate?locale=de" 200 - static +answered GET "/donate?locale=RU" 200 - static +answered GET "/favicon.png?locale=ru" 200 - static +# The route map answers its own addresses first, and the next hop localises. +answered GET "/community?locale=zh" 301 "$O/zh/community" static +# The addresses the engines actually hold, which the stub bundle does not: +# the redirect turns on the route map, not on the files. +answered GET "/cameras/vendors/sigmastar/socs/ssc30kq?locale=ru" 301 "$O/ru/cameras/vendors/sigmastar/socs/ssc30kq" static +answered GET "/cameras/vendors/goke?locale=zh" 301 "$O/zh/cameras/vendors/goke" static +answered GET "/tools/high-resolution-timer?locale=ru" 301 "$O/ru/tools/high-resolution-timer" static +answered GET "/low-latency?locale=ru" 301 "$O/ru/low-latency" static +answered GET "/cameras/boards?locale=ru" 301 "$O/ru/cameras/boards" static +answered POST "/donate?locale=ru" 405 - static +# Percent-encoded paths and repeated locales are left as they were (Qodo on #409). +answered GET "/%72u/donate?locale=zh" 200 - static +answered GET "/donate?locale=ru&locale=zh" 200 - static answered GET /telemetry/anything 410 - nginx answered GET /zh/merchandise 410 - nginx answered GET /admin/snapshots 410 - nginx diff --git a/deploy/nginx/conf.d/openipc-locale-param.conf b/deploy/nginx/conf.d/openipc-locale-param.conf new file mode 100644 index 00000000..17f70bb1 --- /dev/null +++ b/deploy/nginx/conf.d/openipc-locale-param.conf @@ -0,0 +1,70 @@ +# `?locale=ru` and `?locale=zh` on a page, sent to the page in that language. +# +# The Rails site named the language in a parameter, and the search engines +# still hold those addresses: on 2026-10-08 Google had 110 of openipc.org's +# 671 URLs with impressions carrying a query string, and +# /cameras/vendors/sigmastar/socs/ssc30kq?locale=ru alone earned 35 clicks in +# four weeks. The bundle ignores the parameter, so every one of those readers +# got the English page, and its canonical told the engine the English page +# was the one to keep. The home page's script has always turned `/?locale=ru` +# into `/ru` (frontend/apps/site/src/pages/index.astro); this does the same +# for every page, before a byte of the wrong language is sent. +# +# Only a read, only a language the site publishes, only an address without a +# language of its own, and only a page: a name with a dot is a file. Anything +# else on the query string goes along. Behind openipc.ru the Location is +# rewritten by the mirror like every other redirect, so a reader there stays +# there. +# +# Built from $request_uri and $args, never $uri: $uri is decoded, and a +# decoded %0d%0a in a Location header is a header of the requester's choosing. +# +# The Open Wall's own `?locale=` is conf.d/openipc-wall-shell.conf's, in +# locations of its own; this is applied in the seam's `location /` only. + +# The path to localise: "/" for the home page, the path for any other page, +# and empty for an address that already names its language, is a file, or is +# percent-encoded. The route map classifies the decoded $uri, and /%72u/donate +# is /ru/donate to it; a raw path like that left alone is answered as it was. +# Regexes are tried in order, so the home page is decided first. +map $request_uri $openipc_lp_path { + default ""; + "~^/(?:\?.*)?$" "/"; + "~^/(?:ru|zh)(?:[/?]|$)" ""; + "~^[^?]*\." ""; + "~^[^?]*%" ""; + "~^(?/[^?]*)" $openipc_lp_p; +} + +# The query string without `locale`, with its `?`, or nothing at all. +map $args $openipc_lp_args { + default "?$args"; + "" ""; + "~^locale=[^&]*$" ""; + "~^locale=[^&]*&(?.+)$" "?$openipc_lp_rest"; + "~^(?.*?)&locale=[^&]*(?&.*)?$" "?$openipc_lp_pre$openipc_lp_post"; +} + +# Only where the route map (conf.d/openipc-redirects.conf) has nothing of its +# own to say: a page, or an address nothing claims. An address it redirects or +# retires is answered by it first, and a `?locale=` the redirect carries along +# is honoured on the next request -- /about?locale=zh goes to +# /community?locale=zh and from there to /zh/community. +map $openipc_route_action $openipc_lp_routable { + default ""; + page 1; + catchall 1; +} + +# One `locale` only. $arg_locale is the first of several, and the others would +# ride along on the localised address with nothing left to remove them. +map $args $openipc_lp_once { + default 1; + "~(?:^|&)locale=[^&]*&(?:.*&)?locale=" ""; +} + +map "$request_method:$openipc_lp_routable$openipc_lp_once:$arg_locale:$openipc_lp_path" $openipc_locale_param_target { + default ""; + "~^(?:GET|HEAD):11:(?ru|zh):/$" "/$openipc_lp_l$openipc_lp_args"; + "~^(?:GET|HEAD):11:(?ru|zh):(?/.+)$" "/$openipc_lp_l$openipc_lp_q$openipc_lp_args"; +} diff --git a/deploy/nginx/sites-available/org.openipc b/deploy/nginx/sites-available/org.openipc index 9e5a1ab1..a9d453cb 100644 --- a/deploy/nginx/sites-available/org.openipc +++ b/deploy/nginx/sites-available/org.openipc @@ -1046,6 +1046,10 @@ server { limit_conn site_conc 10; limit_conn_status 429; + # An old `?locale=ru|zh` address goes to the page in that language + # (conf.d/openipc-locale-param.conf). + if ($openipc_locale_param_target) { return 301 $openipc_locale_param_target; } + root /srv/www/static/prod/current; try_files $uri $uri/index.html @fallback; diff --git a/deploy/nginx/sites-available/org.openipc.dev b/deploy/nginx/sites-available/org.openipc.dev index aadc1090..e21c5650 100644 --- a/deploy/nginx/sites-available/org.openipc.dev +++ b/deploy/nginx/sites-available/org.openipc.dev @@ -876,6 +876,10 @@ server { } location / { + # An old `?locale=ru|zh` address goes to the page in that language + # (conf.d/openipc-locale-param.conf). + if ($openipc_locale_param_target) { return 301 $openipc_locale_param_target; } + root /srv/www/static/dev/current; try_files $uri $uri/index.html @fallback;