Access other locale data from yml

So I am making a language switcher that pulls the name of the language directly from their respective locales/xx.yml file. One .yml file starts like this:

---
fr:
  lang: "Française"
  langshort: "FR"

Here is the piece of code responsible for displaying language switch:

- locales.each do | locale |
  - if locale == I18n.locale # dont display link to current locale
  - else
    %a{href: "/#{locale}/"} # this part works correctly, it displays correct URL
      %span.mobile-hide= t('lang') # this does not work correctly, it pulls the data from the current locale file
      %span.desktop-hide= t('langshort') # this does not work correctly, it pulls the data from the current locale file

Now I want that = t('lang') and = t('langshort') to actually display the correct language name from other files than the active I18n. How to access them?

@komor72 @tomrutgers I think you guys will know how to do it :slight_smile:

UPDATE: I just realised this piece of code is not working correctly because by default I mount /en to root so there is no /en/ page and it should show directly root (/). I need to find a way to acommodate this too.

First: I think you’re shadowing the locale helper with your loop-variable of the same name. This should work, but worth knowing it or change the name of the loop-variable (good practice should win here).

Second: my set of helpers is somewhat different (I use /helpers/custom_helpers.rb, not a code directly in Haml-file), but should point you in the right direction:

	def insert_langs_menu_item(page_url, new_locale, menu_text, title_attr=nil)
		if I18n.locale == new_locale
			return content_tag :span do menu_text end
		else
      return link_to menu_text, page_url, locale: new_locale, title: (title_attr || menu_text)
		end
	end

My code puts SPAN instead of a link in case of current locale, you prefer to ommit the active (current) one. The key is that the link_to helper currently supports locale parameter, which does all the work for you.

1 Like

Hey @komor72! Thanks for the help.

I still don’t understand what should I put in new_locale and menu_text parts? How do I access the other locale language from my .yaml files?

Can you show me code example from the view?

Sorry for delay.

You can use the helper from my previous post in your Haml-code. new_locale is the new locale you’re referring to in your locales.each loop. menu_text is the displayed text of the link you are creating (in your case this can be your %span.mobile-hide … %span.desktop-hide fragment). title_attr is an optional title-attribute, that is displayed as a tooltip when you hover your mouse over the link.

Something like this, with my changes proposed in the other of your posts:

- locales.each do | l |
  - unless l == I18n.locale # if you really don’t need to display current locale
    = insert_langs_menu_item(current_page, l, l.downcase, 'Switch language')

But if you don’t intend to display a %span for current locale, then there is no need to use a helper, just use it’s content directly:

- locales.each do | l |
  - unless l == I18n.locale # if you really don’t need to display current locale
    = link_to current_page, l.downcase, locale: l, title: 'Switch language'

Additional remarks:

  1. I suggest displaying the current locale as a %span, not ommiting it, so the user always knows, what locale is currently active.
  2. Your basic solution creates URL to homepage of the locale (either /en/ or /de/ or /). My solution always points to the current-page URL for the new locale (i.e.: from /en/archive/hello.html to /de/archiv/hallo.html, for example). This is easy thanks to locale: attribute of the link_to helper.

So my final proposition is this:

- locales.each do | l |
  - if l == I18n.locale
    %span= l.downcase # this is our current locale, no need for URL
  - else
    = link_to l.downcase, current_page, locale: l, title: "Switch language to #{l.upcase}"

You can put this code somewhere in the header part of the page and this will always create a link to other localizations of the very current page, not to the starting / page only.

… Except the locale: attribute of the link_to helper doesn’t work correctly. :slight_smile: I just checked my code and it doesn’t work correctly when current_page has the locale prefix, so if you want to switch from /de/ to / (EN mounted on root), then it doesn’t change anything. Only from / to /xx/ works okay. I’ve checked on MM 4.2.1 and 4.3.3.

I forgot that I have been using a workaround in my code since using Middleman 4.x. I just put a locale-less frontmatter attribute on each of my pages and use it in the locale-switcher code. Like so:

page /source/localizable/portfolio/kulinaria/ikea.html.haml has a frontmatter attribute:

---
i18n_source: "portfolio/kulinaria/ikea"
---
%p Page content…

and I use it in my switcher code instead of simple current_page:

= insert_langs_menu_item(current_page.data.i18n_source, :pl, "PL", "Przełącz na polski")
= insert_langs_menu_item(current_page.data.i18n_source, :en, "EN", "Switch to English")
= insert_langs_menu_item(current_page.data.i18n_source, :de, "DE", "Auf Deutsch umschalten")

@komor72 wow, thank you so much!

Looks like I have pretty messed up Middleman.

When I put to my view this snippet:

- locales.each do | l |
  - unless l == I18n.locale # if you really don’t need to display current locale
    = insert_langs_menu_item(current_page, l, l.downcase, 'Switch language')

assuming this is my insert_langs_menu_item helper in config.rb:

  def insert_langs_menu_item(page_url, new_locale, menu_text, title_attr=nil)
    puts "#{current_page.url}"
    if I18n.locale == new_locale
      # return content_tag :span do menu_text end
    else
      return link_to menu_text, page_url, locale: new_locale, title: (title_attr || menu_text)
    end
  end

…this is what I get:

<a href="/ja/" title="Switch language">en</a>
<a href="/ja/" title="Switch language">es</a>
<a href="/ja/" title="Switch language">fr</a>

When I use this snippet:

- locales.each do | l |
  - unless l == I18n.locale # if you really don’t need to display current locale
    = link_to current_page, l.downcase, locale: l, title: 'Switch language'

…this is what I get:

<a href="en" title="Switch language">#<middleman::sitemap::proxyresource path="ja/index.html" target="localizable/index.html"></middleman::sitemap::proxyresource></a>
<a href="es" title="Switch language">#<middleman::sitemap::proxyresource path="ja/index.html" target="localizable/index.html"></middleman::sitemap::proxyresource></a>
<a href="fr" title="Switch language">#<middleman::sitemap::proxyresource path="ja/index.html" target="localizable/index.html"></middleman::sitemap::proxyresource></a>

When I use this snippet:

- locales.each do | l |
  - if l == I18n.locale
    %span= l.downcase # this is our current locale, no need for URL
  - else
    = link_to l.downcase, current_page, locale: l, title: "Switch language to #{l.upcase}"

…this is what I get:

<a href="/ja/" title="Switch language to EN">en</a>
<a href="/ja/" title="Switch language to ES">es</a>
<span>ja</span>
<a href="/ja/" title="Switch language to FR">fr</a>

On top of these small errors, I am still wondering how can I access the actual language from yml data and use it in the language switcher menu.

---
fr:
  lang: "Française"
  langshort: "FR"

Read the link_to syntax in docs. The argument order is: = link_to text_to_display, url_to_follow, … unless you use block variant, then:

= link_to url_to_follow do
    …some code…

So, the = link_to current_page, l.downcase, locale: l, title: 'Switch language' code is incorrect – you put you URL (current_page) as the first argument, which is wrong. @justshipit, it seems like you have to gain some basic programming knowledge, or more Ruby, before jumping into Haml, Middleman etc.

Anyway I made your problem worse while using the locale: argument and current_page as an URL to follow, since it’s buggy in Middleman. Now I recall I tried to find this bug, but I don’t have debug tools and apparently enough Ruby knowledge to follow the code inside Middleman effectively. :frowning:

Before posting this code yesterday, I’ve checked it briefly in one of my projectś, which has default :en locale (based in \) and an alternate :pl (based in \pl\). Apparently I haven’t spotted another manifestation of this bug, which you showed above: never coming back from /ja/. So today I’ve checked it again and also in another project with default :pl (based in \) and alternates :en and :de (based in \en\ and \de\ respectively). So for now this works as expected, stick with it:

- locales.each do | l |
  - if l == I18n.locale
    %span= l.downcase # this is our current locale, no need for URL
  - else
    = link_to l.downcase, (l == :pl ? "/" : "/#{l}/"), locale: l, title: "Switch language to #{l.upcase}"

You can remove the title: attribute if you don’t like it, or better improve it to have localized content. :slight_smile:

I’ll answer the YAML question later. GTG.

1 Like

I assume we have these:

/source/locales/en.yaml
/source/locales/fr.yaml
/source/locales/ja.yaml
…

The best way (IMHO) is to put names of all languages in ALL locales. Which means: phrases “Switch to English”, “Switch to French” and “Switch to Japanese” should be translated to all your languages. You should have those /source/locales/*.yaml populated with interface elements anyway (menus, header, footer, …), so this should not be really more hassle. So we have:

/source/locales/en.yaml

---
en:
  lang_menu_switch_en: "Switch to English"
  lang_menu_switch_fr: "Switch to French"
  lang_menu_switch_ja: "Switch to Japanese"

The same kind of translation strings should go to fr.yml and j.yml accordingly. Then use the t(…) helper in your code:

…
    = link_to l.downcase, (l == :en ? "/" : "/#{l}/"), locale: l, title: t("lang_menu_switch_#{l}".to_sym, locale: l)
…

Two details:

  1. The "lang_menu_switch_#{l}".to_sym expression gives us correct key symbol for the locale we are looping in. Ommiting .to_sym will work too and I guess is more Ruby-way?
  2. Additional locale: l argument (which comes straight from the I18n gem, not the Middleman feature) allows us to present the text in the language user might be looking for. I mean a Japanese person rather prefers to see some Japanese kanji/kana characters, instead of English string “Switch to Japanese”. As a homework you can implement similar keys for your langshort codes, so instead of EN/FR/JA maybe the links should go English/Française/日本語 ?

This is my proposition of a basic structure of language-menus. They are basic because they always link to homepage of another language, not the same page you are currently on. Until the link_to’s locale: parameter bug is fixed, this is what you have without further problems. My multilang-projects use a little more custom-code (frontmatter data with i18n_source key), but they are not blogs with many posts, but normal sites (profile/portfolio type), so this is not a big problem to manually handle this additional frontmatter key. If you’re interested, I can post my code here.

Hey~

Appreciate your time and effort explaining to me Middleman.

Yes, I have:

locales/en.yml
locales/fr.yml
locales/ja.yml

In my case, I would prefer to serve to the user their NATIVE language directly (your 2nd point). So while English version of the website is active, on the link to French version I want to show “French” in native language, so it will be “Française” and so on. So I would need to somehow access file with another locale while I am on another locale (don’t get lost here :P).

Here is my file structure (I combined them all into one but of course each language is in it’s respective language file:

## in en.yml
en:
  lang: "English"
  langshort: "EN"

## in fr.yml
fr:
  lang: "Française"
  langshort: "FR"

## and so on..

Is it possible to achieve it while having each mention about language in their respective language file or the way you are solving where you list mention about ALL languages into one language file is the only way at this moment?

That’s exactly what t(:symbol, locale: :fr) will do for you. I described it at the beginning of point 2. Now you have all the building blocks at your hand, just use them your way. If you prefer your lang/langshort attributes that’s fine, use:

- locales.each do | l |
  …
    t(:lang, locale: l)
    …
    t(:langshort, locale: l)
  …

in your code. Sorry for too much confusion in my posts, but we jump subjects a little, plus I present various variants of the way to go. Just choose your way. I’m pretty sure I’ve described everything you need.

Maybe this?

- locales.each do | l |
  - if l == I18n.locale
    %span= t(:langshort, locale: l)
  - else
    = link_to l.downcase, (l == :en ? "/" : "/#{l}/"), locale: l, title: t(:lang, locale: l)