SeriesLocalization6

How language selection works

Localization — part 1 of 6

Two different things are called "another language" here, and they are independent:

  • Translating the ENGINE -- the menu, the labels, the dates, the wizards. That is this page, and it is one file of data.
  • Publishing the SITE in more than one language -- posts with a text per language, a root per language, a switcher. That is Publishing in more than one language at the foot of this page.

A site can do either without the other: a Czech blog whose engine speaks Czech publishes one language, and a blog published in Czech and German needs both locales to exist first.

How to translate blog.sh -- the generated site and the CLI wizards -- into a language it doesn't ship yet. No code changes are involved: a language is data, and a partial translation is useful from day one.

Read more

What a complete localization consists of

Localization — part 2 of 6

  1. locales/<lang>.yml -- copy locales/en.yml and translate the values (never the keys). The sections, roughly in order of what a visitor vs. an author sees:
SectionWho sees it
date_format, date_time_formateveryone -- strftime formats for every rendered date
thousands_separator, decimal_pointnobody reads these as words: they are how a number is written, not a message. English keeps , and ., German swaps them, Czech separates thousands with a non-breaking space. ./blog.sh stats formats its numbers through them, and every file size the engine prints -- beside an attachment on a page as well -- takes its decimal mark from decimal_point
language_namesite visitors -- the language's name in itself (Deutsch), which the language switcher shows to a reader of any other language
nav, post, pagination, tag, tags, type, series, index, archive, search, not_found, markdown_page, ui, redirectsite visitors -- the chrome, the listings, the tag index and the archive map, a post's own furniture (reading time, contents, series navigation), the 404 page and the one line an old address shows while it forwards
chromesite visitors -- the headings the engine says when the site writes none of its own: "About", "Links", "Find me on", each widget's "Recent toots"
sharesite visitors -- the row of controls under a post, including the question the Mastodon button asks and the two lines the copy button swaps between
jssite visitors -- shipped into the browser for client-rendered strings; js.date_locale is a BCP-47 tag (de-DE) and must agree with date_format, or server- and client-rendered dates diverge
buildauthors -- what ruby build/build_blog.rb says while it renders: both lines it signs off with, and everything it names as not built -- a post, a page, a tag, a redirect, a picture a post's media folder does not hold
cliauthors -- ./blog.sh, the wizard, $EDITOR hints
posterauthors -- what the CLI says when an announcement cannot be sent or its numbers cannot be fetched
doctor, check, stats, exportauthors -- the commands that report on the installation and the archive. doctor and check pair each finding with a fix line, and the fix is a sentence telling somebody what to do, so it is worth as much care as the finding
setup, style, wizardauthors -- the questions in ./setup.sh and ./style.sh, plus the plumbing both share
cron, importauthors -- scheduled publishing and ./import.sh
lockauthors -- what a command says when another run holds the site
pair, on_this_dayauthors -- ./blog.sh pair (the code an app connects with, the devices let in) and ./blog.sh on-this-day (what the day holds from earlier years); the card's own words for a reader are under chrome and js
language_fileauthors -- the build's and check's sentences about a config/site.<lang>.yml whose menu or footer links go elsewhere
Read more

Rules that keep a translation working

Localization — part 3 of 6

  • Placeholders survive verbatim. %{count}, %{slug}, %{path} -- same set as the English string, spelled exactly. A renamed placeholder isn't substituted and the reader sees %{cout} in the output.
  • Prompts keep their trailing space. A string ending ": " puts the cursor one space after the colon; drop the space and input sticks to it.
  • There is no plural system, on purpose. Write count phrases so one wording works for any number -- the way the Czech locale phrases around its three plural forms, or a neutral Posts: %{count} shape. Don't invent one:/many: variants; nothing reads them.
  • Some terms stay untranslated: repost, boost, reblog name three distinct mechanisms on three networks and are established loanwords; Markdown and RSS are names.
  • Use your language's own typography -- quotes („…", «…», “…”), dashes, spacing. The English text is the meaning, not the punctuation.
  • Multi-line messages may reflow. Match the content, not the line count.

Verifying your locale

Localization — part 4 of 6

Key parity against English (empty arrays = complete):

ruby -ryaml -e '
def keys(h, p = ""); h.flat_map { |k, v| v.is_a?(Hash) ? keys(v, "#{p}#{k}.") : ["#{p}#{k}"] }; end
en = keys(YAML.load_file("locales/en.yml")); xx = keys(YAML.load_file("locales/de.yml"))
p missing: en - xx, extra: xx - en'

Then see it live: set site.lang: <lang> in config/site.yml, and

ruby build/build_blog.rb        # the site side
./blog.sh preview                # read a post page, /search/, /markdown/, /404.html
./blog.sh help                   # the CLI side
./blog.sh                        # the wizard menu
./blog.sh doctor                 # and check, stats, export -- the reporting sections
./import.sh --help

A placeholder typo shows up immediately as a literal %{...} in the output -- worth grepping the rendered public.nosync/ for %{ before submitting.

What doesn't localize (yet)

Localization — part 5 of 6

  • Right-to-left languages. The templates don't mirror the layout (dir="rtl" is not set anywhere), so an Arabic or Hebrew locale would render left-to-right. Honest status: not supported until someone does the layout work, which is CSS and templates, not YAML.
  • Slugs stay ASCII. Titles are transliterated (NFKD plus a table for ß, ł, œ and friends -- see lib/slug.rb); scripts that don't transliterate (CJK, Cyrillic, Arabic) produce an id-based slug instead. URLs work either way.

Publishing in more than one language

Localization — part 6 of 6

site.lang is the language the site is WRITTEN in; site.locales is the list of languages it is PUBLISHED in:

site:
  lang: cs
  locales: [cs, de]

Both locale files have to exist (locales/cs.yml, locales/de.yml) -- that is the first half of this page.

A language the engine has not been translated into

The engine ships en, cs and de. Naming any other language in site.locales stops the build and says so: without words of its own that branch would quietly inherit English, which is worse than being told.

There are three ways out, and the third one is why this section exists:

# config/site.yml -- the site, and one sentence about its languages
site:
  lang: cs
  locales: [cs, sk]
Read more