View file File name : BLOG_LOCALIZATION_GUIDE.md Content :# Blog Localization & Auto-Translation Guide Complete guide to automatically translating blog posts to multiple languages when creating them. ## Features - ✅ **Auto-translate** blog posts to 15+ languages - ✅ **Multiple translation services**: Google Translate (Free), DeepL (Premium), OpenAI (Premium) - ✅ **Bulk translation** of multiple posts - ✅ **Automatic slug generation** for translated versions - ✅ **Manage translations** - View, edit, or delete translations - ✅ **Translation statistics** - Track translation progress - ✅ **API support** - Fetch translated posts via API ## Supported Languages | Code | Language | Code | Language | |------|----------|------|----------| | es | Spanish | pt | Portuguese | | fr | French | ru | Russian | | de | German | ja | Japanese | | it | Italian | zh | Chinese | | ar | Arabic | ko | Korean | | hi | Hindi | nl | Dutch | | tr | Turkish | pl | Polish | | vi | Vietnamese | | | ## Installation & Setup ### 1. Run Migrations Create the `blog_post_localizations` table: ```bash php artisan migrate ``` This creates the table to store translated versions of blog posts. ### 2. Configure Translation Service Choose your preferred translation service by setting environment variables in `.env`: #### Option A: Google Translate (Free - Recommended for Testing) ```env # No API key needed - uses free MyMemory API BLOG_TRANSLATION_SERVICE=google ``` **Pros:** - Completely free - No API key required - Works immediately - Good for testing and development **Cons:** - Slightly lower quality than paid services - Rate-limited for free users #### Option B: DeepL (Premium) 1. Sign up at https://www.deepl.com/pro-api 2. Get your API key from dashboard 3. Add to `.env`: ```env BLOG_TRANSLATION_SERVICE=deepl DEEPL_API_KEY=your_api_key_here ``` **Pros:** - Highest quality translations - Supports 30+ languages - Professional-grade accuracy **Cons:** - Paid service (~$5-50/month depending on usage) - Requires API key #### Option C: OpenAI / ChatGPT (Premium) 1. Get API key from https://platform.openai.com/api-keys 2. Add to `.env`: ```env BLOG_TRANSLATION_SERVICE=openai OPENAI_API_KEY=sk-your_api_key_here ``` **Pros:** - Highest quality translations - Context-aware - Can handle complex content **Cons:** - Most expensive (~$0.002-0.01 per 1K tokens) - Requires API key and paid account ### 3. Run Database Migration ```bash php artisan migrate ``` ## Usage ### Via Dashboard #### Single Post Translation 1. Go to **Apps → Blog Tab** 2. Create or select a blog post 3. Click **Translate** button 4. Select **translation service** (Google/DeepL/OpenAI) 5. Select **languages** to translate to 6. Click **Start Translation** 7. Wait for completion #### Bulk Translation 1. Go to **Apps → Blog Tab** 2. Click **Bulk Translate All Posts** 3. Select **translation service** 4. Select **languages** 5. Click **Translate All** 6. All published posts will be translated #### View Translations 1. Click on a blog post 2. Scroll to **Translations** section 3. See all available translated versions 4. Click language to view full translation #### Delete Translation 1. In **Translations** section 2. Click **Delete** next to a language 3. Confirm deletion ### Programmatically (PHP) #### Translate a Single Post ```php use App\Models\BlogPost; use App\Services\BlogLocalizationService; $post = BlogPost::find(1); // Translate to specific languages $results = BlogLocalizationService::autoTranslateAndSave( $post, languages: ['es', 'fr', 'de'], // Spanish, French, German translator: 'google' ); // Check results foreach ($results as $language => $result) { if ($result['success']) { echo "✓ Translated to {$language}"; } else { echo "✗ Failed to translate to {$language}: " . $result['error']; } } ``` #### Translate to All Languages ```php $results = BlogLocalizationService::autoTranslateAndSave( $post, languages: [], // Empty array = all languages translator: 'deepl' ); ``` #### Get Translated Post ```php // Get specific language translation $translatedPost = BlogLocalizationService::getTranslatedPost($post, 'es'); echo $translatedPost['title']; // Spanish title echo $translatedPost['content']; // Spanish content ``` #### Get All Translations ```php $translations = BlogLocalizationService::getPostTranslations($post); foreach ($translations as $translation) { echo "{$translation['language']}: {$translation['title']}"; } ``` #### Bulk Translate Multiple Posts ```php $postIds = [1, 2, 3, 4, 5]; $languages = ['es', 'fr']; $results = BlogLocalizationService::bulkTranslate( postIds: $postIds, languages: $languages, translator: 'openai' ); ``` #### Delete Translations ```php // Delete all translations for a post BlogLocalizationService::deleteTranslations($post); // Or via model $post->localizations()->delete(); ``` #### Get Translation Statistics ```php $stats = BlogLocalizationService::getTranslationStats($post); echo "Total translations: " . $stats['total_translations']; echo "Languages: " . implode(', ', $stats['languages']); echo "Auto-translated: " . $stats['auto_translated']; echo "Translators used: " . implode(', ', $stats['translators']); ``` ### Via API #### Get Translated Blog Post ```bash GET /api/v1/blog-posts/{slug}?app_id={appId}&language=es ``` **Response:** ```json { "data": { "id": 1, "title": "Título traducido", "slug": "titulo-traducido", "excerpt": "Resumen traducido...", "content": "Contenido traducido...", "featured_image": "url", "author": "Author Name", "language": "es", "is_published": true, "published_at": "2024-01-26T10:00:00Z", "app_id": 1 } } ``` #### Get All Translations for a Post ```bash GET /api/v1/blog-posts/1/translations ``` **Response:** ```json { "data": [ { "language": "es", "title": "Título en español", "slug": "titulo-en-espanol", "excerpt": "Resumen...", "translator": "google", "is_auto_translated": true, "created_at": "2024-01-26T10:00:00Z" }, { "language": "fr", "title": "Titre en français", "slug": "titre-en-francais", "excerpt": "Résumé...", "translator": "google", "is_auto_translated": true, "created_at": "2024-01-26T10:05:00Z" } ] } ``` ## Database Schema ### blog_post_localizations Table ```sql CREATE TABLE blog_post_localizations ( id BIGINT PRIMARY KEY, blog_post_id BIGINT NOT NULL FOREIGN KEY, language VARCHAR(10), -- Language code: es, fr, de, etc. title VARCHAR(255), -- Translated title slug VARCHAR(255), -- Translated slug (indexed) content LONGTEXT, -- Translated content excerpt TEXT, -- Translated excerpt translator VARCHAR(255), -- google, deepl, openai, manual is_auto_translated BOOLEAN DEFAULT 0, -- Whether it was auto-translated created_at TIMESTAMP, updated_at TIMESTAMP, deleted_at TIMESTAMP, UNIQUE (blog_post_id, language), INDEX (language) ); ``` ## Blade Template Integration ### Display Translation Selector ```blade @if($post->localizations()->exists()) <div class="mb-4"> <label class="block text-sm font-medium mb-2">Available Languages:</label> <div class="flex gap-2 flex-wrap"> @foreach($post->localizations as $localization) <a href="?language={{ $localization->language }}" class="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"> {{ strtoupper($localization->language) }} </a> @endforeach </div> </div> @endif ``` ### Display Translated Content ```blade @php $locale = request('language', 'en'); $localization = $post->localizations() ->where('language', $locale) ->first(); @endphp <h1>{{ $localization ? $localization->title : $post->title }}</h1> <div>{{ $localization ? $localization->content : $post->content }}</div> ``` ## Flutter Integration ### Fetch Translated Blog Post ```dart import 'package:http/http.dart' as http; import 'dart:convert'; class BlogService { static const String baseUrl = 'https://yourdomain.com/api/v1'; /// Get blog post in specific language Future<BlogPost> getBlogInLanguage({ required String slug, required int appId, required String language, }) async { try { final queryParams = { 'app_id': appId.toString(), 'language': language, }; final uri = Uri.parse('$baseUrl/blog-posts/$slug') .replace(queryParameters: queryParams); final response = await http.get(uri); if (response.statusCode == 200) { final json = jsonDecode(response.body); return BlogPost.fromJson(json['data']); } else { throw Exception('Failed to load blog'); } } catch (e) { throw Exception('Error: $e'); } } /// Get available translations for a blog post Future<List<String>> getAvailableLanguages({ required int postId, }) async { try { final response = await http.get( Uri.parse('$baseUrl/blog-posts/$postId/translations'), ); if (response.statusCode == 200) { final json = jsonDecode(response.body); final List<dynamic> data = json['data']; return data.map((t) => t['language'].toString()).toList(); } return []; } catch (e) { return []; } } } // Usage final service = BlogService(); // Get English version var englishBlog = await service.getBlogInLanguage( slug: 'my-blog-post', appId: 1, language: 'en', ); // Get Spanish version var spanishBlog = await service.getBlogInLanguage( slug: 'my-blog-post', appId: 1, language: 'es', ); // Get available languages var languages = await service.getAvailableLanguages(postId: 1); ``` ### Language Selector Widget ```dart class LanguageSelector extends StatefulWidget { final String currentLanguage; final Function(String) onLanguageChanged; const LanguageSelector({ required this.currentLanguage, required this.onLanguageChanged, }); @override State<LanguageSelector> createState() => _LanguageSelectorState(); } class _LanguageSelectorState extends State<LanguageSelector> { final Map<String, String> languages = { 'en': '🇺🇸 English', 'es': '🇪🇸 Spanish', 'fr': '🇫🇷 French', 'de': '🇩🇪 German', 'it': '🇮🇹 Italian', 'pt': '🇵🇹 Portuguese', 'ru': '🇷🇺 Russian', 'ja': '🇯🇵 Japanese', 'zh': '🇨🇳 Chinese', 'ar': '🇸🇦 Arabic', 'ko': '🇰🇷 Korean', }; @override Widget build(BuildContext context) { return DropdownButton<String>( value: widget.currentLanguage, items: languages.entries .map((e) => DropdownMenuItem( value: e.key, child: Text(e.value), )) .toList(), onChanged: (value) { if (value != null) { widget.onLanguageChanged(value); } }, ); } } ``` ## Cost Estimation ### Google Translate (Free) - **Cost**: $0 (free tier) - **Monthly capacity**: Unlimited (with rate limiting) - **Per request**: ~0.5 cents ### DeepL API - **Cost**: $5.49/month (500K characters) - $600/month (5M characters) - **Per 1M characters**: ~$0.11-0.12 - **Recommended for**: Professional translations ### OpenAI (GPT-3.5) - **Cost**: ~$0.002 per 1K input tokens, $0.004 per 1K output tokens - **Average blog post**: ~$0.01-0.05 per language - **Recommended for**: High-quality, context-aware translations ## Best Practices ### 1. Use Google for Development ```php // In development if (app()->environment('local')) { $translator = 'google'; } else { $translator = 'deepl'; // or 'openai' } ``` ### 2. Translate Only Published Posts ```php // Only translate published, finalized posts $post->localizations()->delete(); // Clear old translations BlogLocalizationService::autoTranslateAndSave($post, translator: 'google'); ``` ### 3. Set Rate Limits Add rate limiting to translation endpoints: ```php // In routes/api.php Route::post('/blog-posts/{id}/translate', [BlogPostController::class, 'translate']) ->middleware(['auth:sanctum', 'throttle:10,60']); // 10 requests per minute ``` ### 4. Monitor Translation Quality ```php // Log all translation activities \Log::info('Blog translated', [ 'post_id' => $post->id, 'languages' => count($languages), 'translator' => $translator, ]); ``` ### 5. Cache Translations ```php // Cache translations for 24 hours $translations = Cache::remember( "blog_translations_{$post->id}", now()->addDay(), function () use ($post) { return $post->localizations()->get(); } ); ``` ## Troubleshooting ### Translation Returns Empty **Cause**: API timeout or rate limiting **Solution**: ```php try { $results = BlogLocalizationService::autoTranslateAndSave($post, translator: 'google'); } catch (\Exception $e) { \Log::error('Translation failed: ' . $e->getMessage()); // Retry with smaller batch } ``` ### Poor Translation Quality **Solution**: Use better translation service ```php // Switch from Google to DeepL or OpenAI BlogLocalizationService::autoTranslateAndSave( $post, translator: 'deepl' // or 'openai' ); ``` ### API Key Not Working **Solution**: Verify configuration ```bash # Test DeepL php artisan tinker > Http::withHeaders(['Authorization' => 'DeepL-Auth-Key YOUR_KEY'])->get('https://api-free.deepl.com/v1/languages') # Test OpenAI > Http::withHeaders(['Authorization' => 'Bearer YOUR_KEY'])->get('https://api.openai.com/v1/models') ``` ## Security Considerations ### Protect Translation Endpoints ```php // Only team owners can translate Route::post('/blog-posts/{id}/translate', function(BlogPost $post) { $this->authorize('update', $post); // ... })->middleware('auth:sanctum'); ``` ### Rate Limit Translations ```php // Prevent abuse Route::middleware('throttle:5,60')->group(function () { Route::post('/blog-posts/translate', [...]); }); ``` ### Monitor API Usage ```php // Track API costs \Log::info('Translation API call', [ 'service' => 'deepl', 'characters' => strlen($content), 'estimated_cost' => strlen($content) / 1000000 * 0.12, ]); ``` ## FAQ **Q: Can I auto-translate on post creation?** A: Yes! Add to your `saveBlogPost()` method: ```php if ($this->autoTranslate) { BlogLocalizationService::autoTranslateAndSave( $post, $this->selectedLanguages, $this->translationService ); } ``` **Q: How do I get translated posts via API?** A: Add `?language=es` to the API request: ```bash GET /api/v1/blog-posts/my-post?app_id=1&language=es ``` **Q: Can I translate user-generated content?** A: Yes, but verify content safety first: ```php // Add content moderation if ($post->is_published) { BlogLocalizationService::autoTranslateAndSave($post); } ``` **Q: How much does translation cost?** A: Google is free, DeepL is ~$0.12 per 1M characters, OpenAI is ~$0.01 per blog post per language.