Skip to content

Localization

Jonathan Rochkind edited this page · 10 revisions

Umlaut (as of version 4.0) provides support for localization -- displaying in different languages -- via Rails' standard i18n feature. Umlaut localization was originally contributed by @ronan-mch of the Royal Library of Denmark.

Umlaut is an expansive and very extensible application; not all areas may as of yet been localized. If you discover an area or a particular service which is not yet localized but which you would like to be, please let us know. However, the Royal Library of Denmark is already using Danish-localized Umlaut, so it is sufficiently complete for at least some real use cases.

Providing new translations

At present, only Danish translation is built-into Umlaut; you can create additional translation files in your local app, or better yet contribute them back to Umlaut. In order to make a new translation, copy Umlaut's English translation file at https://github.com/team-umlaut/umlaut/blob/master/config/locales/en.yml to a new language abbreviation in your local app (eg ./config/locales/fr.yml), and translate all values.

A local translation file can also be used to change textual labels used in Umlaut even in English, overriding specified labels from Umlaut's distribution translation file. You can copy only parts of the original translation file, and override them locally, and the distribution values will be used for other keys you do not override.

umlaut.locale URL param specifies locale

The current locale is specified by the umlaut.locale URL parameter, for instance, in a URL: /resolve?title=something&umlaut.locale=da.

If no locale is present in the URL, the setting of I18n.default_locale will be used.

In the future, we might make locale-specifying URL's prettier, maybe something like /en/resolve?title=, but this is not operative at present. Let us know if it's important for your use case.

Enabling i18n localization

You don't need to do anything to turn on localization, it's already on, and will be activated if umlaut.locale is present as a query param.

However, you may wish to set I18n.default_locale in your config/application.rb file. You may also wish to specify which locales your app allows and supports by setting eg config.i18n.available_locales = [:en, :da].

If you'd like a UI element for changing the locale to show up, add show_localization_selector true to your local app/controllers/umlaut_controller.rb config block. But you can also customize your Umlaut header to provide your own locale selection UI. You can use Umlaut's default as a guide if you like.

Localization fallback feature

Standard Rails I18n provides a feature where you can specify 'fallbacks' -- if the Spanish locale is selected, but a translation is not available in Spanish, you might want to fallback to the English translation.

Since Umlaut is so extensible and constantly changing, it might be hard for other translations to keep up with English, and if this is an acceptable fallback for your context it might be wise. In your config/application.rb, specify for instance:

 config.i18n.fallbacks =[:en]

Special Cases in the Translation Files

There are some cases where the translation files are organized and used systematically to deal with Umlaut's flexibility and pluggability.

Display section names

The titles and optional prompts of sections of the Umlaut resolve menu screen are controlled by I18n translation files, in a way organized by the section's div_id. To find out a section's div_id, you can look at the source code where default configuration for display sections is done.

Or you can use a web browser's dev tools to look for the id attribute on the div wrapping a given umlaut_section (a bit messier HTML than it could be sorry):

<div id="excerpts">
     <div class="umlaut" style="">
          <div class="umlaut-section excerpts">

So for umlaut display section with div_id excerpts, the title and prompt are configured in the i18n translation file like:

en:
  umlaut:
    display_sections:
      excerpts:
         title: Limited Excerpts
         prompt: "A limited preview which may include table of contents, index, and other selected pages."

You can of course look at the existing Umlaut translation file to find a key you want to override here too. You can create a local file at ./config/locales/en.yml and over-ride just certain keys in it.

The title key actually may not be specified in the translation file, because as a default Umlaut will use the name of the ServiceTypeValue that is handled by the display section.

ServiceTypeValue names

Umlaut ServiceResponses are categorized into types, or as they are called in Umlaut (for unfortunate historical reasons) ServiceTypeValue's. For instance, fulltext, excerpts, document_delivery, highlighted_links ("see also" links) -- are all identifiers for ServiceTypeValues.

Sometimes Umlaut needs to refer to responses of a certain type by name, when creating UI. For instance "Full text links" or "request services". These names are also used as the default titles of a display section -- a display section that only displays fulltext will by default use the name of the fulltext ServiceTypeValue as it's title heading.

These names are controlled in the i18n translation file too, under umlaut.service_type_names, and using standard Rails i18n pluralization functionality to specify a singular and plural name:

en:
  umlaut:
    service_type_names:
      fulltext:
        one: fulltext link
        other: fulltext links
      table_of_contents:
        one: table of contents
        other: tables of contents

Services and their generated ServiceResponses

The core of Umlaut funcationality is Service plugins that create ServiceResponses, individual elements of generated content that usually display on the screen.

If a Service simply generates a ServiceResponse with fixed strings, of course it can not be translated into other langauges/locales:

# not internationalized:
request.add_service_response(
  :service => self,
  :service_type_value => :fulltext,
  :display_text => "A link to great things",
  :notes => "Click here to get great things", 
  :url => "http://greatthings.example.org"
)

For basic cases, to make this i18n-able (translatable), the service can include keys display_text_i18n and notes_i18n, whose values are keys to be looked up in a translation file. The literal display_text and notes values can be left as defaults:

request.add_service_response(
   :service => self,
   :service_type_value => :fulltext,
   :display_text => "A link to great things", 
   :display_text_i18n => "display_text",
   :notes => "Click here to get great things", 
   :notes_i18n => "notes",
   :url => "..."
)

The values of display_text_i18n and notes_i18n are keys that will be looked up in in a Rails i18n translation file, under a scope defined by the service name.

Let's say this service is defined by a class called GreatThings. :display_text_i18n => 'display_text' means to look up the key display_text in the scope umlaut.services.great_things (underscore/lowercase version of class name).

en:
  umlaut:
    services:
      great_things:
        display_text: A link to great things

Actually, the translation lookup will try two keys, one scoped as umlaut.services.%{service_id}, and if not found, then as umlaut.services.%{service_class_name_with_underscores} as above. This lets apps over-ride keys for a specific configured service, when multiple services of the same class are configured.

More complex i18n lookup, with placeholders

The above works fine if you want to use a single static i18n lookup for display_text or notes, but what if you need to call an i18n translation with placeholders, or otherwise do something more complicated?

The Service architecture lets a service define a method transform_view_data(hash) to transform the ServiceResponse hash on demand at display time, using custom logic.

Let's say we want to count the number of great things, and name the link eg "8 Great Things".

We might have an i18n translation key like:

en:
  umlaut:
    services:
      great_things:
        display_text: %{count} Great Things

Then in our GreatThings service plugin, we might create the ServiceResponse with no display text at all, but saving the 'count' in the ServiceResponse for later:

request.add_service_response(
   :service => self,
   :count => some_count,
   :service_type_value => :fulltext,
   :url => "..."
)

And then our Service would implement:

def transform_view_data(hash) 
  hash[:display_text] = self.translate("display_text", :count => hash[:count])

  return hash
end
  • Use the translate method that Umlaut gives to all services, this will look up scoped by serviceID, then by service class name.
  • Store placeholder parameters you might need in the ServiceResponse, you can use whatever arbitrary key names you want, they'll be waiting for you.
  • Use transform_view_data to add in the localized text on demand and on display. Do not do this:

        ```
        request.add_service_response(
          :display_text => I18n.t("something", :count => something),
          :display_text => translate("something", :count => something)
        ```
    
    • Why? Because services run in threads, and it's difficult to ensure the right I18n.locale is set in the bg thread when the service is running. And even if it were, this would freeze the language in the ServiceResponse; we'd rather let the user switch back and forth between languages without regenerating service responses.

Database Setup/Correction

(TODO: Provide Umlaut migration that does this?)

Using localisation may cause problems if your collation is not set to UTF8 in your MySQL database. The problem arises when using the locale selector dropdown; Rails will force UTF8 when sending a form, which will cause a collations error if the Umlaut database has not been created with a UTF8 collation (latin1_swedish_ci is the default). In order to update your collation you can run the following SQL statements, replacing umlaut_db with the name of your umlaut db:

ALTER DATABASE umlaut_db CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `umlaut_db`.`clickthroughs`CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `umlaut_db`.`dispatched_services` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `umlaut_db`.`permalinks` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `umlaut_db`.`referent_values` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `umlaut_db`.`referents` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `umlaut_db`.`requests` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `umlaut_db`.`schema_migrations` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `umlaut_db`.`service_responses` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `umlaut_db`.`sfx_urls` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
Something went wrong with that request. Please try again.