Open Happy-Ferret opened 5 years ago
I would be very keen to add localization to Trizbort. The application doesn't have a vast amount of localizable text, so it should be little effort to translate. Later on, it will enable other contributors to add translations over time, without having to touch the actual code.
I am happy to see development of a TypeScript implementation of this, so it'll be easy to drop in. In particular I like "simple" implementations: it is immediately clear how this should be used.
I do find accessibility an important feature. This is why Trizbort has (some) keyboard support, and localization is also part of accessibility.
A couple of questions about rosetta-typescript:
Is there a reason why the message JSON format is this:
{
"new_map_title": {
"message": "New map"
},
"new_map_text": {
"message": "This will erase all editor contents. Proceed?"
}
}
rather than this:
{
"new_map_title": "New map",
"new_map_text": "This will erase all editor contents. Proceed?"
}
I figure it has to do with some standard, but I'm curious to know.
I thought it might be nice to be able to switch languages on the fly, rather than opening a new URL. This seems to be rather more of a change to Trizbort than to any translation library; it would have to re-render the GUI with new text, while keeping the user's map open. On the other hand, this is probably a feature that no one would ever use. Once a user picks a language, they're likely to stick with it, I suppose. Perhaps this may be a consideration for your project?
There were a few design goals when I starting building Trizbort:
Clearly rosetta-typescript could be compiled into Trizbort and require no additional download. On the other hand, the language files will be additional downloads (well, just one). I suppose it will be retrieved through AJAX from the server, after Trizbort first loads. Do I have that right?
Also see #30 for a use case where localization would add a lot of value.
Ok. I'll try to briefly answer your questions before I endeavor on my daily lunch hike :smile:
Resource file format
I chose the format specifically because it's already being used by Chrome Apps and WebExtensions in general.
Furthermore, as noted before, it's well supported by leading online translation platforms.
Language switching
I thought about that, and it would certainly be a possibility. The main reason I chose to add an additional argument is that it makes working with i18n from my fork's Go back-end quite easy and straightforward.
During window initialization I simply set the Homepage
field (what ends up being displayed in the Electron window) like this:
Homepage: "index.html?lang=" + i18n.GetUILanguage(),
Dependencies
The current implementation utilizes the Fetch API
The following code is what this boils down to (where Translations
is an Interface describing the translation file format)
import { Translations } from './types';
export default function load(locale: string): Promise<Translations> {
const url = `locales/${locale}/messages.json`;
return fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(
`${response.status}: Could not retrieve file at ${url}`
);
}
return response.json();
});
}
Ok.
For the TS version of the library I've created a slight divergence from the Chrome i18n reference.
I've added the following method
setUILanguage(locale: string): Promise<void>
This accepts any one locale string and, unless rejected (i. e. in the case of an unsupported language), loads the new language and replaces all existing message strings.
That should deal with your Language switching question gracefully.
@henck
I just finished the initial implementation this morning (minus the desktop app specific parts).
The source code can be found here
(the i18n library lives under src/rosetta
. The translation strings under locales
).
Any key prepended with app_
is desktop app specific. All the other keys are used throughout both, the web version and the desktop app.
For translation tracking I chose Transifex.
Eventually I plan moving Rosetta into its own distinct git repository and publish it to npm.
https://pedantic-heyrovsky-f84015.netlify.com
https://pedantic-heyrovsky-f84015.netlify.com/?lang=de#
One more implementation detail that will probably change over time:
Currently, strings that are untranslated will show up as their keys for that respective language.
i. e. if there was no German translation for new_map_title
, all a user of the German version would see is "new_map_title". In the future, Rosetta might load the default/source string instead. In that case a user of the German version would see "New map" instead.
Great stuff! I had a look at the code just now. It'll be interesting to add this to Trizbort, too.
I am unsure what Transifex does, exactly. Is that where I would go if I wanted to contribute translation strings?
Exactly. Transifex is my chosen translation platform (you could also pick Crowdin or any other solution. I just found Transifex the easiest one to set up).
Transifex is where you translate strings and add new languages, whereas all the source strings are defined locally (inside locales/en/messages.json
).
This is the translation workflow, in a nutshell:
locales/en/messages.json
.App.i18n.GetMessage(translationKey: string)
{{{i18n "translationKey"}}}
I also added a short write-up about it to my blog:
https://happy-ferret.github.io/posts/yggdrasil-announcement.html
I've got a busy week ahead of me, but I'm positive I can wrap up Yggdrasil v0.3.0 by the end of the first week of September. Once that is done, I'll work on a patch for trizbort.io adding the same functionality to it.
You leave me no choice but to sign up for Transifex to provide you with Dutch and Portuguese translations. A language request should be coming in momentarily.
I didn't follow everything but count me on to translate it in French.
@Selsynn Translations are for Yggdrasil, a fork of Trizbort, and they're done in Transifex (sign up there -- see messages above). @Happy-ferret will need to accept your French language request. We may be able to move any translations back to Trizbort later.
Cool! I'll make sure to add more keys over the coming days. So far, I think I've only added translation keys for the main menu panel and the dialogues.
Ah. I see you already figured out how to request new languages 😺 Just added Dutch, French and Portuguese.
In the future I'll probably add "description" fields to the source strings, to make it easier to actually locate a language string/make sense of it.
@Happy-Ferret You'll still need to accept our joining the various translation groups on Transifex.
@henck Strange. I don't see any requests, and I can't invite you by username somehow.
There should be some pending requests:
Ah! Found it. Was under "Teams" rather than general notifications.
Confirmation received.
OK, Dutch and Portuguese fully translated. Be sure to add new keys and give me a heads-up.
I have found that there is a Spanish group using Trizbort.io to create JSON maps that they then parse into a format needed for their project, using a custom Python script. They might appreciate a Spanish translation of Trizbort.io/Yggdrasil, as well.
I just updated the source strings, @henck and @Selsynn.
There now translations for the Note
panel, the Color Picker
control, Load/Import error dialogs, and the rest of the desktop app menu (please note that I'm following the Microsoft/Windows standard for those app_*
entries. i. e. all words of those app menu entries should be capitalized).
I can see the new strings. However, I don't understand which entries should be capitalized. The ones with keys starting with app_
are not capitalized in your own English version.
Could it be that You Mean Capitalized rather than CAPITALIZED ?
@henck
Exactly. Every single word should start with a capital letter.
i. e. "New Map", "Load Map", "Save As Image", etc.
OK, all done.
Thanks! I'll review/merge the translations during my lunch break, then (hopefully) add the final source strings by the end of the day.
Shouldn't be that many left, and after that I can finally go back to cherry-picking some patches off of Trizbort.io and work on the i18n patch for Trizbort.io.
Pushed another large update.
I think the only ones missing from my version are now the Connector Panel
and the Render Settings Panel
.
Sorry for the many duplicates. In the future, I'll probably shrink those down to one common key. In the meantime, you can probably just use Transifex' Translation Memory feature to (semi-)automatically translate those duplicates.
I've enabled Translation Memory for the entire project now, so all remaining, untranslated strings are unique and indeed without a previous translation.
OK, all done for Dutch and Portuguese. I would like to note that the Initial Caps formatting looks a bit strange in Dutch; I don't know if Microsoft enforces it for their Dutch language products, but I've followed your specification.
I have some trouble with the difference between Bring to the front/Bring forward, so I will wait to see on the app before trying to translate it
Hey, @Selsynn.
You can already test that feature in the web version. It's part of the element's (room, block and note all support it) popup. Simply place a couple rooms, click one of them and choose the option from the menu (see screenshot).
https://pedantic-heyrovsky-f84015.netlify.com/#
@henck Fair point. I thought about that, and it does look strange in most/all Romance languages, too. I'll need to check what Visual Studio Code looks like in Dutch/French.
@Happy-Ferret thx I will do the last of the translation tomorrow morning or this evening when i am at home.
Added the final source strings. 🏎
... and done.
Awesome!
FYI. I already started prototyping code for v0.4.0 of Yggdrasil, which will include Chrome OS support.
Surprisingly, there's not a lot of necessary changes. Just a couple CSP errors (I'll fix these by vendoring the external JS libraries in index.html
).
Interestingly, Rosetta-TS works out of the box. It's using the navigator.language
fallback to pick the correct language. I could either leave it like that or, better yet, extend Rosetta-TS to return chrome.i18n.getMessage()
whenever that method is called under Chrome OS (while also nulling the JSON loader).
@Selsynn
Do you need any help with the remaining translations?
@Happy-Ferret Just needed a reminder that i didn't finish it. Will do it right now. It's done now.
This isn't a bug report or a feature request. More of a general inquiry or an RFC (in fact, I'd love to debate some implementation details).
I was wondering what your stance on i18n (internationalization. In this instance, a focus on localization specifically) was.
Is this something you'd like to see supported in the foreseeable future?
I currently work on a set of simple i18n libraries for both Go and TypeScript (aptly dubbed Rosetta-Go and Rosetta-TS, respectively).
Both libraries follow the Chrome i18n API (just the basics, so far. No placeholder support, yet).
In its most basic incarnation there's a
locales
directory. Inside thatlocales
directory are folders named for language shortcodes (en
,fr
,de
, etc. So far, no support for country-specific shortcodes). Inside each of these folders is amessages.json
file that adheres to the following basic pattern (where "new_map_title" and "new_map_text" are unique keys, and the contents of the "message" fields the language-specific display text):To quickly switch between languages in the browser, the user may provide a
lang
argument (i. e.www.trizbort.io/app/index.html?lang=fr
— orwww.trizbort.io/app/index.html?map=hobbit&lang=fr
, to load a new map while also switching the language to French).If a language code is not recognized, the application will launch with the default fallback language (English, for Trizbort) instead. Although here one may also consider checking the browser language (
window.navigator.language
) against the array of supported languages.Both i18n libraries will provide a
GetMessage(key: string)
method to place a translation.The translation specific code for the screenshots below looks as follows:
The beauty of utilizing Chrome i18n lies in its simplicity, as well as the compatibility with most online translation providers like Transifex or Crowdin.
Screenshots: A first WIP implementation running in my fork.