Flutter: riconoscere la lingua e tradurre un testo - 1/2

In questo articolo vedremo come utilizzare il Machine Leaning Kit di Google in un'app Flutter per identificare la lingua di un testo (prima parte) per poi provare a farne la traduzione (seconda parte). Fornito un testo, si potrà visualizzare un punteggio di confidenza per tutte le possibili lingue associate al testo inserito. Il ML Kit è un SDK per dispositivi mobili che permette di riconoscere oggetti, forme, facce, codici a barre, lingue o immagini basti sui migliori modelli di machine learning di Google.

Il progetto in Flutter

Per l'inserimento del testo e la visualizzazione dei risultati, l'app esporrà una pagina simile ad una classica chat, con la casella di inserimento e una lista di messaggi. 

Partiamo da un nuovo progetto e sostituiamo il codice standard nel file main.dart con questo:


import 'package:flutter/material.dart';
import 'views/home_view.dart';

void main() {
  runApp(const MyApp(title: 'Flutter translate'));
}

class MyApp extends StatefulWidget {
  final String title;
  const MyApp({Key? key, required this.title}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return Home(title: widget.title);
  }
}

Nella cartella lib, creiamo una sottocartella views e dentro questa un file home_view.dart con questo codice:


import 'package:flutter/material.dart';

class Home extends StatefulWidget {
  final String title;
  const Home({Key? key, required this.title}) : super(key: key);

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final TextEditingController textController = TextEditingController();

  @override
  void dispose() {
    textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: SafeArea(
            child: Column(children: [
              Flexible(
                  child: Placeholder(),
              ),
              const Divider(
                height: 1.0,
              ),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: textController,
                        onSubmitted: null,
                        decoration: const InputDecoration.collapsed(
                            hintText: "digitare il testo qui ..."),
                      ),
                    ),
                    IconButton(
                      icon: const Icon(Icons.send_sharp),
                      onPressed: null,
                    ),
                  ],
                ),
              )
            ]),
          )),
    );
  }
}


per ottenere un risultato come questo:


Da terminale, aggiungiamo un package che permette di utilizzare le API dell'ML Kit per il riconoscimento del testo:


 flutter pub add google_mlkit_language_id

Nella cartella lib, creiamo ancora una sottocartella helpers e dentro questa un file mlkit.dart con questo codice:


import 'package:flutter_translate/models/language_model.dart';
import 'package:google_mlkit_language_id/google_mlkit_language_id.dart';

class MLHelper {
  Future<List<LanguageModel>> identifyLanguage(String text) async {
    List<LanguageModel> result = [];
    final languageIdentifier = LanguageIdentifier(confidenceThreshold: 0.3);
    final List<IdentifiedLanguage> languages =
        await languageIdentifier.identifyPossibleLanguages(text);
    try {
      for (IdentifiedLanguage language in languages) {
        result.add(LanguageModel(
            text: language.languageTag, confidence: language.confidence));
      }
    } catch (e) {
    }
    return result;
  }
}


Il codice appena proposto, accetta come input il testo da analizzare e restituisce una lista di oggetti LanguageModel con l'id della lingua e il rispettivo livello di confidenza (di tutte le lingue associate con almeno il 30% di probabilità, avendo fissato anche una soglia minima pari a 0.3) ordinati in senso decrescente. 

Nella cartella lib, creiamo ancora una sottocartella models e dentro questa un file language_model.dart con il codice del modello appena utilizzato:


class LanguageModel {
  String text;
  double confidence;
  LanguageModel({required this.text, required this.confidence});
}


Nella stessa cartella creiamo un ulteriore file item_model.dart con il codice degli oggetti che visualizzeremmo in pagina (un testo e un campo type che utilizzeremo per differenziare gli elementi):


class ItemModel {
  String? id;
  String text;
  String type;
  ItemModel({this.id, required this.text, required this.type});
}


Sempre nella cartella lib, creiamo ancora una sottocartella widgets e dentro questa un file bubble_widget.dart con il codice del widget che utilizzeremo per rappresentare i messaggi nella chat:


import 'package:flutter/material.dart';

class Bubble extends StatelessWidget {
  final String message;
  final String type;
  const Bubble({Key? key, required this.message, required this.type})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment:
          (type == "text") ? MainAxisAlignment.end : MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Flexible(
          child: Container(
            padding: const EdgeInsets.all(8),
            margin: const EdgeInsets.only(bottom: 5),
            decoration: BoxDecoration(
              color: (type == "text") ? Colors.blueAccent : Colors.teal,
              borderRadius: BorderRadius.circular(10),
            ),
            child: Text(
              message,
              style: TextStyle(
                  color: Colors.white, fontSize: (type == "text") ? 12 : 10),
            ),
          ),
        ),
      ],
    );
  }
}


Ora siamo pronti per sostituire il placeholder utilizzato nella home_view.dart con il codice effettivo. Modifichiamo quindi il codice della vista in questo modo:


  final List<ItemModel> items = [];
  late bool waiting = false;

...

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: SafeArea(
            child: Column(children: [
              Flexible(
                  child: ListView.builder(
                reverse: true,
                padding: const EdgeInsets.symmetric(horizontal: 8),
                itemCount: items.length,
                itemBuilder: (context, index) {
                  return Bubble(
                      message: items[index].text, type: items[index].type);
                },
              )),



utilizzando un ListView.builder per mostrare i risultati in pagina.
Occupiamoci ora di definire il codice del metodo da eseguire al tap sull'icona di invio:


  void analyzeText() {
    if (textController.text.isEmpty) return;

    waiting = true;
    String text = textController.text;

    items.insert(
        0,
        ItemModel(
          text: text,
          type: "text",
        ));
    textController.clear();
    setState(() {});
    
....

  }


che non fa altro che aggiungere (in testa alla lista) un nuovo oggetto con il testo recuperato dalla casella di inserimento e associamo questo metodo sia al tap sul pulsante, sia al submit della casella di testo.


...
                    Expanded(
                      child: TextField(
                        controller: textController,
                        onSubmitted: () { analyzeText(); },
                        decoration: const InputDecoration.collapsed(
                            hintText: "digitare il testo qui ..."),
                      ),
                    ),
                    IconButton(
                      icon: const Icon(Icons.send_sharp),
                      onPressed: () { analyzeText(); } ,
                    ),
....


Se proviamo ora a digitare un testo, il risultato dovrebbe essere molto simile a questo:




Oltre ad aggiungere il nuovo oggetto nella chat, usiamo lo stesso testo per determinare la probabile lingua, aggiungendo questo codice allo stesso metodo:


    MLHelper helper = MLHelper();
    helper.identifyLanguage(text).then((result) {
      List<String> text = [];
      for (LanguageModel language in result) {
        text.add(
            '${language.text.toUpperCase()} (${(language.confidence * 100).toStringAsFixed(1)}%)');
      }

      items.insert(
          0,
          ItemModel(
            text: text.join(","),
            type: "language",
          ));
      setState(() {
        waiting = false;
      });
    });


ottenendo quindi il risultato atteso:


Fine prima parte

La prima parte del progetto è terminato e l'applicazione di esempio è completa per determinare la lingua del testo inserito. Nella seconda parte vedremo come tradurre il testo analizzato.
Come per gli articoli precedenti potete trovare questo codice tra le mie repo GitHub: https://github.com/luigimicco/flutter_translate.

Commenti