pic_translator 1.0.2+3 copy "pic_translator: ^1.0.2+3" to clipboard
pic_translator: ^1.0.2+3 copied to clipboard

A Flutter plugin for translating text in images from one language to another. It extracts text from images using ML Kit and overlays the translated text on the original image

example/lib/main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:pic_translator/pic_translator.dart';
import 'package:translator/translator.dart';


void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Text Translator',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const TextTranslatorPage(),
    );
  }
}

class TextTranslatorPage extends StatefulWidget {
  const TextTranslatorPage({super.key});

  @override
  State<TextTranslatorPage> createState() => _TextTranslatorPageState();
}

class _TextTranslatorPageState extends State<TextTranslatorPage>
    with SingleTickerProviderStateMixin {
  final PicTranslator _translator = PicTranslator();
  final ImagePicker _imagePicker = ImagePicker();

  // Text controller for manual text input
  final TextEditingController _textInputController = TextEditingController();

  // Tab controller
  late TabController _tabController;

  File? _selectedImage;
  String _extractedText = '';
  String _translatedText = '';
  String _manualInputText = '';
  String _manualTranslatedText = '';
  bool _isProcessing = false;
  String _targetLanguage = 'en'; // Default to English

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
  }

  @override
  void dispose() {
    _translator.dispose();
    _textInputController.dispose();
    _tabController.dispose();
    super.dispose();
  }

  Future<void> _pickImageFromGallery() async {
    try {
      final XFile? pickedFile =
          await _imagePicker.pickImage(source: ImageSource.gallery);

      if (pickedFile == null) return;

      setState(() {
        _isProcessing = true;
        _selectedImage = File(pickedFile.path);
        _extractedText = '';
        _translatedText = '';
      });

      await _processImage();
    } catch (e) {
      _showSnackBar('Error picking image: ${e.toString()}');
      setState(() {
        _isProcessing = false;
      });
    }
  }

  Future<void> _captureImageFromCamera() async {
    try {
      final XFile? pickedFile =
          await _imagePicker.pickImage(source: ImageSource.camera);

      if (pickedFile == null) return;

      setState(() {
        _isProcessing = true;
        _selectedImage = File(pickedFile.path);
        _extractedText = '';
        _translatedText = '';
      });

      await _processImage();
    } catch (e) {
      _showSnackBar('Error capturing image: ${e.toString()}');
      setState(() {
        _isProcessing = false;
      });
    }
  }

  Future<void> _processImage() async {
    try {
      if (_selectedImage == null) {
        setState(() {
          _isProcessing = false;
        });
        return;
      }

      // Extract text from image
      final recognizedText =
          await _translator.extractTextFromImage(_selectedImage!);

      if (recognizedText.text.isEmpty) {
        setState(() {
          _extractedText = 'No text detected in the image';
          _isProcessing = false;
        });
        return;
      }

      setState(() {
        _extractedText = recognizedText.text;
      });

      // Translate the extracted text
      final translatedText =
          await _translator.translateText(recognizedText, _targetLanguage);

      setState(() {
        _translatedText = translatedText;
        _isProcessing = false;
      });
    } catch (e) {
      _showSnackBar('Error processing image: ${e.toString()}');
      setState(() {
        _isProcessing = false;
      });
    }
  }

  Future<void> _translateManualText() async {
    final textToTranslate = _textInputController.text.trim();

    if (textToTranslate.isEmpty) {
      _showSnackBar('Please enter text to translate');
      return;
    }

    setState(() {
      _isProcessing = true;
      _manualInputText = textToTranslate;
      _manualTranslatedText = '';
    });

    try {
      // Use the GoogleTranslator directly for text-only translation
      final translator = GoogleTranslator();
      final translation = await translator.translate(
        textToTranslate,
        to: _targetLanguage,
      );

      setState(() {
        _manualTranslatedText = translation.text;
        _isProcessing = false;
      });
    } catch (e) {
      _showSnackBar('Error translating text: ${e.toString()}');
      setState(() {
        _isProcessing = false;
      });
    }
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text(message)));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Text Translator'),
        bottom: TabBar(
          controller: _tabController,
          tabs: const [
            Tab(icon: Icon(Icons.image), text: 'Image Text'),
            Tab(icon: Icon(Icons.text_fields), text: 'Direct Text'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          // Image Text Tab
          SingleChildScrollView(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  // Language selector
                  _buildLanguageSelector(),

                  const SizedBox(height: 20),

                  // Image preview
                  Container(
                    height: 200,
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.grey),
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: _selectedImage != null
                        ? Image.file(_selectedImage!, fit: BoxFit.contain)
                        : const Center(child: Text('No image selected')),
                  ),

                  const SizedBox(height: 20),

                  // Buttons row
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      ElevatedButton.icon(
                        onPressed:
                            _isProcessing ? null : _captureImageFromCamera,
                        icon: const Icon(Icons.camera_alt),
                        label: const Text('Camera'),
                      ),
                      ElevatedButton.icon(
                        onPressed: _isProcessing ? null : _pickImageFromGallery,
                        icon: const Icon(Icons.photo_library),
                        label: const Text('Gallery'),
                      ),
                    ],
                  ),

                  const SizedBox(height: 20),

                  // Loading indicator
                  if (_isProcessing)
                    const Center(
                      child: Column(
                        children: [
                          CircularProgressIndicator(),
                          SizedBox(height: 8),
                          Text('Processing...'),
                        ],
                      ),
                    ),

                  const SizedBox(height: 20),

                  // Text results
                  if (_extractedText.isNotEmpty)
                    Card(
                      elevation: 2,
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              'Extracted Text:',
                              style: TextStyle(
                                  fontWeight: FontWeight.bold, fontSize: 16),
                            ),
                            const SizedBox(height: 8),
                            Container(
                              width: double.infinity,
                              padding: const EdgeInsets.all(12),
                              decoration: BoxDecoration(
                                color: Colors.grey.shade100,
                                borderRadius: BorderRadius.circular(8),
                              ),
                              child: Text(_extractedText),
                            ),
                          ],
                        ),
                      ),
                    ),

                  const SizedBox(height: 12),

                  if (_translatedText.isNotEmpty)
                    Card(
                      elevation: 2,
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              'Translated Text:',
                              style: TextStyle(
                                  fontWeight: FontWeight.bold, fontSize: 16),
                            ),
                            const SizedBox(height: 8),
                            Container(
                              width: double.infinity,
                              padding: const EdgeInsets.all(12),
                              decoration: BoxDecoration(
                                color: Colors.blue.shade50,
                                borderRadius: BorderRadius.circular(8),
                              ),
                              child: Text(_translatedText),
                            ),
                          ],
                        ),
                      ),
                    ),
                ],
              ),
            ),
          ),

          // Direct Text Input Tab
          SingleChildScrollView(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  // Language selector
                  _buildLanguageSelector(),

                  const SizedBox(height: 20),

                  // Text input
                  TextField(
                    controller: _textInputController,
                    decoration: InputDecoration(
                      labelText: 'Enter text to translate',
                      hintText: 'Type or paste text here',
                      border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(8),
                      ),
                      suffixIcon: IconButton(
                        icon: const Icon(Icons.clear),
                        onPressed: () {
                          _textInputController.clear();
                        },
                      ),
                    ),
                    maxLines: 5,
                    textInputAction: TextInputAction.done,
                    onSubmitted: (_) => _translateManualText(),
                  ),

                  const SizedBox(height: 20),

                  // Translate button
                  ElevatedButton.icon(
                    onPressed: _isProcessing ? null : _translateManualText,
                    icon: const Icon(Icons.translate),
                    label: const Text('Translate'),
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.symmetric(vertical: 12),
                    ),
                  ),

                  const SizedBox(height: 20),

                  // Loading indicator
                  if (_isProcessing)
                    const Center(
                      child: Column(
                        children: [
                          CircularProgressIndicator(),
                          SizedBox(height: 8),
                          Text('Translating...'),
                        ],
                      ),
                    ),

                  const SizedBox(height: 20),

                  // Translated result
                  if (_manualTranslatedText.isNotEmpty)
                    Card(
                      elevation: 2,
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                const Text(
                                  'Translated Text:',
                                  style: TextStyle(
                                      fontWeight: FontWeight.bold,
                                      fontSize: 16),
                                ),
                                IconButton(
                                  icon: const Icon(Icons.copy),
                                  tooltip: 'Copy to clipboard',
                                  onPressed: () {
                                    // Copy translated text to clipboard
                                    Clipboard.setData(ClipboardData(
                                        text: _manualTranslatedText));
                                    _showSnackBar(
                                        'Translated text copied to clipboard');
                                  },
                                ),
                              ],
                            ),
                            const SizedBox(height: 8),
                            Container(
                              width: double.infinity,
                              padding: const EdgeInsets.all(12),
                              decoration: BoxDecoration(
                                color: Colors.blue.shade50,
                                borderRadius: BorderRadius.circular(8),
                              ),
                              child: Text(_manualTranslatedText),
                            ),
                          ],
                        ),
                      ),
                    ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildLanguageSelector() {
    return Row(
      children: [
        const Text('Target Language: ',
            style: TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(width: 8),
        Expanded(
          child: DropdownButton<String>(
            isExpanded: true,
            value: _targetLanguage,
            onChanged: (value) {
              if (value != null) {
                setState(() {
                  _targetLanguage = value;

                  // Retranslate based on current tab
                  if (_tabController.index == 0) {
                    // Image tab
                    if (_extractedText.isNotEmpty &&
                        _extractedText != 'No text detected in the image') {
                      _isProcessing = true;
                      _processImage();
                    }
                  } else {
                    // Text tab
                    if (_textInputController.text.isNotEmpty) {
                      _translateManualText();
                    }
                  }
                });
              }
            },
            items: PicTranslator.availableLanguages.entries.map((entry) {
              return DropdownMenuItem<String>(
                value: entry.key,
                child: Text(entry.value),
              );
            }).toList(),
          ),
        ),
      ],
    );
  }
}
3
likes
105
points
1
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for translating text in images from one language to another. It extracts text from images using ML Kit and overlays the translated text on the original image

License

MIT (license)

Dependencies

cupertino_icons, flutter, google_fonts, google_ml_kit, image, image_picker, path_provider, permission_handler, translator

More

Packages that depend on pic_translator