0021: Offerwall Localization Vertical Slice
STATUS
Accepted
CONTEXT
Adgem identified a significant revenue opportunity through localization of offerwall content for international markets. The primary driver was working with publisher Shopback who required localized offers in Traditional Chinese and other languages.
The need for dynamic, scalable localization became apparent as manual translation workflows would not be sustainable for growing international business requirements. This led to the decision to implement a comprehensive localization feature as a vertical slice, covering the full stack from data storage to AI-powered translation services.
Business Requirements
- Support for Traditional Chinese initially (Shopback requirement)
- Extensible architecture to support additional languages (German, and others)
- Capability of translating dynamic content using AI services
- Fallback mechanisms for translation failures
- Feature flag controlled rollout
- Minimal impact on existing functionality
Scope of Localization
Based on discovery with Shopback and analysis of offerwall content, the following fields were identified for translation:
Campaign Offerwall Creative:
- basic_requirements
- offer_instructions
Goal:
- description
Campaign: - No fields required for translation
Considered Options
Option 1: Third-Party Translation Service Integration
- Integration with services like Crowdin and Loom
- Real-time translation on content delivery
- Caching layer for translated content
Pros: - Established translation services - Multiple service options - Time to integration - More-streamlined human review process - More granular model selection flexibility
Cons: - Higher cost - Limited customization for domain-specific terminology - Vendor lock-in concerns - Variable quality for specialized content - Selling features that we don't really need
Option 2: OpenAI-Powered Translation Service (Selected)
- Custom
TranslationServiceusing OpenAI API - Integration with
spatie/laravel-translatablepackage
Pros: - High-quality, context-aware translations - Language specific prompts to promote better prompt engineering - Can incorporate business rules and tone preferences - Future extensibility for other AI capabilities
Cons: - Dependency on OpenAI service availability - Potential for temperature to introduce variance in translations
DECISION
We chose Option 2: OpenAI-Powered Translation Service with the following architectural approach:
Accessing and Mutating Translatable fields
- Selected
spatie/laravel-translatablepackage to store and retrieve data. Spatie package was selected over others due to their reputation and maintenance quality - Converted targeted fields to JSON format storing translations as
{"en": "English text", "zh-TW": "Traditional Chinese text"} - Needed to override
getTranslationacccessors to allow backwards compatibility (e.g. support both goal description both as a string or as a translation json).
With these changes to the Eloquents model(s) in place, it's easy to update content, even if you don't know that the field is translatable. This
$goal->description = 'foo';
$goal->save();
updates the goal's en key, resulting in {"en": "foo", "zh-TW": "Traditional Chinese text"}.
Similarly, accessing a model is also easy. Just use the accessor like normal:
$goal->description; // returns 'foo'
In both cases, the language that's accessed / mutated is whatever is set in the App::Locale. By default, this in en. But this system makes it easy to service requests in foreign locales; simply set the locale at the beginning of the thread, and that locale will be used for the entirety of the request lifecycle.
App::setLocale('zh-TW');
$goal->description; // returns 'Traditional Chinese text'
Generating Translations
1. Translation Triggers
One of these four actions will trigger a (re)translation
- BulkTranslateCampaigns command
- TranslateCampaignsForApp command
- CampaignController@update - when a campaign is updated in the adgem dashboard and one of the (English) value of one of the translatable fields changes
- CampaignController@store - when a campaign is created (via clone or import from Tune) in the adgem dashboard
2. TranslateCampaignJob
- Queued background job for translating campaign content
- Input: Campaign ID + target locale(s)
- Processing: Translates all translatable fields across campaign's offerwall creative and goals
3. TranslationService
- Wrapper around OpenAI API calls
- Input: Instructions to translate a string from English to desired language
- Output: Translated string in target language
- Preservation of placeholder values (e.g.,
{attribution_window}) - Error handling and retry logic
Configurable Features:
- Language specific prompts to better allow prompt engineering
- Model Parameters
NOTES
References
- ADR-0025: Introduction of spatie/laravel-translatable package
- OpenAI API Documentation
- Spatie Laravel Translatable Documentation
- PR #60: docs(ADR-0026): Offerall Localization - Vertical Slice
- PR #68: fix: Flatten indexes of docs to prep for auto-index merge
- PR #127: docs: backfill PR reference links for existing ADRs
Related Jira Issues
- CAMP-312: Offerwall Localization Discovery & V1 Implementation (Epic)
- CAMP-317: Create Feature Flag for Campaign Delivery Localization
- CAMP-318: Update
Goalto be translatable - CAMP-327: Feature documentation for localization
- CAMP-328: Update dynamic translation (Traditional Chinese feedback)
- CAMP-336: ADR of impact of spatie/laravel-translatable
- AGPI-1356: Language Localization - Create TranslationService component
- AGPI-1357: Language Localization - Create TranslateCampaignJob component
Original Author
Ben Giese
Approval Date
08/07/2025
Approved By
Liam Mohebbi, Ron White
APPENDIX
Supported Languages (Initial)
- English (en) - Source language
- German (de)
- Traditional Chinese - Taiwan (zh-TW)
- Traditional Chinese - Hong Kong(zh-HK)