← Back to blog

Build a quick multilingual app with Flutter and Firebase Extensions

Renuka Kelkar

DevRel Advocate

3rd April, 2023

Need a custom tool? Invertase can help

Tired of investing in off-the-shelf software that doesn't quite fit your business? Invertase can build solutions tailored to your exact needs. Tell us what you're looking for here and we'll be in touch.

Introduction

The online world is changing rapidly, and Mobile and web applications have reached out to global audiences. Language plays an important role in reaching out to a global audience. You may lose the audience in other regions by developing a mobile app in a single language. No matter how much the app can benefit them, developing a multilingual app is the solution to this problem.

Enabling an application to work in multiple languages needs more time and work. That’s where Firebase Translate Text Extension helps you.

This article provides a guide on using the Translate Text Extension to quickly build a multilingual Flutter web app.

Translate Text Extension

extensions.dev

Firebase Extensions are prebuilt solutions that can be easily added to your application without the need to research, write or debug the code.

The Translate text extension is straightforward to use to translate text in your Cloud Firestore Database without any extra lines of code.

Getting started

Before starting, first, we need to:

Create a Cloud Firestore Database

Upgrade Firebase with Blaze plan

To install this extension, your Firebase project must be on the Blaze (pay-as-you-go) plan. You will only be charged for the resources you use. Most Firebase services offer a free tier for low-volume use. Learn more about Firebase billing.

Install Translate Text Extension

Option 1: Using the Firebase CLI

To install and manage extensions, you can also use the Firebase CLI:

Set up a new project directory or navigate to an existing one

Run the below command in your command line

firebase ext:install firebase/firestore-translate-text --project=projectId_or_alias

You can install the Translate text extension via Firebase CLI, or if you prefer to use a command line like me, run the firebase ext:install command by passing your project ID into Firebase CLI.

Option 2: Using the Firebase Console

To add an extension, please click on the extension tab on the Firebase console and select the extension from the marketplace.

Configure Extension

Cloud Functions location

Specify the location where you want to deploy the functions created for this extension.

Target languages for translations as a comma-separated list

Specify the target languages you want to translate new strings. The languages are identified using ISO-639-1 codes in a comma-separated list, for example: en, es, de, fr.

For these codes, visit the list of supported languages.

Collection path

Specify the collection from where you want to translate new strings.

Input field name

This is the input field name that contains the string that you want to translate.

Translations output field name

Here, you must specify the field name where you want to store the translated string after the translations.

Languages field name (Optional)

What field name contains the languages you want to translate into? This field is optional. If you don’t specify it, the extension will use the languages specified in the LANGUAGES parameter.

Translate existing documents?

Should existing documents in the Firestore collection be translated as well? If you’ve added new languages since a document was translated, this will also fill those in.

After configuring the extension, click on the install button and wait for 3 to 5 minutes to finish the installation setup.

How does it work?

When you add or update any documents in Firestore, it will automatically translate them into multiple languages which you have specified. Behind the scenes, this extension uses the Cloud function, which listens to changes in your specified Cloud Firestore collection and passes the string to the Google Cloud Translate API. This converts those strings into specified languages and then writes all these changes to the original document.

Create Collections

First, create a collection named “translations” in Cloud Firestore, which then creates a document containing a field named input with a type string and named translated with a type map. For example:

In a few seconds after creating your document, you will see the translated field like this…

Project Overview

The project Translate Text Extension consists of two main functions

  1. Flashcards: It’s multilingual flashcards.
  2. Translator: In this, you can insert new text/sentence in English, and it will convert into three different languages.

This project aims to see how to use Translate Text Extension in the Flutter project and quickly build a Multilingual App.

Let’s start!

I am using Zapp online editor to build this project. Zapp! is an online Flutter sandbox environment running directly in the browser. Zapp! enables developers to quickly prototype Flutter applications without requiring a local environment.

Setting up environment

  1. Create an Account on Zapp
  2. Create a new Flutter project.
  3. Set up Firebase into a Flutter web project.
  4. Add all required dependencies.
  cloud_firestore: ^4.4.5
  firebase_core:

File Structure

Here, I have created four folders:

Model: model class for data.

Pages: Contains all the pages

Utils: Contains Color constants.

Widgets: Contains reusable custom widgets I created to simplify the code.

Dashboard Page

After setting up Firebase, it’s time to start coding!

The dashboard page is a simple Flutter page containing the two TextButtons used for navigating to the FlashCard and Translator pages.

import 'package:flutter/material.dart';
import 'package:flutter_app/pages/translator.dart';
import 'package:flutter_app/widgets/custome_button.dart';

import 'flash_card.dart';

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

  @override
  State<Dashboard> createState() => _DashboardState();
}

class _DashboardState extends State<Dashboard> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Translate Text Extension")),
      body: Center(
          child: Column(children: [
        SizedBox(
          height: 30,
        ),
        Image.network('https://cdn-icons-png.flaticon.com/512/3898/3898150.png',
            height: 200, width: 200, fit: BoxFit.cover),
        SizedBox(
          height: 50,
        ),
        CustomeButton(
          titleText: "Flashcard",
          onPressed: () {
            Navigator.push(
                context, MaterialPageRoute(builder: (_) => FlashCard()));
          },
        ),
        SizedBox(
          height: 30,
        ),
        CustomeButton(
          titleText: "Translator",
          onPressed: () {
            Navigator.push(
                context, MaterialPageRoute(builder: (_) => Translator()));
          },
        ),
      ])),
    );
  }
}

Flashcard Page

On this page, we are using StreamBuilder to fetch data from translations collection.

To create the sliding effect, I have used PageView.builder, an in-build widget in Flutter.

To display the data in different languages, I have created a reusable FlashCardTile widget.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/model/translator.dart';
import 'package:flutter_app/utils/colors.dart';
import 'package:flutter_app/widgets/flashcard_tile.dart';

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

  @override
  State<FlashCard> createState() => _FlashCardState();
}

class _FlashCardState extends State<FlashCard> {
  final ref = FirebaseFirestore.instance.collection('translations');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Multilingual Flash Card")),
      body: StreamBuilder(
        stream: ref.snapshots(),
        builder: (context, snapshot) {
          final snapshotData = snapshot.data;
          if (snapshotData != null) {
            List<TranslatorModel> categoryList = snapshot.data!.docs
                .map((e) => TranslatorModel.fromJson(e.data()))
                .toList();
            return PageView.builder(
                allowImplicitScrolling: true,
                itemCount: categoryList.length,
                itemBuilder: (context, index) {
                  return Padding(
                    padding: const EdgeInsets.all(20.0),
                    child: Container(
                      decoration: BoxDecoration(
                          color: Colors.blue[200],
                          borderRadius: BorderRadius.all(Radius.circular(10))),
                      width: double.infinity,
                      height: double.infinity,
                      child: Column(
                        children: [
                          Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Image.network(
                                categoryList[index].image ??
                                    'https://cdn-icons-png.flaticon.com/512/1829/1829586.png',
                                height: 200,
                                width: 200),
                          ),
                          SizedBox(
                            height: 10,
                          ),
                          Text(
                            categoryList[index].input ?? 'no Input Data',
                            style: Theme.of(context).textTheme.titleLarge,
                          ),
                          SizedBox(
                            height: 20,
                          ),
                          FlashCardTile(
                            titleText: categoryList[index].translated?.es ??
                                'no Translated Data',
                            subtitleText: 'Spanish',
                            bgcolor: MyColor.myRed,
                          ),
                          FlashCardTile(
                            titleText: categoryList[index].translated?.fr ??
                                'no Translated Data',
                            subtitleText: 'french',
                            bgcolor: MyColor.myBlue,
                          ),
                          FlashCardTile(
                              titleText: categoryList[index].translated?.de ??
                                  'no Translated Data',
                              subtitleText: 'German',
                              bgcolor: MyColor.myGreen),
                        ],
                      ),
                    ),
                  );
                });
          } else {
            return const CircularProgressIndicator();
          }
        },
      ),
    );
  }
}

Translator Page

To create translator functionality in the app. I have created a new collection called “test”.

In Cloud Firestore, I tried to add a test collection in my existing extension setting, but it was not working. In Translate Text Extension, you should connect only one collection at a time; if you want to translate data from multiple Firestore collections, you must create a separate instance for each collection. Just like below:

translator.dart

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

import '../widgets/custome_tile.dart';
import '../model/translator.dart';

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

  @override
  State<Translator> createState() => _TranslatorState();
}

class _TranslatorState extends State<Translator> {
  final ref = FirebaseFirestore.instance
      .collection('test')
      .doc('RlzUfb7W2m8TY3xpi3J5')
      //calling withConverter is that the generic type T is set to your custom Model. This means that every subsequent call on the document ref, will work with that type instead of Map<String, dynamic>.
      .withConverter<TranslatorModel>(
        fromFirestore: (snapshot, _) => TranslatorModel.fromJson(
          snapshot.data()!,
        ),
        toFirestore: (model, _) => model.toJson(),
      );

  /// Controller for updating translation input text.
  final TextEditingController _textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Text Translator")),
      body: StreamBuilder<DocumentSnapshot<TranslatorModel>?>(
        stream: ref.snapshots(),
        builder: (context, snapshot) {
          final snapshotData = snapshot.data;
          if (snapshotData != null) {
            // Convert the snapshot to a map.
            final TranslatorModel? translatedData = snapshotData.data();
            if (translatedData == null) {
              return Center(child: Text('No Data'));
            }
            return Column(
              children: [
                CustomeTile(
                  titleText: translatedData.input ?? 'No Input',
                  subtitleText: 'English',
                ),
                Divider(),
                CustomeTile(
                  titleText: translatedData.translated?.de ?? 'No De',
                  subtitleText: 'German',
                ),
                CustomeTile(
                  titleText: translatedData.translated?.es ?? 'No Es',
                  subtitleText: 'Spanish',
                ),
                CustomeTile(
                  titleText: translatedData.translated?.fr ?? 'No Fr',
                  subtitleText: 'French',
                ),
                CustomeTile(
                  titleText: translatedData.translated?.it ?? 'No It',
                  subtitleText: 'Italian',
                ),
                SizedBox(
                  height: 30,
                ),
                Image.network(
                    'https://cdn-icons-png.flaticon.com/512/576/576515.png',
                    height: 100,
                    width: 100,
                    fit: BoxFit.cover),
                Spacer(),
                Padding(
                  padding: const EdgeInsets.all(16),
                  child: Container(
                    height: 50,
                    width: 400,
                    decoration: BoxDecoration(
                      color: Colors.blue[300],
                      borderRadius: BorderRadius.all(
                        Radius.circular(10),
                      ),
                    ),
                    child: TextField(
                      style: TextStyle(color: Colors.black),
                      controller: _textController,
                      decoration: InputDecoration(
                        border: InputBorder.none,
                        hintText: 'Input text',
                        contentPadding: EdgeInsets.only(top: 15, left: 10),
                        suffixIcon: IconButton(
                          icon: Icon(Icons.send, color: Colors.blue),
                          onPressed: () async {
                            // Update the input field on the translation document.
                            await ref.update(
                              {'input': _textController.text},
                            );

                            // Clear the text field to prepare for next input.
                            _textController.clear();
                          },
                        ),
                      ),
                    ),
                  ),
                ),
              ],
            );
          } else {
            return const CircularProgressIndicator();
          }
        },
      ),
    );
  }
}

In the translator page, to fetch the data from the particular document from the Firestore, I have used StreamBuilder and passed the documentid.

To display the multilingual data, I have used CustomeTile widget.

To enter the new text/sentence, I have used TextField widget.

After entering new text, I then hit the IconButton which will call the update method to update the input field in the Firestore.

After a few seconds, it’s done, and the translated text will be displayed!

Translate Text Firebase Extension is a very fast and reliable way to add Translate functionality into your Flutter application because the Firebase engineers do all the hard work for you. You can use this extension to build an app like a chat app or social media app where you want to add translation functionality.

Final result

You can check the full example here.

Stay tuned for more updates and exciting news that we will share in the future. Follow us on Invertase TwitterLinkedin, and Youtube, and subscribe to our monthly newsletter to stay up-to-date. You may also join our Discord to have an instant conversation with us.

Renuka Kelkar

DevRel Advocate

Categories

Tags