I think one of the best ways is to use the link
, as:
<link rel="alternate" hreflang="en-gb" href="http://en-gb.site.com" />
<link rel="alternate" hreflang="pt-br" href="http://pt-br.site.com" />
This will already make searchers already show the content in the right language, in most cases.
But obviously this is not enough. To get the user’s language, a good way is to get the window.navigator.language
, then use some kind of matcher of BCP-47, so if the browser result is en-us
it would get the nearest language available, the en-gb
, for example. If there is no language next will fall into a pattern, which is what you define.
This is the first time I try to make a site with multiple languages, so what I’m going to write here is what I’m doing, but it wasn’t "tested in production" and maybe there are other problems in this method.
I am using Gopherjs, Golang, so I just copied what I use and tried to simplify, but it will be necessary to find equivalent functions for Javascript.
What I’ve done is simply:
<div data-text="welcome"></div>
Then, when starting the page, the client’s language is obtained, using the window.navigator.language
, and there is a dictionary and the list of languages:
var Idiomas = []language.Tag{language.English, language.BrazilianPortuguese}
var Dicionario = texts{
"welcome": {
"Welcome",
"Bem-vindo",
},
"another-text": {
"Another text",
"Outro texto",
},
}
Now, to know what content to display, I use the text/language
, you should find someone else for Javascript. That way it simply becomes:
var idiomaEscolhido string
// Obtêm o idioma do subdominio, se existir
subdominio := strings.Split(js.Global.Get("location").Get("host").String(), ",")
if len(subdominio) > 1 && subdominio[0] != "meusite" {
idiomaEscolhido = subdominio[0]
}
// Obtêm o idioma do navegador, se não há definido até então
if idiomaUsuario = "" {
idiomaUsuario = js.Global.Get("navigator").Get("language").String()
}
// Encontra o idioma mais próximo
_, index := language.MatchStrings(language.NewMatcher(AvailableLanguages), lang)
The index
represents 0
for language.English
and 1
for language.BrazilianPortuguese
. First we use the Domain language (such as en-us.meusite.com
), but if there is no Ubdomain (such as meusite.com
) we will use the browser language. So when loading the page just do a:
for _, el := range t.QuerySelectorAll("[data-text]") {
text, ok := Dicionario[el.GetAttribute("data-text")][index]
if !ok == "" {
logs.Warn("Não encontramos textos para " + el.GetAttribute("data-text"))
}
t.SetText(el, text)
}
The idea of this code is to get all the data-text
and replace. So, if there is data-text="welcome"
he must pick up the text that is on the map of dicionario
. So the dicionario["welcome"][0]
will be the English, and the dicionario["welcome"][1]
will be the Portuguese.
This has some limitations, such as date formatting or plural formatting. For example, in Brazil, we use DD/MM/AAAA
, while elsewhere is MM/DD/AAAA.
One way to mitigate this is by using an international standard, but this can be strange in some cases.
On the performance side, there is how to mitigate the impact. Once you have to find all the data-text
and then perform the insertion of the text, you can use the Htmltemplateelement (the <template>
) or the Htmlslotelement (the <slot>
). These two elements are not rendered except when started. This indicates that all data-text
within it will not be translated and will not be obtained. So each time you display a template
will have to translate the text contained in it.
This reduces the impact, since only what has been started will be translated. Then, only what the user sees will be translated, not waste time translating what the user will not see.
In the SEO part, you can perform the same of JS on the server side. The goal here is simple. When the user accesses en-us.site.com
the bot will already see the content in English, so all data-text
will already be completed in advance, even if the client does not have Javascript.
For this, it is possible to create, in my case, a http.Handle
, on it will execute a code using the htmlquery
, which uses XPATH. Then creates a file index_en_us.html
and index_pt_bt.html
, both have the contents of the data-text
already completed and previously stored.
When the user accesses en-us.site.com
he will be served by index_en_us.html
. However, if you click on "English", the effects will be immediate and you will not need to wait for a request for the pt-br.site.com
, since the translation can also be done on the client.
If you use Nodejs you can do the same thing, maybe even reuse the client code on the server.
When the user accesses the default page, site.com
, he will be served with the index_en_us.html
. However, the content will be translated on the client side, based on the browser language.
You could also read the header of Accept-Language
, on the server, and give content with the best language. However, many Cdns (such as Cloudflare) do not respect the Vary
. So even if you return a header from Vary: Accept-Language
, to indicate that the content is another depending on the Accept-Language
, they continue to give the same result for any request. Therefore, I preferred to send the default content and the client, in Javascript, translate. The client-side translation is positive because it is immediate, so the user can switch the language whenever they want, without having to wait.
I once researched the same thing i18next that seems to do exactly what you need only with JS. I didn’t get to use because it was decided not to have 2 languages.
– Neuber Oliveira
Take a look at this plugin http://www.openxrest.com/translatejs/
– André Vicente
Can you take a look at: How to translate a website into PHP?, How to make a multilingual site?, Website in two languages and How to make a multilingual site and identify the country of origin?
– rray
With javascript you have this => Internationalize web app with jQuery i18n plugin
– rray
To identify the parent using jquery vc you can see this response in the EN OS http://stackoverflow.com/a/28960373/3155576, it indicates the code $. getJSON('http://freegeoip.net/json/', Function(result) { Alert(result.country_code); }); I tested the URL and it came right back
– h3nr1ke