Our Gravity Forms add-ons are used all over the world on sites in 112 languages. For 10 years, we struggled to provide users translations in even the most popular languages. Now, with AI, we have a way to do just that.
In the past, we have tried to incentize translation by humans, we have paid for human translations, and it is expensive, time-consuming, and hard to justify the cost. Those problems disappear when using AI.
We have translated GravityView into 23 languages. Historically, this would have cost approximately $40,000…now it cost us $50.
Why translate plugins?
It’s more professional. If I’m buying a plugin from a developer in France, I expect that it is translated into English. Translations in English-native WordPress plugins into other languages is not as common, so I believe offering such translations gives GravityKit a professional edge.
It’s a nicer experience to have native language support. I want our customers to feel at home when using our products. Although major browsers such as Chrome, Safari, and Firefox include translations, I don’t like the idea of our users needing to toggle a translation in order to use our software.
Browser language support is incomplete. Firefox, for example, lacks support for Persian translations. Persian is our second most popular language by number of installations. We can’t assume every language is supported.
Translation teams are hard to organize
Incentivizing translation
For 10 years, GravityKit has offered an incentive to people to translate our plugins: translate our plugins in your language and you will receive a free All Access license worth hundreds of dollars. When users or customers asked us for translations, we would tell them this and sign them up for our translation service.
We have 30 translators, but only a couple languages being actively maintained. There are a few more languages that sporadically get updated every two years.
This system was never perfect, but it gave a path to translation for those who truly cared.
Paying for translations
Every few years, I would get frustrated with the slow pace of our translations and then go to Fiverr and pay multiple translators to translate our most popular plugin (GravityView) into different languages based on popularity. This would cost $200–$350 per translation, which was still cheaper than many of the translation services or agencies available for hire.
Paying for translations never made sense financially. The multiple times I wanted to formalize the process over the years, I would reach out to translation services and describe what I want: I want them to translate our plugins now, and then also translate new strings as they are added and change over time.
The translation services would reply “Sure…” and then would share their rates for the service: from $0.09–$0.40 per word, per language. That’s $800–$3,600 per language to get started, and possibly hundreds of dollars for an update that has lots of strings. It never made sense to pursue.
Human-powered translation prevents code changes
I have empathy for translators, so I always do my best to not change any translation strings in our code. I have often kept labels the same instead of changing them so that I wouldn’t create work for translators.
An example of this is standardizing GravityView’s settings: we have many strings that are in title case, and some settings that are in sentence case. We know we should use just one (there are arguments for each), but I haven’t wanted to make this change because it would mean our translators would need to update the strings: changing the capitalization of a word needs a new translation.
The financial demands and coordination required to streamline our translation process made it a real challenge. We needed a new approach—enter OpenAI.
Uploading .po
files to LLMs
I attempted to upload the entire file to both ChatGPT and Claude, but they could only translate a few lines before stopping. It would give me messages like this:
(The rest of the translations follow the same pattern.)
Please note that due to the length of the file, I've only provided a partial translation. Let me know if you need the full translation saved or any specific sections to be emphasized.
I finally decided to go looking for libraries that did what I needed. I found a few that didn’t work for me, but then I found one that did: gpt-po.
Discovering gpt-po
gpt-po
is a command-line script that uses the OpenAI API to translate each of the strings in your plugin’s translations file. It goes line-by-line and requests a translation. This is the tool I used to translate our plugins.
How to use the script
If you’re looking for full instructions, please check out the official gpt-po readme! This is just a summary.
First, open the application you use to access your computer’s command line. On the Mac, the default application is called Terminal.
Then, change directories into the folder your translations live in.
cd path/to/folder/with/translations
You will need an OpenAI API key for your requests to process at any reasonable speed. I suggest you get one! Once you have one, enter the code below, replacing {key}
with your API key.
export OPENAI_API_KEY={key}
Now create a new file for each language you want to translate. If your source translation file is named gravitykit-gk-gravityview.po
and you want to translate it to Finnish (with a language code of fi
), duplicate the original file and rename it by adding -fi
to the end of the file name (gravitykit-gk-gravityview-fi.po
).
Once you have the file, tell gpt-po
to translate that file by passing the file name to the script:
gpt-po translate --po gravitykit-gk-gravityview-fi.po --lang=FI
If it works, it should show you a message that has a progress indicator:
When the script finishes, it will show you a message “done.” You can then bundle the file with your plugin, upload to WordPress Translations, or upload it to TranslationsPress (which is what we do).
Nice things about gpt-po
I’m very happy with the script:
- It’s very smart about keeping placeholders: We use both
sprintf()
andstrtr()
to translate, and in both cases it keeps the syntax intact. This has been a struggle for human translators. It’s very easy to misplace a%
or a$
, which then may trigger a fatal error for users in a certain language! - It’s very, verrrry afforable: Using the OpenAI API cost us less than $5 per language!
- I could translate multiple languages at once: At one point, I had four tabs open in my Terminal application, each translating different languages.
- It supports regional localizations: Languages that are used globally often have regional differences. These differences are supported in WordPress. Want to use Brazilian Portuguese? Pass
--lang=pt-BR
to the script. Want to use European Portuguese? Pass--lang=pt
. - You can use different models: I chose to translate using the default model (
gpt-4o
) because I wanted the best available translation, but it would have been cheaper (and probably just as good) if I had used another model likegpt-4o-turbo
.
Results of translating GravityView using AI
A few years ago, this translation would have cost us approximately $40,000…now it cost us $50.
Thanks to OpenAI and ChatGPT, GravityView (our most popular plugin) is now fully translated in 23 languages. A few years ago, this translation would have cost us approximately $40,000…now it cost us $50. Our translation workflow is now 1/800 of the price, requires no back-and-forth, and is (mostly) error-free.
Languages supported:
- Arabic
- Bengali
- Chinese
- Danish
- Dutch
- Finnish
- French
- German
- Hebrew
- Hungarian
- Italian
- Japanese
- Korean
- Norwegian
- Persian
- Polish
- Portuguese
- Portuguese (Brazil)
- Romanian
- Russian
- Spanish
- Swedish
- Turkish
Nothing is perfect, so when a user reports errors in our translations, we will fix them. However, we now, at least, have a translation to start with.
Next steps?
Adding translation glossaries
You can define a glossary (dictionary in gpt-po
terms) for each language. For example, languages may literally translate “setup wizard” as “setup mystical magician” when that’s not what you mean. WordPress.org has glossaries per-locale that can be downloaded and translated into the JSON format necessary. That seems like a good idea. If I do this, I’ll share the files.
Automating the process
It would be great to have this happen automatically during our plugin build process. We use CircleCI as our CI/CD tooling, so we would add a script there. We already build the translation file and automatically upload it to TranslationsPress. The next step is to translate each file and push to the service as well.
Addenda
The ethics of using AI for translations
In any conversation about using AI as a tool, it’s important to consider the ethical implications of AI. In the case of translations, I think translation is a straightforward use case that represents the forward march of progress rather than the replacement of a creative act.
Just as editors were replaced with spell-check, so too are translators being replaced by AI. Translation is already built into browsers by the browser vendors, and those translations are powered by AI technology as well.
Our translation service
Our translations are powered by TranslationsPress. It runs the same software that powers the Translating WordPress project: GlotPress. TranslationsPress lacks the network effect of having hundreds of thousands of users and a team of translation moderators. However, it has a nice feature that, in some ways, makes up for that: translation memory.
Translation memory allows sharing translations that were created by other plugins that also use TranslationsPress. We benefit from translations by Gravity Forms and Yoast because they both use the service. If they have strings that match the exact text we want to translate, it becomes available to us. The downside? The translation memory feature requires a human clicking each translation and approving it. This can be tedious and time-consuming. We have asked for auto-approval mechanisms and TranslationsPress has said it’s on their feature request backlog.
TranslationsPress has been fine. The service itself has been very stable for serving our translation strings. I’ve never noticed downtime.
When uploading translations, the TranslationsPress server often crashes. Their server appears very underpowered. Since this happens each time I upload a translation file, I end up having to upload it multiple times to ensure the translations are all imported.
Translations on WordPress.org
If you have a plugin hosted on WordPress.org, one of the benefits is that your plugin is translatable by all users via the Translating WordPress team.
This comes with some downsides, including that the WordPress Translations team can decide that your translations don’t meet their criteria. If they feel like you’re using slang, or if you’re not using “glossary terms” properly (for example, if you were to write “AJAX” instead of “Ajax”), they will flag the translation and may reject it.
If you want to use AI for translations hosted by WordPress.org, make sure you address the glossary terms or they may reject your submissions.
A note on using strtr()
instead of sprintf()
We started using the PHP function strtr()
for translation strings instead of sprintf()
in part because of fatal errors caused by broken translations. If a single “%
” was out of place, it could break an entire site.
Using strtr()
has the following benefits:
- If a string is missing the translation placeholder, nothing breaks—only an untranslated placeholder will be shown.
- It has the same performance as
sprintf()
(0.008 seconds difference over 100,000 iterations in PHP 7.4). - It’s much more readable to have
[error_message]
in a string than it is to have “%2$s
”. - The function name represents what it does.
strtr()
is short for string translate…that’s more aligned with what we’re doing, not just printing a formatted string likesprintf()
.
One thing sprintf()
has going for it: replacements are type-safe (%d
will only be replaced by a number and %s
only a string). But we do so much type checking in our code that this is mainly a hypothetical benefit.
Final thoughts
Using the OpenAI API to translate our Gravity Forms add-ons has unlocked the ability to make our plugins accessible to users in speaking different languages at a fraction (again 0.125% 🤯) of traditional costs.
We will be automating this process further and I will share more information when that happens, but for now, it’s a great start to making our products. We hope you enjoy! Or, in Bengali, আমরা আশা করি আপনি উপভোগ করবেন 🌏
Helpful tips right in your inbox.
Subscribe to our weekly newsletter for tips, special offers, and more!
Helpful tips right in your inbox.
Subscribe to our weekly newsletter for tips, special offers, and more!