Translate your React app using Format.js

Matt Angelosanto - Apr 12 '23 - - Dev Community

Written by Emmanuel Odioko✏️

Making sure your React app is available in several languages is one of the most significant ways to ensure people worldwide can access it in today's globally integrated society. Localization is helpful in this situation. Format.js, an open source toolkit, offers a selection of tools for localizing your React application.

Format.js enables you to translate the UI elements, messages, and dates in your app — making it easier for users outside of your country to use it. This tutorial will show you how to translate your React application using Format.js.

To follow along with this article, you'll need a basic knowledge of React, a terminal, and a code editor (I recommend VS Code). Let's get started!

Jump ahead:

Getting started with React

First, we use Create React App to create a React application:

npx create-react-app translate-app 
Enter fullscreen mode Exit fullscreen mode

Then, we can navigate to the newly created project after the installation with the cd translate-app/ command. Let's quickly set up a very simple ecommerce application where we have a bunch of products displayed in cards. Our simple app will also have a cart component that shows products we've added to our cart and allows us to remove those products.

Building the ProductCard and Cart components

Now, create a new ./src/components/ProductItem.jsx file and enter the following code:

// ./src/components/ProductItem.jsx
const ProductItem = ({ product, addToCart }) => {
  return (
    <li className="product">
      <div className="product-image img-cont">
        <img src={product.thumbnail} alt="" />
      </div>
      <div className="product-details">
        <h3>{product.title}</h3>
        <p>{product.description}</p>
        <span className="product-price">${product.price}</span>
      </div>
      <div className="product-actions">
        <button
          disabled={product?.isInCart}
          onClick={() => addToCart(product)}
          className="cta"
        >
          Add to cart
        </button>
      </div>
    </li>
  );
};
export default ProductItem;
Enter fullscreen mode Exit fullscreen mode

The code above shows that this is a simple component with two props — product and addToCart. These components are for the product information displayed on the component and the function to add a specified product to the cart.

Now, to create the Cart component, create a new ./src/components/Cart.jsx file and enter the following code:

// ./src/components/Cart.jsx
const Cart = ({ cart, removeItem }) => {
  return (
    <div className="dropdown">
      <div className="trigger group">
        <button className="cta">Cart</button>
        <span className="badge"> {cart?.length}</span>
      </div>
      <div className="content">
        <aside className="cart">
          <header className="cart-header">
            <h2>Your Cart</h2>
            <p>
              You have <span>{cart.length}</span> items in your cart
            </p>
          </header>
          <ul className="items">
            {cart.map((item) => {
              return (
                <li tabIndex={0} key={item.id} className="item">
                  <div className="item-image img-cont">
                    <img src={item.thumbnail} alt={item.name} />
                  </div>
                  <div className="item-details">
                    <h3>{item.title}</h3>
                    <p className="item-price">${item.price}</p>
                    <button onClick={() => removeItem(item)} className="cta">
                      Remove
                    </button>
                  </div>
                </li>
              );
            })}
          </ul>
        </aside>
      </div>
    </div>
  );
};
export default Cart;
Enter fullscreen mode Exit fullscreen mode

In this component, we also accept two props. The cart prop contains the list of products that have been added to the cart. The removeItem prop will be used to pass the removed item to the parent component.

Creating functions to add to our React app

Next, in ./src/App.js, we’ll import our components and create a few functions to fetch products from dummyjson.com. These will be used for our dummy products, to add products to the cart, and to remove products from the cart. First, enter the following code into the ./src/App.js file:

// ./src/App.js
import "./App.css";
import { useEffect, useState } from "react";
import ProductItem from "./components/ProductItem";
import Cart from "./components/Cart";
// function to fetch products from dummyjson.com
const getProducts = async () => {
  try {
    const res = await fetch("https://dummyjson.com/products");
    const data = await res.json();
    return data;
  } catch (error) {
    console.log({
      error,
    });
    return [];
  }
};
function App() {
  // set up state for products and cart
  const [products, setProducts] = useState([]);
  const [cart, setCart] = useState([]);
  // function to add product to cart
  const handleAddToCart = (product) => {
    console.log("product", product);
    setCart((cart) => {
      return [...cart, product];
    });
    setProducts((products) => {
      return products.map((p) => {
        if (p.id === product.id) {
          return {
            ...p,
            isInCart: true,
          };
        }
        return p;
      });
    });
  };

 // function to remove product from cart
const handleRemoveFromCart = (product) => {
    setCart((cart) => {
      return cart.filter((p) => p.id !== product.id);
    });
    setProducts((products) => {
      return products.map((p) => {
        if (p.id === product.id) {
          return {
            ...p,
            isInCart: false,
          };
        }
        return p;
      });
    });
  };
  // fetch products on component mount
  useEffect(() => {
    getProducts().then((data) => {
      setProducts(data.products);
    });
  }, []);
  return (
    <div className="app">
      <header className="app-header">
        <div className="wrapper">
          <div className="app-name">Simple store</div>
          <div>
            <Cart cart={cart} removeItem={handleRemoveFromCart} />
          </div>
        </div>
      </header>
      <main className="app-main">
        <div className="wrapper">
          <section className="products app-section">
            <div className="wrapper">
              <header className="section-header products-header">
                <div className="wrapper">
                  <h2 className="caption">Browse our products</h2>
                  <p className="text">
                    We have a wide range of products to choose from. Browse our
                    products and add them to your cart.
                  </p>
                </div>
              </header>
              <ul className="products-list">
                {products.map((product) => (
                  <ProductItem
                    key={product.id}
                    product={product}
                    addToCart={handleAddToCart}
                  />
                ))}
              </ul>
            </div>
          </section>
        </div>
      </main>
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Awesome! For purposes of this article, the styles used to create this example project will be placed in the ./src/App.css file of the project and are available on GitHub. You can also copy the styles from Pastebin or use your own styles.

Now, if we start our app by running the npm start command, we should see something like this: Translating React Apps With Format.js

Nice! Next, we'll install and set up Format.js to start translating our React application.

Setting up Format.js in React

To get started setting up Format.js in React, use the following commands:

Install react-intl, a Format.js package for React:
npm i -S react react-intl
Enter fullscreen mode Exit fullscreen mode

Once installed, we can access different helper functions, components, and Hooks from Format.js that we can use in our React app.

Adding the IntlProvider

This component helps us add i18n functionality to our application by providing configurations like the current locale and set of translated strings/messages to the root of the application. This makes these configurations available to the different <Formatted /> components used throughout the application.

In our ./src/App.js file, we'll wrap our app with the <IntlProvider /> component:

// ./src/App.js
// ...
import { IntlProvider } from "react-intl";
function App() {
  // ...
  // set up state for locale and messages
  const [locale, setLocale] = useState("es");
  const [messages, setMessages] = useState({
    "app.name": "Tienda sencilla",
  })
  // ...
  return (
    <IntlProvider messages={messages} key={locale} locale={locale}>
      {/* ... */}
    </IntlProvider>
  );
export default App;
Enter fullscreen mode Exit fullscreen mode

Based on the code above, we import the IntlProvider component from the react-intl library. We also set up the state for locale and messages variables with the initial values of "es" and {"app.name": "Tienda sencilla"}, respectively.

The IntlProvider component is then used to wrap the content of the App component. We also pass messages and locale variables as props to the IntlProvider. The key prop is set to the locale state to force React to re-render the component when the locale value changes.

The IntlProvider component provides internationalization support to the wrapped component by supplying it with translations for the current locale. By using the messages and locale state variables, the content of the wrapped component can be translated based on the selected locale. In this example, the app.name message key is translated to "Tienda sencilla" for the Spanish locale. Next, we'll use the <FormattedMesage /> component to see the translation in action.

Using the FormattedMessage component

First, we'll use the FormattedMessage component to translate the app name that appears in the app-header in ./src/App.js using the "app.name" property from our messages, as shown below:

// ./src/App.js
// ...
import { IntlProvider } from "react-intl";
function App() {
  // ...
  // set up state for locale and messages
  const [locale, setLocale] = useState("es");
  const [messages, setMessages] = useState({
    "app.name": "Tienda sencilla",
  })
  // ...
  return (
    <IntlProvider messages={messages} key={locale} locale={locale}>
      <div className="app">
        <header className="app-header">
          <div className="wrapper">
            <div className="app-name">
              <FormattedMessage id="app.name" defaultMessage={"Simple Store"} />
            </div>
            {/* ... */}
          </div>
        </header>
        {/* ... */}
      </div>
    </IntlProvider>
  );
export default App;
Enter fullscreen mode Exit fullscreen mode

Here, we use the FormattedMessage component from the react-intl library to translate the app name that appears in the app-header. The FormattedMessage component takes two props: ID and defaultMessage. The ID prop is used to identify the translation message in the messages object. In this case, it is set to "app.name".

The defaultMessage prop is used as a fallback message in case the translation for the specified ID is not found. In this case, it is set to "Simple Store".

By using FormattedMessage, the app name is translated based on the currently selected locale. When the locale state variable changes, the IntlProvider component will re-render and provide FormattedMessage with the translations for the new locale. With that, we should have something like this: Translating React Apps With Format.js Step One

Similarly, we can translate other text in our .src/App.js file by adding more properties to the messages object and using <FormattedMessage /> to display the values like this:

// ./src/App.js
// ...
import { IntlProvider } from "react-intl";
function App() {
  // ...
  // set up state for locale and messages
  const [locale, setLocale] = useState("es");
  const [messages, setMessages] = useState({
    "app.name": "Tienda sencilla",
    "app.description": "Una tienda sencilla con React",
    "app.products.caption": "Explora nuestros productos",
    "app.products.text":
      "Tenemos una amplia gama de productos para elegir. Explora nuestros productos y agrégalos a tu carrito.",
  })
  // ...
  return (
    <IntlProvider messages={messages} key={locale} locale={locale}>
      <div className="app">
        {/* ... */}
        <main className="app-main">
          <div className="wrapper">
            <section className="products app-section">
              <div className="wrapper">
                <header className="section-header products-header">
                  <div className="wrapper">
                    <h2 className="caption">
                      <FormattedMessage
                        id="app.products.caption"
                        defaultMessage={"Browse our products"}
                      />
                    </h2>
                    <p className="text">
                      <FormattedMessage
                        id="app.products.text"
                        defaultMessage={"We have a wide range of products to choose from. Browse our products and add them to your cart."}
                      />
                    </p>
                  </div>
                </header>
                <ul className="products-list">
                  {products.map((product) => (
                    <ProductItem
                      key={product.id}
                      product={product}
                      addToCart={handleAddToCart}
                    />
                  ))}
                </ul>
              </div>
            </section>
          </div>
        </main>
      </div>
    </IntlProvider>
  );
export default App;
Enter fullscreen mode Exit fullscreen mode

Here, we see how to use <FormattedMessage /> to translate other text in the ./src/App.js file by adding more properties to the messages object. In this example, messages contains properties for the app name, description, product caption, and product text.

The component displays these values by passing the relevant ID to the FormattedMessage, along with a default message to display if a translation is unavailable. In this example, the component displays the product caption and text using the ID corresponding to the property keys in the messages object with a fallback text passed to defaultMessage.

With that, we should have something like this: Translating React Apps With Format.js Step Two

Nice!

Translating the Cart and ProductItem components

We can take it even further by translating our Cart and ProductItem components. First, we need to add the translations in the messages object in ./src/App.js with the code below:

const [messages, setMessages] = useState({
  "app.name": "Tienda sencilla",
  "app.description": "Una tienda sencilla con React",
  "app.products.caption": "Explora nuestros productos",
  "app.products.text":
    "Tenemos una amplia gama de productos para elegir. Explora nuestros productos y agrégalos a tu carrito.",
  "app.cart": "Carrito",
  "app.cart.title": "Tu carrito",
  "app.cart.empty": "El carrito está vacío",
  "app.cart.items":
    "{count, plural, =0 {No tienes artículos} one {# articulo} other {# artículos }} en tu carrito",
  "app.cart.remove": "Eliminar",
  "app.cart.add": "Añadir a la cesta",
  "app.item.price": "{price, number, ::currency/EUR}",
});
Enter fullscreen mode Exit fullscreen mode

Here, we update the messages object in ./src/App.js by adding translations for the new components. The translations include strings such as "app.cart.title", "app.cart.empty", "app.cart.items", and "app.item.price". These translations will display the correct text in the Cart and ProductItem components.

Translate the Cart

Moving forward, we'll translate the Cart component. Go ahead and add the code below into ./src/components/Cart.jsx:

// ./src/components/Cart.jsx
import { FormattedMessage } from "react-intl";
const Cart = ({ cart, removeItem }) => {
  return (
    <div className="dropdown">
      <div className="trigger group">
        <button className="cta">
          <FormattedMessage id="app.cart" defaultMessage="Cart" />
        </button>
        <span className="badge"> {cart?.length}</span>
      </div>
      <div className="content">
        <aside className="cart">
          <header className="cart-header">
            <h2>
              <FormattedMessage
                id="app.cart.title"
                defaultMessage="Your Cart"
              />
            </h2>
            <p>
              <FormattedMessage
                id="app.cart.items"
                defaultMessage={`You have {count, plural, =0 {no items} one {one item} other {# items}} in your cart`}
                values={{ count: cart.length }}
              />
            </p>
          </header>
          <ul className="items">
            {cart.map((item) => {
              return (
                <li tabIndex={0} key={item.id} className="item">
                  <div className="item-image img-cont">
                    <img src={item.thumbnail} alt={item.name} />
                  </div>
                  <div className="item-details">
                    <h3>{item.title}</h3>
                    <p className="item-price">
                      <FormattedMessage
                        id="app.item.price"
                        defaultMessage={`{price, number, ::currency/USD}`}
                        values={{ price: item.price }}
                      />
                    </p>
                    <button onClick={() => removeItem(item)} className="cta">
                      <FormattedMessage
                        id="app.cart.remove"
                        defaultMessage="Remove"
                      />
                    </button>
                  </div>
                </li>
              );
            })}
          </ul>
        </aside>
      </div>
    </div>
  );
};
export default Cart;
Enter fullscreen mode Exit fullscreen mode

In the code above, we use FormattedMessage to display the translated strings. The FormattedMessage component takes an ID prop corresponding to the message object's translation key. It also takes a defaultMessage prop, which displays a default value if the translation is not found.

For example, FormattedMessage with id="app.cart.title" and defaultMessage="Your Cart" displays the text "Tu carrito" in Spanish when the locale is set to "es".

Take a close look at the code block below:

<p>
  <FormattedMessage
    id="app.cart.items"
    defaultMessage={`You have {count, plural, =0 {no items} one {one item} other {# items}} in your cart`}
    values={{ count: cart.length }}
  />
</p>
Here, "app.cart.items" corresponds to:
const [messages, setMessages] = useState({
  "app.cart.items":
    "{count, plural, =0 {No tienes artículos} one {# articulo} other {# artículos }} en tu carrito",
});
Enter fullscreen mode Exit fullscreen mode

Note that the message template uses count as a variable representing the cart's number of items. It has three possible options depending on the value of count:

  • =0 {no items}:: If the value of count is zero, this option is used, and the message will say "no items"
  • one {one item}:: If the value of count is one, this option is used, and the message will say "one item"
  • other {# items}:: If the value of count is anything other than zero or one, this option is used, and the message will say # items, where # is replaced with the value of count

This is for the message specified in defaultMessage and the one specified in the messages state. It is a message string that includes a plural form in Spanish. The message contains a count variable used to determine the correct plural form of the message. The syntax for the plural form is {count, plural, ...}.

In this syntax, the first argument is the variable name (count in this case), and the second is the type of pluralization used. Inside the plural argument, there are three cases:

  • =0 {No tienes artículos}: This is the case when the count variable is equal to zero, which means the cart is empty. The message, in this case, is No tienes artículos (you have no items)
  • one {# articulo}: This is when the count variable equals one. The message, in this case, is "# articulo" (one item)
  • other {# artículos}: The default case for all other counts. The message, in this case, is "# artículos" (X items), where X is the value of the count variable

So, the full message in Spanish would be "No tienes artículos" (you have no items) for an empty cart, "1 artículo" (1 item) for a cart with one item, and "# artículos"  for carts with two or more items. This follows the Intl MessageFormat that you can learn more about in the docs.

Translate the ProductItem

For the ProductItem component in ./src/components/ProductItem.jsx, add the following code:

// ./src/components/ProductItem.jsx
import { FormattedMessage } from "react-intl";
const ProductItem = ({ product, addToCart }) => {
  return (
    <li className="product">
      <div className="product-image img-cont">
        <img src={product.thumbnail} alt="" />
      </div>
      <div className="product-details">
        <h3>{product.title}</h3>
        <p>{product.description}</p>
        <span className="product-price">
          <FormattedMessage
            id="app.item.price"
            defaultMessage={`{price, number, ::currency/USD}`}
            values={{ price: product.price }}
          />
        </span>
      </div>
      <div className="product-actions">
        <button
          disabled={product?.isInCart}
          onClick={() => addToCart(product)}
          className="cta"
        >
          <FormattedMessage id="app.cart.add" defaultMessage="Add to Cart" />
        </button>
      </div>
    </li>
  );
};
export default ProductItem;
Enter fullscreen mode Exit fullscreen mode

One thing we should take note of is the product-price, as shown below:

<span className="product-price">
  <FormattedMessage
    id="app.item.price"
    defaultMessage={`{price, number, ::currency/USD}`}
    values={{ price: product.price }}
  />
</span>
"app.item.price" corresponds to:
const [messages, setMessages] = useState({
  "app.item.price": "{price, number, ::currency/EUR}",
});
Enter fullscreen mode Exit fullscreen mode

In the code above, "{price, number, ::currency/EUR}" is a message format pattern used in the react-intl library. It specifies how to format a price variable as a number with a currency symbol of EUR.

The curly braces {} indicate placeholders in the message pattern. Meanwhile, price is the name of the variable that should be substituted into the pattern. The number keyword specifies that the variable should be formatted as a number.

The ::currency/EUR option indicates that the number should be formatted as a currency value using the EUR currency symbol. With all that done, our app should be completely translated: Translated React App Using Format.js Awesome!

Switching between languages

One final and important feature to add to our application is a language switch feature. Follow along below.

Create locale JSON files

First, we’ll create the JSON files for each locale. For the Spanish locale, we create a new ./src/locales/es.json file, as shown below:

{
  "app.name": "Tienda sencilla",
  "app.description": "Una tienda sencilla con React",
  "app.products.caption": "Explora nuestros productos",
  "app.products.text": "Tenemos una amplia gama de productos para elegir. Explora nuestros productos y agrégalos a tu carrito.",
  "app.cart": "Carrito",
  "app.cart.title": "Tu carrito",
  "app.cart.empty": "El carrito está vacío",
  "app.cart.items": "{count, plural, =0 {No tienes artículos} one {# articulo} other {# artículos }} en tu carrito",
  "app.cart.remove": "Eliminar",
  "app.cart.add": "Añadir a la cesta",
  "app.item.price": "{price, number, ::currency/EUR}"
}
For the English locale, we create a ./src/locales/en.json file:
{
  "app.name": "Simple store",
  "app.description": "A simple store with React",
  "app.products.caption": "Explore our products",
  "app.products.text": "We have a wide range of products to choose from. Explore our products and add them to your cart.",
  "app.cart": "Cart",
  "app.cart.title": "Your Cart",
  "app.cart.empty": "Your cart is empty",
  "app.cart.items": "{count, plural, =0 {You have no items} one {You have one item} other {You have # items}} in your cart",
  "app.cart.remove": "Remove",
  "app.cart.add": "Add to cart",
  "app.item.price": "{price, number, ::currency/USD}"
}
Enter fullscreen mode Exit fullscreen mode

Bravo!

Dynamically import locale messages

Now, we'll use the useEffect Hook to asynchronously load the messages for the selected locale when the component is mounted or when the locale state is changed. Here's the code:

// ./src/App.js
// ...
function App() {
  // ....
  const [locale, setLocale] = useState("es");
  const [messages, setMessages] = useState({
    // ...
  });
  // ...
  // function to dynamically import messages depending on locale
  useEffect(() => {
    import(`./locales/${locale}.json`).then((messages) => {
      console.log({
        messages,
      });
      setMessages(messages);
    });
  }, [locale]);
  return (
    // ...
  )
};
export default App;
Enter fullscreen mode Exit fullscreen mode

The code above uses dynamic imports to load the JSON file containing the messages for the selected locale. Once the messages are loaded, it sets the messages state with the loaded messages. Finally, add a select input to switch between the locales, as shown below:

// ./src/App.js
// ...
function App() {

  // ...
  return (
    <IntlProvider messages={messages} key={locale} locale={locale}>
      <div className="app">
        <header className="app-header">
          <div className="wrapper">
            <div className="app-name">
              <FormattedMessage id="app.name" defaultMessage={"Simple Store"} />
            </div>
            <div style={{ display: "flex", gap: "1rem" }}>
              <Cart cart={cart} removeItem={handleRemoveFromCart} />
              <select
                onChange={(e) => {
                  setLocale(e.target.value);
                }}
                value={locale}
                name="language-select"
                id="language-select"
                className="select-input"
              >
                <option value="es">Español</option>
                <option value="en">English</option>
              </select>
            </div>
          </div>
        </header>
        {/* ... */}
      </div>
    </IntlProvider>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

With that, we should have something like this: Final Translated React App Using Format.js Awesome.

Conclusion

In this article, we learned how to use Format.js to translate your React application. Ultimately, by following the instructions in this article, you can quickly translate your React application using Format.js, expanding your audience and improving the UX of your app.

Whether you're building a simple store or a complex web app, internationalization is an important consideration that can significantly enhance the UX for non-native speakers of your language. By using the power of Format.js, you can easily add support for multiple languages and cultures to your React applications. Here is a link to a deployed preview of the example project built in this tutorial.

If you want to learn more about Format.js or internationalization in general, several resources are available online. Here are a few recommended ones:

  • Format.js documentation: This is the official documentation for Format.js. It provides a comprehensive guide to using Format.js and details the features and APIs
  • React Intl documentation: If you're specifically interested in using Format.js with React, this documentation provides a guide to using the React Intl library, built on top of Format.js
  • Unicode Common Locale Data Repository: The Unicode CLDR provides a comprehensive set of locale data, including information about date and time formats, currency symbols, and other language-specific details. This is a valuable resource if you need to support multiple languages and cultures

LogRocket: Full visibility into your production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket Signup

LogRocket combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?

Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.

No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — start monitoring for free.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player