feat(i18n): nine languages, each at its own address; release v6.3.0
The site now speaks German, French, Spanish, Ukrainian, Russian, Chinese, Korean and Hindi alongside English — 623 strings per language, 5,607 translations, covering the landing page, the key exchange, the chat, group calls and every error along the way. Each locale is a real page at a real URL (/de/, /fr/, …), generated at build time from locales/*.json. That is the whole point: the app is client-rendered, so a language that only swaps strings at runtime has no address for a crawler to index and no link anyone can share. Each page carries its own canonical, a reciprocal hreflang cluster and translated schema.org. The URL decides the language, always. A stored preference only applies where the address does not say, and nothing ever redirects on Accept-Language — that is how sites become invisible to search engines outside one country. robots.txt and sitemap.xml did not exist before; both are generated now. Bugs found on the way, each with a test that would have caught it: - partner logos used page-relative paths and 404'd from any /xx/ page - post-build stamped ?v= only into the root shell, leaving locales behind - "Back online" appeared on every page load, not just after being offline - update timestamps were hard-coded to US format for every reader - t() threw on a partial window, taking whole components down with it
This commit is contained in:
@@ -104,6 +104,15 @@
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# The dictionary is imported by a bare path from a plain browser module, so nothing
|
||||
# busts its cache. It must revalidate or translations freeze at whatever a visitor
|
||||
# first loaded.
|
||||
<FilesMatch "^(index|generated)\.js$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "no-cache, must-revalidate"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# CSS files - long cache
|
||||
<FilesMatch "\.css$">
|
||||
<IfModule mod_headers.c>
|
||||
@@ -133,6 +142,13 @@
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# Crawler-facing files - short cache so search engines re-read them
|
||||
<FilesMatch "^(robots\.txt|sitemap\.xml)$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "public, max-age=3600"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# ============================================
|
||||
# SECURITY
|
||||
# ============================================
|
||||
@@ -186,6 +202,35 @@
|
||||
|
||||
# If file not found, redirect to index.html (for SPA routing)
|
||||
<IfModule mod_rewrite.c>
|
||||
# Locale shells first: a deep link under /de/ must fall back to the German page,
|
||||
# not the English one. Rewritten by scripts/build-i18n.js from locales/site.json.
|
||||
# BEGIN generated locale shells
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^de(/.*)?$ /de/index.html [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^fr(/.*)?$ /fr/index.html [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^es(/.*)?$ /es/index.html [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^uk(/.*)?$ /uk/index.html [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^ru(/.*)?$ /ru/index.html [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^zh(/.*)?$ /zh/index.html [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^ko(/.*)?$ /ko/index.html [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^hi(/.*)?$ /hi/index.html [L]
|
||||
# END generated locale shells
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} !^/meta\.json$
|
||||
|
||||
Reference in New Issue
Block a user