A couple of people have asked me to translate No Meat Today in their language, but the last time I tried to tackle that, none of the solutions I found were satisfying or in my price range.
I just found Localazy and I'm sold. And not just because there’s /lazy/ in the name. It’s built by devs and from the CLI to the way it works, it felt like we understood each other and it was behaving “as it should”.
So here’s a little recount of my experience so far, hopefully that’ll help you get started.
If you can’t wait to get started, you can register using this link and I’ll earn extra phrases (thanks 😁).
What needs translation?
In order to get No Meat Today ready for a new country, I have to translate these file formats:
- strings: until SwiftUI arrived, I would split Localizable.strings into multiple files and use table names but I’ve started to regroup files and use a simple prefix in the key instead
- stringsdict: this is used to handle plural
- plist: I use this for the daily messages, so it’s a list of sentences, packaged by theme (Default, Premium, Star Wars, Xmas…)
- some assets: I only have some screenshots for the paywall that are translated (en/fr), I can default to English
- txt: marketing texts & screenshots published on the App Store. This is downloaded into my app folder by fastlane.tools
Localazy supports only the first 3 file formats: strings, stringsdict and plist, but that’s already better than some other solutions I considered.
I suggested them to support txt files, you can vote for it if you like that idea.
Pricing
Fair warning: you will probably have to pay. But something that feels fair.
You get 200 phrases for free and can buy more. Adding 500 (so 700 total) will cost you a one-time fee of $50, and 1000 (1200 total) $90.
I found the project launch on Product Hunt and they advertised 1000 keys for free and a 75% discount. I contacted them about it and Václav was transparent about how this was too generous to build a viable business and that they had to decrease it. I wasn’t really surprised to be honest, and the current pricing still seems pretty reasonable to me.
They also offer a starter pack subscription at $19/mo or $199/year, and it wouldn’t be surprising if they decided to drop the one time fee at some point. Hopefully that will include more affordable tiers, but in any case, Václav promised me that any phrases bought now will be owned forever, which is enough for me.
You can get free phrases with a referral link (here is mine, again, I have no shame). Know that this referral bonus is added on a daily basis, and you only get it if the user integrates their app (upload texts).
I was told they would offer more ways how to earn free phrases.
Setup
The main pages you’ll want to look at are
It can be worth having a look at these two as well since they describe the configuration of the two core commands of Localazy: upload
and download
.
Install the CLI
Installation is done via Homebrew
xcode-select --install # Only if you haven't done so before
brew tap localazy/tools
brew install localazy
Create your configuration file
I followed the Quick Start - iOS & macOS – Localazy instructions at first, but this didn’t work for me.
I have a Watch Extension, so for instance I have a Base.lproj in both the “No Meat Today” subfolder and “No Meat Today Watch App Extension”.
After playing with upload/download a bit, I ended up with this configuration.
{
"writeKey": "my-write-key",
"readKey": "my-read-key",
"upload": {
"files": [
{
"type": "ios-strings",
"pattern": "No Meat Today/Base.lproj/Localizable.strings",
"path": "No Meat Today"
},
{
"type": "ios-strings",
"pattern": "No Meat Today/fr.lproj/Localizable.strings",
"path": "No Meat Today",
"lang": "fr"
},
{
"type": "ios-stringsdict",
"pattern": "No Meat Today/Base.lproj/Localizable.stringsdict",
"path": "No Meat Today"
},
{
"type": "ios-stringsdict",
"pattern": "No Meat Today/fr.lproj/Localizable.stringsdict",
"path": "No Meat Today",
"lang": "fr"
},
{
"type": "ios-plist",
"pattern": "No Meat Today/Base.lproj/Silliness.plist",
"path": "No Meat Today"
},
{
"type": "ios-plist",
"pattern": "No Meat Today/fr.lproj/Silliness.plist",
"path": "No Meat Today",
"lang": "fr"
},
{
"type": "ios-strings",
"pattern": "No Meat Today Watch App Extension/en.lproj/Localizable.strings",
"path": "No Meat Today Watch App Extension"
},
{
"type": "ios-strings",
"pattern": "No Meat Today Watch App Extension/fr.lproj/Localizable.strings",
"path": "No Meat Today Watch App Extension",
"lang": "fr"
}
]
},
"download": {
"files": "${path}/${iosLprojFolder}/${file}"
}
}
As you can see, unlike the proposed configuration I use a path variable so that I can support the same Localizable.strings
file in two different subfolders, which means I can translate all extensions (Watch App, Widget…).
The app’s default language is English, and my app is localised in French, which means I already had translated texts. The above configuration allowed me to upload my translations in one go.
Does it mean I’ll have to add new rows for each new language? I don’t think so.
If you edit a translation file and call localazy upload
it will push the edit translation for review, which is quite nice. While I will probably want to do that for French (since it’s my native language and I may spot typos or find better translations while coding), it’s unlikely that I will try to edit strings in a language that I don’t speak. So for these other languages, I will only download and not upload, since I won’t do any changes to these files.
Lingo: before you read the rest
First, a reminder of what a localisation in a strings file looks like and some terms used thereafter.
/* This is a comment */
"a.key" = "This is a key";
- translation notes: the comment / first line
- key: the left hand side of the 2nd line, a uniq string to identify your text across all languages
- phrase: the right hand side of the 2nd line
- source language: the language used in your Base.lproj files
- source phrases: the phrases in your source/base language
Translating
This is what the UI looks like. I think it’s pretty straightforward. But more importantly, I invited a translator who is not a dev and they found it easy to use as well.
Some things to notice:
- everything that can help is there: the key, the source phrase, the translation notes, even the file path
- at the bottom you can see 2 types of suggestions: ShareTM is the database of translations from other apps/devs who decided to share their translations, and the other one is an automatic translation. This is really helpful to get a first quick & dirty translation of each phrase.
- I love bad puns
Good to know & other tips
Things below should help you get started, I figured some of it by trial and error, and the rest by talking with someone in the team (🙌 Vaclav!)
Your default language is kinda hidden
When you first upload your strings, you can be surprised that you don’t find your base/source language. It took me a while to see it, but your source phrases are hidden behind the little sandwich menu next to the mention of your source language.
When I first uploaded my strings, I only found the French language and couldn’t figure out where the English phrases were. I even tried to add en_US as a language before I realised the source language wasn’t treated as the other languages.
I mentioned this to the team and apparently they’ll try to improve this.
Know that another way to access your source phrases is by heading to the “File management” section.
You will lose control over the format of your files (but not the Base one 🙌, and it’s OK anyway)
Up until now, I would make sure my files were organised in the same way, having the same number of lines, spaces, comments. I would then open both language files simultaneously and modify them at the same time.
The identical number of lines especially made it easier to spot discrepancies, which meant untranslated keys.
So naturally, when I started searching for a service to handle translations, I imagined there might be one that would scan my files, keep the keys exactly where they were (append the missing ones) and only change the phrases/translations.
But, this is not how things work.
Instead, each time you download the translations, the language files are overwritten and keys are sorted alphabetically.
I can live with that, I guess. I mean, as I explained above, I’m unlikely to modify phrases in languages other than English and French.
Deleting a key doesn’t delete it in the base language
By default, base files are not overwritten, which means that if you delete a key, it will be deleted in all your language files except the base one, where you’ll have to delete it manually.
This is a good thing, because it means that you can format your base file as you want.
In particular, you can make use of // MARK : - Section
to make it easier to navigate your Localizable.strings.
If you do use these, make sure you add a different comment (even an empty one such as /**/
) above the first key so that the MARK doesn’t show up as translation notes.
Finally, if you do want to overwrite the files in your base language, you can for it by setting the includeSourceLang in your config file.
What if I change a translation in the source language?
This is something I was worried about and it works just as you would expect: when you edit a translation, all languages see a notice that the source phrase was changed, so that they can be changed if necessary.
If you do it from the web, you get a bit more flexibility because you can decide whether existing translations need to be updated or not.
But, I wouldn’t do it from there, because, remember, the source phrases are not downloaded, so you’d have to change it manually in Xcode.
What if I change a translation in a language that is not the source language?
If you change a translation in Xcode, say in the fr.lproj/Localizable.strings, and then run localazy upload
, it will upload the translation for review but it won’t be taken into account until your review it
This means that if you run localazy download
before you review the change on the web, your change will be overwritten.
But what if you run localazy upload
again before you review it, will you lose your change? No, because there is a versioning system and you’ll still be able to find your unreviewed change. 💪
There is a Glossary
You can add terms that require extra context or attention in a Glossary.
For instance, I invented the term “Cowliday” and added it to the Glossary to explain it and give some instructions that will show up each time the term is used.
When translating a phrase, the term is highlighted and hovering it will show the comments.
Parting notes
- Localazy supports all the file formats I expected it to, and hopefully txt support will arrive some day to simplify App Store marketing localisation
- It’s free for apps with less than 200 phrases, and affordable for small apps plus there are ways to get free phrases
- CLI is a charm
- Setup is pretty easy if you follow the configuration above
- The UI to translate is reactive and works well, and is easy to use for non-devs (well, I tested with a single person for now)
- Register using my referral link
Final note: if you want to help translate No Meat Today in your language, I’m looking for volunteers.
Top comments (0)