Ghost's translate helper on a multilingual site
Ghost themes translate their built-in words (Subscribe, Sign in, Read more) with the {{t}} helper, which reads one file from locales/ chosen by the site's single publication language. If your site serves English at / and Spanish at /es/, every visitor gets the same language for those words. This page explains why, and shows a small partial that picks the string file per page instead. The partial was tested on Ghost 6.70 with the Source theme.
Why {{t}} cannot do it alone
Ghost's own translation FAQ says the helper is built for a single-language site, and recommends one Ghost install per language when the content differs. Setting the publication language in Ghost Admin changes the word list for the whole site at once. A page cannot ask for a different one, so a bilingual theme has to decide the language itself.
Deciding the language of a page
On one install, the usual setup (see the guide Multilingual Ghost on one install) is a path prefix per language in routes.yaml plus an internal tag such as #es on each translated post. A template can then work out the language in this order:
- The URL starts with
/es/: the page is Spanish. This covers the Spanish home page, tag pages and listings. - The current page carries the
#estag: Spanish. This covers posts and pages. - Inside a post loop, the post carries the tag: Spanish.
- Otherwise it is the default language, and the normal
{{t}}applies.
The partial
Put this in partials/polyglot/t.hbs in your theme. It is the output the Polyglot kit generates for English plus Spanish; add one branch per extra language.
{{#match (url) "~^" "/es/"}}{{> polyglot/t-es k=k}}{{else}}{{#has tag="#es"}}{{> polyglot/t-es k=k}}{{else}}{{#with post}}{{#has tag="#es"}}{{> polyglot/t-es ..}}{{else}}{{t ../k}}{{/has}}{{else}}{{t k}}{{/with}}{{/has}}{{/match}}
Then partials/polyglot/t-es.hbs holds one match per string and falls back to {{t k}} when a string is missing, so an untranslated word shows in the default language instead of breaking the page:
{{#match k "Subscribe"}}Suscribirse{{else match k "Sign in"}}Iniciar sesión{{else}}{{t k}}{{/match}}
In the theme, replace {{t "Subscribe"}} with {{> "polyglot/t" k="Subscribe"}}. Two things we hit while testing:
- Do not use
@rootin these partials. The Ghost theme validator (gscan) rejects it. Pass the key in ask, as above. - Wrapping a string in
{{#with post}}hides the key inside the block, which is why the loop branch passes..and reads../k.
What this does not translate
Newsletter emails, member portal screens and Ghost's built-in search are still sent in the publication language. Content you write yourself (titles, body, navigation labels) is not touched by any of this. Strings with placeholders, such as "By {authors}", need their own handling and are best left alone at first. Spanish wording in any list of strings should be checked by a native speaker before you publish it.
The shortcut
Writing and testing the partials for every string by hand takes an afternoon. The Polyglot for Ghost kit generates them from a config file, ships string files for several languages, and includes a patched Source theme that passes gscan. If you only need the routes, use the free routes.yaml generator, and check the result with the hreflang checker.