In this article, we will design and implement an end-to-end Translations service, including the supporting infrastructure and client integrations for mobile, web, and backend services. We will begin by outlining the requirements and then move on to the system design, covering key components such as: an admin translations portal, a backend Translations service, client pipeline integration, a client-agent/sidecar, and client libraries. We will support translating both application and user-submitted text.
- Requirements
- High Level Design
- Admin Translations Portal
- Backend Service, APIs, and Pub/Sub Integration
- Clients - Enqueueing New Content for Translation
- Static Application Content
- Dynamic User-Generated Content
- Clients - Integrating Translated Content
- Mobile Clients
- Loading Application Translations During the Build Process
- Translation Updates
- Rendering Translations
- Web Clients
- Server-Side-Rendering and Client-Side-Rendering
- Server Sidecar/Agent/Daemon and Server Library
- Backend Service Clients
- Application Content
- User-Generated Content
- Mobile Clients
- Performance Analysis and Conclusion
- Software Architecture - Delivery and Taxi Transport Platform with Gig-Economy Marketplace
- Software Architecture - Mapping, Navigation, and ETA System
- Software Architecture - Metrics and Alarms Monitoring System
- Software Architecture - Payments System
- Software Architecture - Hotel Reservation Booking System
Requirements
Internationalization is a critical requirement for products targeting global customers. It requires replacing hard-coded text strings with tokens to enable translation interpolation, designing fluid user interfaces that accommodate varying text lengths and direction, displaying multiple currencies, and addressing regional legal requirements. Text can originate from two primary sources: application content, often static (e.g. button labels, marketing content), and user-generated content, often dynamic and constantly changing (e.g. user comments). A locale is defined as the combination of a user’s region and language. For this system, translations will be required for each supported locale, with a 2-hour window for Machine Learning / AI to complete translations of newly submitted content and a 48-hour window for content requiring human translator review.
User Requirements
- Support for hundreds of locales, geographic regions, and languages
- Scalability to handle thousands of translations within a single page
- Support for both application content and user-generated content, including static and dynamic text
- Low latency content rendering without negatively impacting performance
- Admin portal for human translators, providing contextual information to ensure high quality translations
- Updates to application content and user-generated content should trigger translations for each supported locale, with ML / AI translations added within 2 hours, and content requiring human translation added within 48 hours
High Level Design
This system will require an Admin Translations Portal for human translators to submit translations. It will require a backend service for serving data to the portal, receiving new translation requests, and sending out translation update events for clients to subscribe to. Clients will require a way to automatically enqueue new untranslated content - the integration will vary based on the type of client: application content within mobile/web apps or user-generated content submitted and persisted within backend services. Client will also require a way to automatically receive and use newly translated content.
Components:
- Admin Translations Portal
- Backend Translations Service
- Mobile and Web Browser Clients
- Backend Service Client
Admin Translations Portal
The administrative portal is where human translators will translate text into a specific locale. Given this is not customer-facing and won’t require sub-second latency or search engine optimization (SEO), a small Single Page Application (SPA) hosted on AWS S3 will be a good solution with simplicity and very low cost. It will leverage OpenID Connect (OIDC) and OAuth for authentication and authorization. This frontend will be written in TypeScript with React.
Three pages will be required:
- Login page: Using OIDC and OAuth for authentication and authorization to produce a JSON Web Token (JWT)
- List page: Displays rows of text requiring translation, with search and filter functionality
- Detail page: Provides a form for translators to view untranslated text, contextual information, and input translated text
Backend Translations Service, APIs, and Pub/Sub Integration
The Translations service is responsible for managing translations, offering APIs for viewing, adding, and updating translations. It will require a database for persisting translation data and an integration with a pub/sub system, such as Kafka, for publishing translation update events to channels which client services may subscribe to.
APIs
-
GET: /translations
- List of translation objects containing title, status, and date updated
-
POST: /translations
- Adds new required translations in bulk
-
GET: /translations/{id}
- Translation object containing title, status, date updated, contextual info, link if available, and any other metadata
-
POST: /translations/{id}
- Upserts translation in DB and pushes event to the Pub/Sub service
Persistence
The database will store translations for querying. Access patterns will consist of key-lookups and paginated lists with filtering. This data will require significant scale, is largely static, and not relational so a NoSQL database will fit well. Given this, we will leverage Amazon DynamoDB. Keys will use UUID’s and objects will contain region, language, contextual info, translation status, original text, and optionally translated text. We can leverage separate tables per locale.
Compute Infrastructure
The backend service could run in a multitude of compute environments ranging from serverless Lambdas, to containers running on Fargate or Kubernetes (EKS), to EC2 virtual machines. Given we require low latency, significant scale, and want to minimize operational load, we will go with Fargate containerized instances for now. AWS Fargate provides a containers-as-a-service platform for quickly deploying containers with autoscaling and minimal operational load. A load balancer will distribute requests across backend containers.
Translation Events
Clients require the ability to send untranslated text and then receive translated text asynchronously once available. There are several patterns we could leverage including: clients polling the Translations service or submitting a webhook for the Translations service to POST back to, or using a Pub/Sub service for publishing translation events. Since (1) it will take up to two days for content to be translated and we will be supporting (2) a large number of clients, Pub/Sub will be a great design pattern for this use-case. Clients will submit translations to the Translations service via API, receive a UUID back, and then receive updates via a Pub/Sub channel for the selected locales.
There are numerous Pub/Sub services we could leverage such as Apache Kafka, Redis Pub/Sub, and even Amazon SNS+SQS. Kafka is a popular event-streaming service perfect for publishing, processing, and subscribing to events. Kafka provides a highly durable disk-based replicated Pub/Sub model which is great for data durability if the service goes down so we will leverage that. Each time content is translated an event will be published with the relevant UUID to the Pub/Sub channel.
Clients - Enqueueing New Content for Translation
Clients require the ability to enqueue new content for translation whenever app changes are merged or users submit content. Though we will leverage AI for translations when possible, humans may need to review or help with some translations which we allow up to 48 hours for so we want to start the translation process as soon as possible.
Static Application Content:
Once code is merged containing new translation tokens, a CI/CD integration will submit the new strings for translation to the Translations service via a POST call to the API. Tokens will include a translation key, default English text, description, and options for pluralization. Later, when translations are received a file per locale will be updated, mapping each translation key to its translation. Upon application startup the locale translation file is loaded into memory for low latency quick lookups.
{t('translationKey', { count: list.count, description: 'lorem ipsum', default: list[i].title })}
Dynamic User-Generated Content:
Enqueuing user-content happens as soon as a backend service finishes processing the request via a translation backend library. After the user-content is persisted to the backend service’s database, the service makes a POST call to the Translations service to add the newly required translation. Later, when translations are received it is added to the service’s database.
Next, we discuss the client integration providing the retrieval and rendering of translated text for clients.
Clients - Integrating Translated Content
There are three types of clients we must consider for rendering translated content: (1) Android and iOS mobile applications, (2) web frontends, and (3) backend services with user-submitted content. We will support preloading translations, as well as dynamically loaded translations. Let’s discuss this in more detail.
Mobile Clients
Loading Application Translations During the Build Process:
Mobile clients will primarily pull translations offline as part of the mobile build process with different packages and preloaded locales published to separate geographies via the Android and Apple app marketplaces. During the packing and publishing workflow a new step will be added which takes tokenized translations, pulls localized translated text, and stores it within the mobile application package/bundle.
Translation Updates:
A mobile library will also be available for updating translations if they are edited or loading locales dynamically if not already preloaded on the mobile application. The mobile application will check for translation updates on startup and then if desired, can optionally subscribe to the translations pub/sub system for ongoing translation updates, though such updates would be uncommon.
Rendering Translations:
When a mobile app page is loaded it simply replaces tokenized text with the already locally stored translations for the specified locale.
Web Clients
Server-Side-Rendering and Client-Side-Rendering:
We need to consider support for both traditional server-side-rendered web clients and the newer style of client-side-rendered single-page-apps (SPAs). Server-side-rendered web clients will leverage a backend server which will generate the webpage and send it to the browser, whereas client-side-rendered web clients will load a content shell from a content-delivery-network (CDN) which will then make JavaScript async network calls to backend service APIs to load data and render the webpage. To support both configurations, we will offer both (1) a server-side translations containerized sidecar and (2) a web library for dynamically loading translations from the browser. A React library with a custom React component will interpolate the translated text when rendering the web page, replacing the translation token.
Server Sidecar/Agent/Daemon and Server Library:
Web server clients will have a separate process as part of a containerized sidecar to (1) first load translations on server startup and persist them on the server, and then (2) dynamically receive new translations as they are available or updated through our Pub/Sub system. In tandem with the containerized translations sidecar, the backend service translations library integrates these stored translations into the app when rendering content.
{
"messageKey": {
"singular": "You have [count] message",
"plural": "You have [count] messages"
}
}
Loading Application Translations During the SPA Build Process:
For web clients using client-side rendering within a Single-Page-Application (SPA), there are two options, either: (1) separate web app artifacts can be generated at build-time per locale or the translations web library can dynamically load translations on page load - the former being more performant with less latency on page load. During the build process separate files are persisted per locale and then loaded within the SPA.
Web Library:
As an alternative, a web client frontend library will be available to dynamically load new translations directly in the browser, subscribed to our Pub/Sub system, though this will not be the preferable option given latency.
Backend Service Clients
Application Content:
Backend service clients will primarily require translations support for dynamic user-submitted content as opposed to static application content. For any static application content, the backend server can simply leverage the same containerized server sidecar as the web server client to load static translations on server startup.
User-Generated Content:
Whenever a backend service receives data from users they will need to translate that data for other users and locales. As noted earlier, the enqueuing of user-content happens as soon as the backend service submits the user content through our translations backend library. The backend service containerized sidecar is subscribed to our translations Pub/Sub system and waits for a translation event.
Whenever the backend service sidecar receives a new translation event with the user-submitted content translated, it adds these into the server backend database. When the backend service loads that data thereafter it can load the data with the specified locale.
Performance Analysis and Conclusion
We covered a variety of use cases, from mobile clients, to server-rendered and client-rendered web clients, to backend service clients. We also discussed the different needs of static application content and dynamic user-submitted content. We designed a system that ensures high performance for a good customer experience by enqueueing new translations as early as possible and loading them asynchronously outside the user flow. By using a Pub/Sub system, we minimize load on the backend Translations service of tens of thousands of clients polling it. By using local client caches, clients can depend on low-latency interpolation of translated text with minimal latency for customers. Altogether this provides a highly-performant translations solution.
This completes this system design to build an end-to-end translations solution, including admin translations portal, backend translations service, client pipeline integration, client sidecar/agent, and client libraries.