Retiring Zettlr Translate
Today, I want to share an announcement with you. Zettlr Translate is going to be retired in the near future. This decision has been a long time in the making, but now is just as good a time as any to tackle this issue. In this blogpost, I want to outline why this decision has been made, how it will affect Zettlr, and what the next steps will be.
What is Zettlr Translate?
If you’re new to the community, you may wonder what Zettlr Translate is to begin with. Zettlr Translate is the translation service that we use in order to translate all the different strings in the app into various languages. I have created that service five years ago in order to make it easy for non-technical users to help with translating the app, since I speak exactly two languages and hence cannot do that by myself. It was my way of enabling many more users to experience Zettlr in their native language. As of today, there are more than 13,000 translations in the database and there are (partial) translations for 38 different languages. That is quite impressive, and I would like to already thank those of you who have at some point participated in making a multi-lingual Zettlr a reality. It really means a lot to me!
Why will Zettlr Translate be retired?
The next question now concerns the fact that I wish to move away from that. In order to answer this question, it makes sense to answer another one first: Why have I created Zettlr Translate in the first place? For that, let me sketch the situation around the app five years ago.
When I started developing Zettlr, one of the first demands of the back then still small and young community was to be able to translate it. The only reference point I had back then for how to do that was the WinterCMS framework (which was called OctoberCMS at that point). It supports translation with a relatively simple JSON format where you put identifiers into the code of your app and then add translation files which resolve those identifiers to actual strings. For example, the identifier dialog.preferences.title
could be resolved to “Preferences” in English and “Einstellungen” in German. Inside the source code, therefore, you would put trans('dialog.preferences.title')
and the app would then look up that identifier and insert the correct string in the language of choice.
Implementing that was straight forward, but the immediate next question was how to enable people to use that information in order to translate. One has to say at this point that while there are some frameworks for doing so out there, none really appealed to me. The standard solution I found back then was gettext
which seemed decent, but (because I didn’t know better back then) appeared to only support C source code. Also, it seemed to raise the bar of actually starting to translate the app, as it would include a lot of technical knowledge I couldn’t expect of my community. Other translation frameworks either required setting up a server or were paid, so it seemed overkill to me. Lastly, I had a lot of time on my hands back then (I was only employed part-time, so a large part of my week was free) and decided it would be a fun exercise.
So I built my own translation service, Zettlr Translate, which had a ton of benefits for the still young app: (1) It was very easy for people to help translate, as no technical knowledge was required (2) it was minimalist with no superfluous functionality that we didn’t need and (3) it was a fun project for me, and I did feel a little bit proud for how well it worked.
Now we can answer the original question: Lots of things have changed in the last five years, and while Zettlr Translate continues to work excellent, there are many reasons for why I don’t want to spend too much time on keeping this up anymore.
The first reason is that I simply don’t have as much time now as I had back when I started to develop Zettlr. As you may know, I started a PhD two years ago, and this severely limits my time. I still find enough free time throughout the week to improve Zettlr, but I can’t keep several projects going. And, realistically, my free time will never be as much as freshly out of university. In other words: While Zettlr remains a high priority for me, I need to cut down on non-essential maintenance work, one of which was the Forum. Instead of maintaining and updating a forum, I switched to GitHub Discussions and Discord which serve similar purposes. The benefit is that I don’t have to worry about keeping some software up to date and it just works. Zettlr Translate now is the second service that may have looked cool but whose costs of upkeeping simply aren’t offset by its use. After all, I did in a certain way reinvent the wheel back then, so it is time to be realistic. Zettlr Translate still works very well, but I cannot guarantee that it will continue to do so forever, and since the database is getting a little bit sluggish due to its size, I figured it is good to cap this time-eating maintenance work.
The second reason for why Zettlr Translate should be replaced is that by now I have five years experience developing applications; experience that I didn’t have five years ago. Back then I had no idea of how to properly set up translations, but now I do. Now I know how gettext
works and how to use it in order to set up an environment where translating Zettlr is possible even for non-technical users, as the environment is widely supported. You may have even heard of the go-to editor “POedit” that allows anyone to edit translatable files with no technical knowledge. gettext
is a standard way of making any application translatable, so instead of reinventing any wheels, sticking to conventions that already exist makes it both safe and easy. An added benefit is that GitHub, where the source code of Zettlr resides, has gotten a ton of new features in the meantime. When I started to develop Zettlr, there was no GitHub Discussions, no issue templates, and no GitHub Actions. All of these and many more improvements that GitHub has introduced over the last five years makes maintaining translations directly with the source code much easier.
A third reason for retiring the service is that by now the community is much larger and more mature. Many more people are flocking in, and I have “proven” myself. Five years ago, Zettlr was not really feature-rich and pretty unknown, so why should anyone bother helping me out? That’s why I tried to keep the bar extremely low for people to start participating. By now, the community knows I’m reliable and that I can deliver well working software, so the incentive to do that one extra step is higher. And by switching to a well-known system for translating text I’m not demanding the impossible. To the contrary: Some may already know how to deal with those *.po
-files that contain the translations. And, if one doesn’t, this newly acquired knowledge helps in translating a ton of other software that all use gettext
. In short: switching to gettext
has benefits beyond just me being able to sleep well again.
A last reason for my decision has to do with efficiency. gettext
is a mature suite of programs that has benefited from many experienced software developers over the years. It supports many more things (such as plurals) which Zettlr Translate never supported and makes it easier for me to add new translations. Because I had to manually add any new translation strings to Zettlr Translate after I introduced them, I grew less and less excited to introduce new strings to the app. Instead of simply writing a component for the graphical user interface, adding translation strings as I needed, I was searching for ways to avoid precisely that. Another reason that made adding new strings unappealing was the ID-system I implemented back then. In hindsight, it doesn’t make sense to put something like trans('dialog.preferences.title')
into the source code instead of just trans('Preferences')
. It was the only way to do it that I knew back then, but it wasn’t a good idea. There is a host of problems with this approach that make it incredibly cumbersome. For example, what if the meaning of one of those identifiers changes slightly? The only way an ID-system gives you to mitigate that is to either nuke all existing translations for the given identifier, or invent a new one. Additionally, such an ID system requires you to always think of a generic identifier instead of literally typing out what the string should say right in the source code. I think I could come up with many more reasons, but you get the idea.
The Next Steps
So what is going to happen next? The first thing I will do right after publishing this blog post is to inform every user registered on Zettlr Translate about this change to give them a chance to react to this. Second, over the coming weeks, I will gradually experiment with setting up gettext
on the repository so that I can automatically generate the necessary files for translating the app. Third, I will export a dump from the Zettlr Translate database and add that to the PO-files so that none of the existing translations get lost. Lastly, I will look into ways for setting up easy to use ways to continue helping with the translation. And then, sometime in Q1 2023, we should have a proper system in place that is stable, robust, and will help us keep the multi-lingual nature of Zettlr for decades to come.
If you either want to continue translating under the new system or would like to start helping to translate the app, just follow either the Twitter or Mastodon accounts of Zettlr where I will post updates, or (even better) join the Zettlr Discord. There, you can even ping me directly if you have any questions regarding this migration. I’m always happy to answer any questions you have, so don’t hesitate – even if you have some critique! For example, if you have ideas of how to make it easy for people to translate parts of the app, or want to discuss how the migration is going, I’m more than happy to talk with you!
Again, I would like to thank the translators for their incredible support and I am looking forward to a better translation experience in the future! The next post here will be the 5-year anniversary post on December 26, 2022, so make sure to have a look. Until then, I wish you all a great December!