|
164 | 164 | } |
165 | 165 | $language_count = $languages.size |
166 | 166 |
|
| 167 | +# Localized copy for the "translation missing" interstitial (source/missing.html.haml), |
| 168 | +# keyed by the same language codes as $languages. English is the canonical set and |
| 169 | +# the per-key fallback — see data/interstitial.yml and the interstitial_string helper. |
| 170 | +require "yaml" |
| 171 | +$interstitial = YAML.load_file("data/interstitial.yml") |
| 172 | + |
167 | 173 | activate :i18n, |
168 | 174 | lang_map: $languages, |
169 | 175 | mount_at_root: :en |
|
183 | 189 | redirect "#{code}/index.html", to: "#{code}/#{$published_version_for.call(code)}/index.html" |
184 | 190 | end |
185 | 191 |
|
| 192 | +# Every (language, version) pair that hasn't been translated yet still resolves |
| 193 | +# to a real URL: a generated interstitial that explains the gap in the target |
| 194 | +# language (or English) and links to the English text plus how to contribute the |
| 195 | +# missing translation — instead of a 404. This is what lets the version/language |
| 196 | +# selectors offer *every* version for *every* language (issue #719): pick one we |
| 197 | +# don't have and you land somewhere helpful rather than nowhere. |
| 198 | +# |
| 199 | +# We mirror the selector's exposure rule: all spec versions when serving locally |
| 200 | +# (so an in-progress 2.0.0 draft previews), capped at the published $last_version |
| 201 | +# in a production build so unreleased drafts never ship. |
| 202 | +spec_versions = $versions.select { |v| File.directory?("source/en/#{v}") } |
| 203 | + |
| 204 | +generate_interstitials = lambda do |max_version| |
| 205 | + $languages.each_key do |code| |
| 206 | + spec_versions.each do |version| |
| 207 | + next if max_version && Gem::Version.new(version) > Gem::Version.new(max_version) |
| 208 | + next if File.directory?("source/#{code}/#{version}") # real translation exists |
| 209 | + proxy "/#{code}/#{version}/index.html", "/missing.html", |
| 210 | + locals: { language_code: code, version: version }, ignore: true |
| 211 | + end |
| 212 | + end |
| 213 | +end |
| 214 | + |
| 215 | +configure :development do |
| 216 | + generate_interstitials.call(nil) |
| 217 | +end |
| 218 | + |
| 219 | +configure :build do |
| 220 | + generate_interstitials.call($last_version) |
| 221 | +end |
| 222 | + |
186 | 223 | # Patch releases of this project (e.g. 1.1.1) ship fixes to the site and tooling |
187 | 224 | # without changing the changelog *specification*, so they never get their own |
188 | 225 | # spec page — the spec stays at the x.y.0 minor (1.1.0). But those patch versions |
@@ -237,9 +274,44 @@ def exposed_version_for(code) |
237 | 274 | VersionRouting.exposed_version_for(installed, last_version: $last_version, build: build?) |
238 | 275 | end |
239 | 276 |
|
240 | | - def available_translation_for(language) |
241 | | - version = exposed_version_for(language.first) |
242 | | - "#{version} #{language.last[:name]}" if version |
| 277 | + # The spec versions a visitor may pick from the version selector. We cap at the |
| 278 | + # published $last_version in a production build so unreleased drafts (e.g. an |
| 279 | + # in-progress 2.0.0) never surface; serving locally exposes the full set so |
| 280 | + # drafts can be previewed. Oldest → newest. |
| 281 | + def selectable_versions |
| 282 | + versions = spec_versions |
| 283 | + versions = versions.select { |v| Gem::Version.new(v) <= Gem::Version.new($last_version) } if build? |
| 284 | + versions |
| 285 | + end |
| 286 | + |
| 287 | + # The real spec version directories ($versions also carries the en index |
| 288 | + # template that the glob picks up), oldest → newest. |
| 289 | + def spec_versions |
| 290 | + dirs = $versions.select { |v| File.directory?("source/en/#{v}") } |
| 291 | + dirs.sort_by { |v| Gem::Version.new(v) } |
| 292 | + end |
| 293 | + |
| 294 | + # Whether a given spec version has actually been translated for a language. |
| 295 | + def version_available?(code, version) |
| 296 | + File.directory?("source/#{code}/#{version}") |
| 297 | + end |
| 298 | + |
| 299 | + # major.minor for display (we never ship patch-level spec pages). |
| 300 | + def display_version(version) |
| 301 | + version.split(".").first(2).join(".") |
| 302 | + end |
| 303 | + |
| 304 | + # A language's autonym (e.g. "Français"), falling back to its code. |
| 305 | + def language_name(code) |
| 306 | + ($languages[code] || {})[:name] || code |
| 307 | + end |
| 308 | + |
| 309 | + # A localized interstitial string for a language, keyed by e.g. "title" or |
| 310 | + # "lede". Falls back to English per key, so a partially-translated (or entirely |
| 311 | + # untranslated) language still renders a complete page. Interpolate the result |
| 312 | + # with String#% and the appropriate %{...} placeholders. See data/interstitial.yml. |
| 313 | + def interstitial_string(code, key) |
| 314 | + ($interstitial[code] || {})[key] || $interstitial["en"][key] |
243 | 315 | end |
244 | 316 |
|
245 | 317 | # The release date for a version, read from CHANGELOG.md (e.g. the line |
|
0 commit comments