Crunchyroll iOS i18n

Cristian Lupu
Crunchyroll
Published in
4 min readOct 29, 2021
Crunchyroll iOS

Introduction

In order to launch the new Crunchyroll iOS app into international countries we needed to implement internationalization, or for short i18n. We pursued the following objectives for selecting an i18n platform:

  • Our app platforms (web, mobile, living room) should have almost the same flow of adding and modifying the strings.
  • Load the new and updated strings on each app startup.
  • Allow the team of translators to review and sign off the updated strings.
  • Possibility to download translations and bundle them into the app in case of offline mode.

After extensive internal research on various TMS services available we decided to go with Transifex service.

A bit of information about Transifex

Transifex is a Localization Automation Platform that helps developers and marketers publish digital content across multiple languages.

It has a substantial number of features:

  • File & content hosting. Keep all translations in one centralized platform.
  • Team management & collaboration. There are 6 user roles in Transifex.
  • Powerful and easy solution for Content Operations.
  • Notification system: Email notifications, In-app notifications, Watch notifications, Webhooks, Slack integration.
  • Detailed insights & reporting.
  • HTML and variable placeholders.
  • Working with Plurals and Genders translations.
  • Translation Memory to allow consistency of strings between the different platform projects.
  • Many supported files formats, including YAML, XHTML, XLIFF, PO, STRINGS, and PLIST.

Transifex has 6 user roles:

  • Organization Administrator
  • Project Maintainers
  • Team Manager
  • Language Coordinator
  • Reviewers
  • Translator

Transifex’s Version Control

The biggest downside of Transifex that affects all platforms is the missing version control of localizations. Imagine you released a version that has a translation with some unique key. In the next version, you realize that key became useless and you think of removing it. Because of missing version control, the removal of the key will affect all previous app versions because the app relies on the downloaded translations as the source of truth. So when the app will use NSLocalizedString(“that_removed_key”) you will most probably get an empty string. In order to support the previous versions and to move forward with key deprecations we move the old, unused keys under a new section in the .strings file:

// MARK: - Deprecated"deprecated_key" = "translated_string";

And for each version bump we move unused keys under the previous version section:

// MARK: - 4.0.0"deprecated_key_in_4.0.1" = "translated_string";

Crunchyroll iOS localizations upload/download flow

The flow is the following:

  1. The product owner elaborates a new feature, which probably contains some text (button, label, etc.) in English.
  2. Dev team, as a part of the development process, adds the provided text to locale files, defines new translation keys. When the PR passes the validation, the locale files get merged to the main branch.
  3. When a new PR is merged to main, GitHub will trigger a Jenkins job responsible for uploading translations into the iOS Transifex project.
  4. The uploader Jenkins job checkouts to the project’s main branch and uploads the locale files to Transifex using Transifex CLI.
  5. The team of translators will translate, and review the strings.
  6. Transifex triggers the following webhooks:
  • Translation completed— it’s fired whenever a new translation is added and triggers a Jenkins job which is responsible for retrieving the strings from Transifex and uploading them to the staging S3 bucket.
  • Review completed — it’s fired whenever a translation gets approved and triggers the deployment of the reviewed strings to the production S3 bucket.

7. When locale files and their translations will reach the S3 bucket, clients will be able to download them using the S3 bucket URL.

S3 bucket for the production Crunchyroll iOS localizations
S3 bucket for the production Crunchyroll iOS localizations
Inside of a lproj folder
Inside of a lproj folder

Each locale is stored in a .lproj folder that contains:

  • InfoPlist.strings — localizations for strings from Info.plist file.
  • Localizable.strings — singular localizations.
  • Localizable.stringdict — plural localizations.

Before releasing a new version we bundle the translations in the case when the Crunchyroll iOS app will not have a network connection. We are using Transifex CLI to download them as .strings, .stringsdict files, and then bundle them as resource files. The InfoPlist.strings file is copied into the main bundle because you can’t reference it at runtime.

Consuming strings at runtime

On each app startup, we download the localization files for the current preferred locale. As InfoPlist.strings is not needed at runtime, we use only the Localizable.{strings, stringsdict} files. We save them in a {locale}.lproj folder within a Translations.bundle that acts like a folder.

Inside of Crunchyroll iOS app documents folder
Inside of Crunchyroll iOS app documents folder

After the localizations are downloaded we now can use the NSLocalizedString function to get the translation.

public func NSLocalizedString(
_ key: String,
tableName: String? = nil,
bundle: Bundle = Bundle.main,
value: String = "",
comment: String
) -> String

As these localizations are not in the main bundle, we can’t use that function with the default parameters. We shall create a new Bundle instance with the Translations.bundle + {locale}.lproj URL —the local folder created in the app’s Documents folder, and then pass the bundle instance to the NSLocalizedString as a parameter. We also shall pass the tableName parameter, and for our case it’s Localizable.

Conclusion

We were able to scale the Transifex across many projects Web, iOS, Android, and LRX. We’ve been using it for more than 2 years in production and we are happy with the choice we made.

--

--