"""
Localization helper module for App Store Optimization.
Manages multi-language ASO optimization strategies.
"""
from typing import Dict, List, Any, Optional, Tuple
class LocalizationHelper:
"""Helps manage multi-language ASO optimization."""
# Priority markets by language (based on app store revenue and user base)
PRIORITY_MARKETS = {
'tier_1': [
{'language': 'en-US', 'market': 'United States', 'revenue_share': 0.25},
{'language': 'zh-CN', 'market': 'China', 'revenue_share': 0.20},
{'language': 'ja-JP', 'market': 'Japan', 'revenue_share': 0.10},
{'language': 'de-DE', 'market': 'Germany', 'revenue_share': 0.08},
{'language': 'en-GB', 'market': 'United Kingdom', 'revenue_share': 0.06}
],
'tier_2': [
{'language': 'fr-FR', 'market': 'France', 'revenue_share': 0.05},
{'language': 'ko-KR', 'market': 'South Korea', 'revenue_share': 0.05},
{'language': 'es-ES', 'market': 'Spain', 'revenue_share': 0.03},
{'language': 'it-IT', 'market': 'Italy', 'revenue_share': 0.03},
{'language': 'pt-BR', 'market': 'Brazil', 'revenue_share': 0.03}
],
'tier_3': [
{'language': 'ru-RU', 'market': 'Russia', 'revenue_share': 0.02},
{'language': 'es-MX', 'market': 'Mexico', 'revenue_share': 0.02},
{'language': 'nl-NL', 'market': 'Netherlands', 'revenue_share': 0.02},
{'language': 'sv-SE', 'market': 'Sweden', 'revenue_share': 0.01},
{'language': 'pl-PL', 'market': 'Poland', 'revenue_share': 0.01}
]
}
# Character limit multipliers by language (some languages need more/less space)
CHAR_MULTIPLIERS = {
'en': 1.0,
'zh': 0.6, # Chinese characters are more compact
'ja': 0.7, # Japanese uses kanji
'ko': 0.8, # Korean is relatively compact
'de': 1.3, # German words are typically longer
'fr': 1.2, # French tends to be longer
'es': 1.1, # Spanish slightly longer
'pt': 1.1, # Portuguese similar to Spanish
'ru': 1.1, # Russian similar length
'ar': 1.0, # Arabic varies
'it': 1.1 # Italian similar to Spanish
}
def __init__(self, app_category: str = 'general'):
"""
Initialize localization helper.
Args:
app_category: App category to prioritize relevant markets
"""
self.app_category = app_category
self.localization_plans = []
def identify_target_markets(
self,
current_market: str = 'en-US',
budget_level: str = 'medium',
target_market_count: int = 5
) -> Dict[str, Any]:
"""
Recommend priority markets for localization.
Args:
current_market: Current/primary market
budget_level: 'low', 'medium', or 'high'
target_market_count: Number of markets to target
Returns:
Prioritized market recommendations
"""
# Determine tier priorities based on budget
if budget_level == 'low':
priority_tiers = ['tier_1']
max_markets = min(target_market_count, 3)
elif budget_level == 'medium':
priority_tiers = ['tier_1', 'tier_2']
max_markets = min(target_market_count, 8)
else: # high budget
priority_tiers = ['tier_1', 'tier_2', 'tier_3']
max_markets = target_market_count
# Collect markets from priority tiers
recommended_markets = []
for tier in priority_tiers:
for market in self.PRIORITY_MARKETS[tier]:
if market['language'] != current_market:
recommended_markets.append({
**market,
'tier': tier,
'estimated_translation_cost': self._estimate_translation_cost(
market['language']
)
})
# Sort by revenue share and limit
recommended_markets.sort(key=lambda x: x['revenue_share'], reverse=True)
recommended_markets = recommended_markets[:max_markets]
# Calculate potential ROI
total_potential_revenue_share = sum(m['revenue_share'] for m in recommended_markets)
return {
'recommended_markets': recommended_markets,
'total_markets': len(recommended_markets),
'estimated_total_revenue_lift': f"{total_potential_revenue_share*100:.1f}%",
'estimated_cost': self._estimate_total_localization_cost(recommended_markets),
'implementation_priority': self._prioritize_implementation(recommended_markets)
}
def translate_metadata(
self,
source_metadata: Dict[str, str],
source_language: str,
target_language: str,
platform: str = 'apple'
) -> Dict[str, Any]:
"""
Generate localized metadata with character limit considerations.
Args:
source_metadata: Original metadata (title, description, etc.)
source_language: Source language code (e.g., 'en')
target_language: Target language code (e.g., 'es')
platform: 'apple' or 'google'
Returns:
Localized metadata with character limit validation
"""
# Get character multiplier
target_lang_code = target_language.split('-')[0]
char_multiplier = self.CHAR_MULTIPLIERS.get(target_lang_code, 1.0)
# Platform-specific limits
if platform == 'apple':
limits = {'title': 30, 'subtitle': 30, 'description': 4000, 'keywords': 100}
else:
limits = {'title': 50, 'short_description': 80, 'description': 4000}
localized_metadata = {}
warnings = []
for field, text in source_metadata.items():
if field not in limits:
continue
# Estimate target length
estimated_length = int(len(text) * char_multiplier)
limit = limits[field]
localized_metadata[field] = {
'original_text': text,
'original_length': len(text),
'estimated_target_length': estimated_length,
'character_limit': limit,
'fits_within_limit': estimated_length <= limit,
'translation_notes': self._get_translation_notes(
field,
target_language,
estimated_length,
limit
)
}
if estimated_length > limit:
warnings.append(
f"{field}: Estimated length ({estimated_length}) may exceed limit ({limit}) - "
f"condensing may be required"
)
return {
'source_language': source_language,
'target_language': target_language,
'platform': platform,
'localized_fields': localized_metadata,
'character_multiplier': char_multiplier,
'warnings': warnings,
'recommendations': self._generate_translation_recommendations(
target_language,
warnings
)
}
def adapt_keywords(
self,
source_keywords: List[str],
source_language: str,
target_language: str,
target_market: str
) -> Dict[str, Any]:
"""
Adapt keywords for target market (not just direct translation).
Args:
source_keywords: Original keywords
source_language: Source language code
target_language: Target language code
target_market: Target market (e.g., 'France', 'Japan')
Returns:
Adapted keyword recommendations
"""
# Cultural adaptation considerations
cultural_notes = self._get_cultural_keyword_considerations(target_market)
# Search behavior differences
search_patterns = self._get_search_patterns(target_market)
adapted_keywords = []
for keyword in source_keywords:
adapted_keywords.append({
'source_keyword': keyword,
'adaptation_strategy': self._determine_adaptation_strategy(
keyword,
target_market
),
'cultural_considerations': cultural_notes.get(keyword, []),
'priority': 'high' if keyword in source_keywords[:3] else 'medium'
})
return {
'source_language': source_language,
'target_language': target_language,
'target_market': target_market,
'adapted_keywords': adapted_keywords,
'search_behavior_notes': search_patterns,
'recommendations': [
'Use native speakers for keyword research',
'Test keywords with local users before finalizing',
'Consider local competitors\' keyword strategies',
'Monitor search trends in target market'
]
}
def validate_translations(
self,
translated_metadata: Dict[str, str],
target_language: str,
platform: str = 'apple'
) -> Dict[str, Any]:
"""
Validate translated metadata for character limits and quality.
Args:
translated_metadata: Translated text fields
target_language: Target language code
platform: 'apple' or 'google'
Returns:
Validation report
"""
# Platform limits
if platform == 'apple':
limits = {'title': 30, 'subtitle': 30, 'description': 4000, 'keywords': 100}
else:
limits = {'title': 50, 'short_description': 80, 'description': 4000}
validation_results = {
'is_valid': True,
'field_validations': {},
'errors': [],
'warnings': []
}
for field, text in translated_metadata.items():
if field not in limits:
continue
actual_length = len(text)
limit = limits[field]
is_within_limit = actual_length <= limit
validation_results['field_validations'][field] = {
'text': text,
'length': actual_length,
'limit': limit,
'is_valid': is_within_limit,
'usage_percentage': round((actual_length / limit) * 100, 1)
}
if not is_within_limit:
validation_results['is_valid'] = False
validation_results['errors'].append(
f"{field} exceeds limit: {actual_length}/{limit} characters"
)
# Quality checks
quality_issues = self._check_translation_quality(
translated_metadata,
target_language
)
validation_results['quality_checks'] = quality_issues
if quality_issues:
validation_results['warnings'].extend(
[f"Quality issue: {issue}" for issue in quality_issues]
)
return validation_results
def calculate_localization_roi(
self,
target_markets: List[str],
current_monthly_downloads: int,
localization_cost: float,
expected_lift_percentage: float = 0.15
) -> Dict[str, Any]:
"""
Estimate ROI of localization investment.
Args:
target_markets: List of market codes
current_monthly_downloads: Current monthly downloads
localization_cost: Total cost to localize
expected_lift_percentage: Expected download increase (default 15%)
Returns:
ROI analysis
"""
# Estimate market-specific lift
market_data = []
total_expected_lift = 0
for market_code in target_markets:
# Find market in priority lists
market_info = None
for tier_name, markets in self.PRIORITY_MARKETS.items():
for m in markets:
if m['language'] == market_code:
market_info = m
break
if not market_info:
continue
# Estimate downloads from this market
market_downloads = int(current_monthly_downloads * market_info['revenue_share'])
expected_increase = int(market_downloads * expected_lift_percentage)
total_expected_lift += expected_increase
market_data.append({
'market': market_info['market'],
'current_monthly_downloads': market_downloads,
'expected_increase': expected_increase,
'revenue_potential': market_info['revenue_share']
})
# Calculate payback period (assuming $2 revenue per download)
revenue_per_download = 2.0
monthly_additional_revenue = total_expected_lift * revenue_per_download
payback_months = (localization_cost / monthly_additional_revenue) if monthly_additional_revenue > 0 else float('inf')
return {
'markets_analyzed': len(market_data),
'market_breakdown': market_data,
'total_expected_monthly_lift': total_expected_lift,
'expected_monthly_revenue_increase': f"${monthly_additional_revenue:,.2f}",
'localization_cost': f"${localization_cost:,.2f}",
'payback_period_months': round(payback_months, 1) if payback_months != float('inf') else 'N/A',
'annual_roi': f"{((monthly_additional_revenue * 12 - localization_cost) / localization_cost * 100):.1f}%" if payback_months != float('inf') else 'Negative',
'recommendation': self._generate_roi_recommendation(payback_months)
}
def _estimate_translation_cost(self, language: str) -> Dict[str, float]:
"""Estimate translation cost for a language."""
# Base cost per word (professional translation)
base_cost_per_word = 0.12
# Language-specific multipliers
multipliers = {
'zh-CN': 1.5, # Chinese requires specialist
'ja-JP': 1.5, # Japanese requires specialist
'ko-KR': 1.3,
'ar-SA': 1.4, # Arabic (right-to-left)
'default': 1.0
}
multiplier = multipliers.get(language, multipliers['default'])
# Typical word counts for app store metadata
typical_word_counts = {
'title': 5,
'subtitle': 5,
'description': 300,
'keywords': 20,
'screenshots': 50 # Caption text
}
total_words = sum(typical_word_counts.values())
estimated_cost = total_words * base_cost_per_word * multiplier
return {
'cost_per_word': base_cost_per_word * multiplier,
'total_words': total_words,
'estimated_cost': round(estimated_cost, 2)
}
def _estimate_total_localization_cost(self, markets: List[Dict[str, Any]]) -> str:
"""Estimate total cost for multiple markets."""
total = sum(m['estimated_translation_cost']['estimated_cost'] for m in markets)
return f"${total:,.2f}"
def _prioritize_implementation(self, markets: List[Dict[str, Any]]) -> List[Dict[str, str]]:
"""Create phased implementation plan."""
phases = []
# Phase 1: Top revenue markets
phase_1 = [m for m in markets[:3]]
if phase_1:
phases.append({
'phase': 'Phase 1 (First 30 days)',
'markets': ', '.join([m['market'] for m in phase_1]),
'rationale': 'Highest revenue potential markets'
})
# Phase 2: Remaining tier 1 and top tier 2
phase_2 = [m for m in markets[3:6]]
if phase_2:
phases.append({
'phase': 'Phase 2 (Days 31-60)',
'markets': ', '.join([m['market'] for m in phase_2]),
'rationale': 'Strong revenue markets with good ROI'
})
# Phase 3: Remaining markets
phase_3 = [m for m in markets[6:]]
if phase_3:
phases.append({
'phase': 'Phase 3 (Days 61-90)',
'markets': ', '.join([m['market'] for m in phase_3]),
'rationale': 'Complete global coverage'
})
return phases
def _get_translation_notes(
self,
field: str,
target_language: str,
estimated_length: int,
limit: int
) -> List[str]:
"""Get translation-specific notes for field."""
notes = []
if estimated_length > limit:
notes.append(f"Condensing required - aim for {limit - 10} characters to allow buffer")
if field == 'title' and target_language.startswith('zh'):
notes.append("Chinese characters convey more meaning - may need fewer characters")
if field == 'keywords' and target_language.startswith('de'):
notes.append("German compound words may be longer - prioritize shorter keywords")
return notes
def _generate_translation_recommendations(
self,
target_language: str,
warnings: List[str]
) -> List[str]:
"""Generate translation recommendations."""
recommendations = [
"Use professional native speakers for translation",
"Test translations with local users before finalizing"
]
if warnings:
recommendations.append("Work with translator to condense text while preserving meaning")
if target_language.startswith('zh') or target_language.startswith('ja'):
recommendations.append("Consider cultural context and local idioms")
return recommendations
def _get_cultural_keyword_considerations(self, target_market: str) -> Dict[str, List[str]]:
"""Get cultural considerations for keywords by market."""
# Simplified example - real implementation would be more comprehensive
considerations = {
'China': ['Avoid politically sensitive terms', 'Consider local alternatives to blocked services'],
'Japan': ['Honorific language important', 'Technical terms often use katakana'],
'Germany': ['Privacy and security terms resonate', 'Efficiency and quality valued'],
'France': ['French language protection laws', 'Prefer French terms over English'],
'default': ['Research local search behavior', 'Test with native speakers']
}
return considerations.get(target_market, considerations['default'])
def _get_search_patterns(self, target_market: str) -> List[str]:
"""Get search pattern notes for market."""
patterns = {
'China': ['Use both simplified characters and romanization', 'Brand names often romanized'],
'Japan': ['Mix of kanji, hiragana, and katakana', 'English words common in tech'],
'Germany': ['Compound words common', 'Specific technical terminology'],
'default': ['Research local search trends', 'Monitor competitor keywords']
}
return patterns.get(target_market, patterns['default'])
def _determine_adaptation_strategy(self, keyword: str, target_market: str) -> str:
"""Determine how to adapt keyword for market."""
# Simplified logic
if target_market in ['China', 'Japan', 'Korea']:
return 'full_localization' # Complete translation needed
elif target_market in ['Germany', 'France', 'Spain']:
return 'adapt_and_translate' # Some adaptation needed
else:
return 'direct_translation' # Direct translation usually sufficient
def _check_translation_quality(
self,
translated_metadata: Dict[str, str],
target_language: str
) -> List[str]:
"""Basic quality checks for translations."""
issues = []
# Check for untranslated placeholders
for field, text in translated_metadata.items():
if '[' in text or '{' in text or 'TODO' in text.upper():
issues.append(f"{field} contains placeholder text")
# Check for excessive punctuation
for field, text in translated_metadata.items():
if text.count('!') > 3:
issues.append(f"{field} has excessive exclamation marks")
return issues
def _generate_roi_recommendation(self, payback_months: float) -> str:
"""Generate ROI recommendation."""
if payback_months <= 3:
return "Excellent ROI - proceed immediately"
elif payback_months <= 6:
return "Good ROI - recommended investment"
elif payback_months <= 12:
return "Moderate ROI - consider if strategic market"
else:
return "Low ROI - reconsider or focus on higher-priority markets first"
def plan_localization_strategy(
current_market: str,
budget_level: str,
monthly_downloads: int
) -> Dict[str, Any]:
"""
Convenience function to plan localization strategy.
Args:
current_market: Current market code
budget_level: Budget level
monthly_downloads: Current monthly downloads
Returns:
Complete localization plan
"""
helper = LocalizationHelper()
target_markets = helper.identify_target_markets(
current_market=current_market,
budget_level=budget_level
)
# Extract market codes
market_codes = [m['language'] for m in target_markets['recommended_markets']]
# Calculate ROI
estimated_cost = float(target_markets['estimated_cost'].replace('$', '').replace(',', ''))
roi_analysis = helper.calculate_localization_roi(
market_codes,
monthly_downloads,
estimated_cost
)
return {
'target_markets': target_markets,
'roi_analysis': roi_analysis
}