<?xml version="1.0" encoding="UTF-8"?>
    <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
      <channel>
        <atom:link href="https://dev-blog.vinissimus.com/rss.xml" rel="self" type="application/rss+xml" />
        <title>Vinissimus developers</title>
        <description>Vinissimus developers blog.</description>
        <link>https://dev-blog.vinissimus.com</link>
        <lastBuildDate>Mon, 17 May 2021 18:22:09 GMT</lastBuildDate>
      
            <item>
              <title>Virtual Sommelier, text classifier in the browser</title>
              <description>How to develop a food text classifier to suggest the best wines to pair with the name of a dish or an ingredient.</description>
              <link>https://dev-blog.vinissimus.com/food-pairing-classifier</link>
              <guid isPermaLink="false">https://dev-blog.vinissimus.com/food-pairing-classifier/</guid>
              <pubDate>Mon, 17 May 2021 00:00:00 GMT</pubDate>
              <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>At Vinissimus, we have recently launched a <a href="https://www.vinissimus.com/en/virtual-sommelier/">virtual sommelier</a> that suggests wines given a text of a food dish.</p>
<img width="400" height="257" src="https://dev-blog.vinissimus.com/images/blog-images/virtual-sommelier.png" alt="example" class="center">

<p>In this article we&#39;ll explore the development of this suggester, trained with machine learning and consumed directly from the browser.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Have a database with many wines (there are +15000 wines in our database), with food labels (in total we have <strong>+1000 food labels</strong>).</li>
</ul>
<h2 id="requirements">Requirements</h2>
<ul>
<li>Given a text, for example &quot;Wine for paella&quot; (or just &quot;paella&quot;), returns all the labels among the +1000 we have that are related: paella, seafood, rice, shrimp...</li>
<li>Fast to train and use.</li>
</ul>
<h2 id="type-of-problem-to-solve">Type of problem to solve</h2>
<p>Before starting with the project, it&#39;s necessary to know what kind of problem we are facing; regression, binary-class classification, multi-class classification, multi-class multi-label classification... To know this, we must know what each term is.</p>
<h3 id="regression">Regression</h3>
<p>The regression makes sense when the value we want to predict is a numerical value that can give a new value outside the training values.</p>
<p>It&#39;s not the type of problem we want to solve ❌...</p>
<h3 id="classification">Classification</h3>
<p>We use a classification, when the value we want to predict is a value within a set of predefined values (classes).</p>
<p>Okay, this is what we want ✅.</p>
<p>Within the classification, there are:</p>
<ul>
<li><strong>Binary single-label</strong>: predicts a class between two classes <em>(not our case, since we have 1000 classes ❌ )</em>.</li>
<li><strong>Multi-class single-label</strong>: predicts a class between more than two classes <em>(not our case either, since we don&#39;t have to choose 1. For example for paella we can recommend: paella, rice and seafood labels ❌ )</em>.</li>
<li><strong>Multi-class multi-label</strong>: predicts a range of classes between more than two classes <em>(This is what we want ✅ )</em>.</li>
</ul>
<p>It is important to know that our problem is a <strong>multi-class multi-label classification</strong> as this will determine some hyperparameters to use such as the loss function.</p>
<h2 id="exploring-techniquestools">Exploring techniques/tools</h2>
<p>Now that we know that the problem we want to solve is a multi-class multi-label classification, let&#39;s explore a few ways in order to solve the problem, considering that we want to load the model directly from the browser.</p>
<h3 id="tensorflowjs">Tensorflow.js</h3>
<p><em><strong>Spoiler</strong>: we&#39;ll discard it.</em></p>
<p><a href="https://www.tensorflow.org/js">Tensorflow</a> is one of the most used frameworks for deeplearning, it allows you to create neural network models in a simple and declarative way. It also has a JavaScript version that allows us to load an already trained model from the browser to make predictions. So initially this tool could be considerated adequate to solve the problem.</p>
<p>Tensorflow works with tensors (n-dimensional vectors) as a lingua franca, so to work with text we must transform the text into tensors. To do this there are several embedding models, however we&#39;ll use the <a href="https://tfhub.dev/google/universal-sentence-encoder/1">Universal Sentence Encoder</a> that is already optimized to work from the browser, because to make the prediction we must also pass the text to tensor from the browser.</p>
<img src="https://dev-blog.vinissimus.com/images/blog-images/example-classification.png" alt="example" class="center transparent">

<p>We can transform our entire dataset into encodings with:</p>
<pre><code class="language-js"><span class="hljs-keyword">import</span> <span class="hljs-string">&#x27;@tensorflow/tfjs-node-gpu&#x27;</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> use <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@tensorflow-models/universal-sentence-encoder&#x27;</span>
<span class="hljs-keyword">import</span> data <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./data.json&#x27;</span>
<span class="hljs-keyword">import</span> _ <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;lodash&#x27;</span>
<span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;fs&#x27;</span>

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">&#x27;Encoding...&#x27;</span>)
use
  .load()
  .then(<span class="hljs-function">(<span class="hljs-params">model</span>) =&gt;</span>
    model.embed(data.map(<span class="hljs-function">(<span class="hljs-params">{ text }</span>) =&gt;</span> text.trim().toLowerCase()))
  )
  .then(<span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span> {
    fs.writeFileSync(
      <span class="hljs-string">&#x27;embeddings.json&#x27;</span>,
      <span class="hljs-built_in">JSON</span>.stringify(_.chunk(<span class="hljs-built_in">Array</span>.from(r.dataSync()), <span class="hljs-number">512</span>))
    )
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">&#x27;Saved...&#x27;</span>)
  })</code></pre>
<p>And use a network architecture like this:</p>
<pre><code class="language-js"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> tf <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@tensorflow/tfjs&#x27;</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">&#x27;@tensorflow/tfjs-node-gpu&#x27;</span>

<span class="hljs-keyword">const</span> model = tf.sequential()

model.add(
  tf.layers.dense({
    <span class="hljs-attr">inputShape</span>: [<span class="hljs-number">512</span>],
    <span class="hljs-attr">activation</span>: <span class="hljs-string">&#x27;relu&#x27;</span>,
    <span class="hljs-attr">units</span>: <span class="hljs-number">512</span>,
  })
)

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i += <span class="hljs-number">1</span>) {
  model.add(
    tf.layers.dense({
      <span class="hljs-attr">inputShape</span>: [<span class="hljs-number">512</span>],
      <span class="hljs-attr">activation</span>: <span class="hljs-string">&#x27;relu&#x27;</span>,
      <span class="hljs-attr">units</span>: <span class="hljs-number">512</span>,
    })
  )
}

model.add(
  tf.layers.dense({
    <span class="hljs-attr">activation</span>: <span class="hljs-string">&#x27;sigmoid&#x27;</span>,
    <span class="hljs-attr">units</span>: classes.length,
  })
)

model.compile({
  <span class="hljs-attr">loss</span>: <span class="hljs-string">&#x27;binaryCrossentropy&#x27;</span>,
  <span class="hljs-attr">optimizer</span>: <span class="hljs-string">&#x27;adam&#x27;</span>,
  <span class="hljs-attr">metrics</span>: [<span class="hljs-string">&#x27;accuracy&#x27;</span>],
})</code></pre>
<p>To train the model, pass it the encodings that we have generated:</p>
<pre><code class="language-js"><span class="hljs-keyword">import</span> embeddings <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./embeddings.json&#x27;</span>
<span class="hljs-keyword">import</span> outputs <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./outputs.json&#x27;</span>

<span class="hljs-keyword">const</span> dataset = tf.data
  .generator(<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>* <span class="hljs-title">gen</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; embeddings.length; i += <span class="hljs-number">1</span>) {
      <span class="hljs-keyword">yield</span> {
        <span class="hljs-attr">xs</span>: embeddings[i],
        <span class="hljs-attr">ys</span>: outputs[i],
      }
    }
  })
  .batch(<span class="hljs-number">128</span>)

model.fitDataset(dataset, { <span class="hljs-attr">epochs</span>: <span class="hljs-number">600</span> }).then(<span class="hljs-function">(<span class="hljs-params">history</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(history)
  model.save(<span class="hljs-string">&#x27;file://./model&#x27;</span>)
})</code></pre>
<p>Of course there are many hyperparameters to play with: number of epochs, batch size, dense layer activation functions, optimizer, etc. However, after spending a lot of time we haven&#39;t found yet the best way to solve two problems that had arisen when we tried to solve the problem with Tensorflow:</p>
<ul>
<li>The time needed to train with +1000 classes and +400000 examples in the dataset made it unfeasible. Around 10 days of training.</li>
<li>Testing with fewer classes and examples works well... But calculating the embeddings with the Universal Sentense encoder is a bit expensive (although the prediction is cheaper). To make the prediction we have to pass the embeddings so it&#39;s a price to pay.</li>
</ul>
<p>One of the requirements (Fast to train and use) was not feasible with Tensorflow.js. We have to <strong>look for other alternatives</strong>!</p>
<h3 id="fasttext">FastText</h3>
<p><em><strong>Spoiler</strong>: This is what we finally use.</em></p>
<p><a href="https://fasttext.cc/">FastText</a> is a Facebook tool that, among other things, is used to train text classification models. Unlike Tensorflow.js, it is more intended to work with text so we don&#39;t need to pass a tensor and we can use the text directly. Training a model with it is much faster and there are fewer hyperparameters. Besides, to use the model from the browser is possible through WebAssembly. So it&#39;s a good alternative to try. Moreover, we can directly use the fastText CLI, which makes it easier to test combinations.</p>
<p>After some tests, we found that fastText met the requirements. The following sections of the article will focus on the use of FastText.</p>
<h2 id="preparing-the-data--data-augmentation">Preparing the data &amp; data augmentation</h2>
<p>FastText expects a text file with different labels and texts with a similar format to this one:</p>
<pre><code>__label__1606 __label__433 rabbit with mushrooms</code></pre>
<p>The text <code>rabbit with mushrooms</code> is related to the labels with the id <code>1606</code> <em>(id of the &quot;rabbit with mushrooms&quot; label)</em> and <code>433</code> <em>(id of the &quot;rabbit&quot; label)</em>.</p>
<p>The initial problem is that we don&#39;t start from ready-made sentences because the search engine didn&#39;t exist before, so we have to generate them from each label we have.</p>
<p>Surely we could put more labels on it, for example, white meat, but how do we make all those relationships?</p>
<p>What we did is to save an array with each label in a JSON, and make several scripts for each label to have extra information such as: synonyms, plurals, closest words, relations, etc. For each language we have (en, es, it, fr and de).</p>
<ul>
<li>For <strong>synonyms</strong>, <strong>plurals</strong> and missing translations we used the <a href="https://www.deepl.com/en/docs-api/">API of DeepL</a>.</li>
<li>For <strong>closest words</strong>, FastText has available <a href="https://fasttext.cc/docs/en/pretrained-vectors.html">Wikipedia vectors</a> to search the closest words with k-nearest.</li>
<li>For <strong>relations</strong>, we simply made several iterations in the array applying logics like: all words that have &quot;beef, goat, etc&quot; are marked as children of &quot;red meat&quot;. And so on with all the detected labels that were more generic, such as: fish, rice, pasta, etc.</li>
</ul>
<p>Apart from normalizing each text with this simple JS function:</p>
<pre><code class="language-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">normalize</span>(<span class="hljs-params">text = <span class="hljs-string">&#x27;&#x27;</span></span>) </span>{
  <span class="hljs-keyword">return</span> text
    .trim()
    .toLowerCase()
    .normalize(<span class="hljs-string">&#x27;NFD&#x27;</span>)
    .replace(<span class="hljs-regexp">/[\u0300-\u036f]/g</span>, <span class="hljs-string">&#x27;&#x27;</span>)
}</code></pre>
<p><strong>Example of 2 items of this array:</strong></p>
<pre><code class="language-json">[
  {
    <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;1109&quot;</span>,
    <span class="hljs-attr">&quot;txt&quot;</span>: {
      <span class="hljs-attr">&quot;es&quot;</span>: <span class="hljs-string">&quot;revueltos&quot;</span>,
      <span class="hljs-attr">&quot;fr&quot;</span>: <span class="hljs-string">&quot;oeufs brouilles&quot;</span>,
      <span class="hljs-attr">&quot;de&quot;</span>: <span class="hljs-string">&quot;ruhreier&quot;</span>,
      <span class="hljs-attr">&quot;it&quot;</span>: <span class="hljs-string">&quot;uova strapazzate&quot;</span>,
      <span class="hljs-attr">&quot;en&quot;</span>: <span class="hljs-string">&quot;scrambled eggs&quot;</span>
    },
    <span class="hljs-attr">&quot;similar&quot;</span>: [<span class="hljs-string">&quot;fritos&quot;</span>, <span class="hljs-string">&quot;revuelto&quot;</span>, <span class="hljs-string">&quot;egg&quot;</span>, <span class="hljs-string">&quot;huevo&quot;</span>, <span class="hljs-string">&quot;estrellados&quot;</span>],
    <span class="hljs-attr">&quot;parent&quot;</span>: [<span class="hljs-string">&quot;779&quot;</span>]
  },
  {
    <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;779&quot;</span>,
    <span class="hljs-attr">&quot;txt&quot;</span>: {
      <span class="hljs-attr">&quot;es&quot;</span>: <span class="hljs-string">&quot;huevos&quot;</span>,
      <span class="hljs-attr">&quot;fr&quot;</span>: <span class="hljs-string">&quot;oeuf&quot;</span>,
      <span class="hljs-attr">&quot;de&quot;</span>: <span class="hljs-string">&quot;eier&quot;</span>,
      <span class="hljs-attr">&quot;it&quot;</span>: <span class="hljs-string">&quot;uova&quot;</span>,
      <span class="hljs-attr">&quot;en&quot;</span>: <span class="hljs-string">&quot;eggs&quot;</span>
    },
    <span class="hljs-attr">&quot;similar&quot;</span>: [
      <span class="hljs-string">&quot;uovo&quot;</span>,
      <span class="hljs-string">&quot;œuf&quot;</span>,
      <span class="hljs-string">&quot;ei&quot;</span>,
      <span class="hljs-string">&quot;kartoffel omelette&quot;</span>,
      <span class="hljs-string">&quot;omelette&quot;</span>,
      <span class="hljs-string">&quot;huevo&quot;</span>,
      <span class="hljs-string">&quot;spiegelei&quot;</span>,
      <span class="hljs-string">&quot;tortilla de patatas&quot;</span>,
      <span class="hljs-string">&quot;tortilla&quot;</span>,
      <span class="hljs-string">&quot;gebraten&quot;</span>,
      <span class="hljs-string">&quot;tortillas&quot;</span>,
      <span class="hljs-string">&quot;fritos&quot;</span>,
      <span class="hljs-string">&quot;frito&quot;</span>,
      <span class="hljs-string">&quot;fichi&quot;</span>,
      <span class="hljs-string">&quot;ous&quot;</span>
    ],
    <span class="hljs-attr">&quot;parent&quot;</span>: []
  }
]</code></pre>
<p>Preparing this array has been the most laborious part of the whole process. Once this array is ready, then we can generate with the format that FastText is expecting as many food sentences as possible by adding plurals, synonyms, knowing which generic labels to put for each sentence, etc. Besides we can add extra words to the sentences such as &quot;Wine for ...&quot;, &quot;Pairing for ...&quot;, etc.</p>
<p>So we went from 1000 labels, and therefore 1000 possible sentences with 1 label per sentence, to increase to 74,000 sentences and each sentence with several labels.</p>
<h2 id="training">Training</h2>
<p>Once the file with all the sentences and labels has been generated, we can train the model. With FastText we can do this directly with the CLI. After playing a little with the hyperparameters, this was the command that best converged our loss function:</p>
<pre><code>./fasttext supervised -input data/dataset.txt -output model -epoch 50 -lr 0.1 -lrUpdateRate 1000 -minCount 1 -minn 3 -maxn 6 -wordNgrams 2 -dim 100 -neg 20 -loss ova</code></pre>
<p>As a <strong>loss function</strong> we use the <strong>ova</strong> (one vs all) which is the one that best suits us for a multi-class multi-label classification problem. Other parameters such as epoch, learning rate, etc, are the result of playing with the hyperparameters so that the loss function is as close to 0 as possible (where there is less error).</p>
<p><strong>minn</strong> and <strong>maxn</strong> are important to avoid misspelings when typing. So if people search for &quot;pizzza&quot;, for example, they will get the same results as &quot;pizza&quot;. On the other hand, it significantly increases the final size of the model. I&#39;ll explain later how to fix this.</p>
<p>If you run the command, you&#39;ll see that the training time is much faster than using Tensorflow, with 20min maximum.</p>
<h2 id="evaluation">Evaluation</h2>
<p>To know how well your model is doing, one of the things to look at during the training, as I said, is how the loss is closer to zero. We can also look how the accuracy is closer to 100. However, once it&#39;s already trained we can evaluate how well the model is doing by looking at two other factors: Recall and precision. To do this, FastText has a <a href="https://fasttext.cc/docs/en/cheatsheet.html#text-classification">test</a> command that can be applied to a set of sentences that have not been used during training.</p>
<h2 id="reducing-the-model-size-quantization">Reducing the model size: Quantization</h2>
<p>One problem we encountered was that the size of the model occupied 400mb, so it was totally unfeasible to be used in the browser... This is the cost we include for avoiding misspelings with minn and maxn parameters.</p>
<p>To solve this, we use a well-known technique in machine learning called quantization, which consists of reducing the memory size reserved for each weight.</p>
<p>Fortunately, FastText has its own implementation to apply quantization in its models. For more details they published a <a href="https://arxiv.org/pdf/1612.03651.pdf">paper</a>.</p>
<p>It&#39;s important to be aware that applying quantization is not a panacea, and that we are likely to lose some model accuracy.</p>
<p>We apply the quantization with this command:</p>
<pre><code>./fasttext quantize -output model -input data/dataset.txt -qnorm -retrain -epoch 1 -cutoff 100000</code></pre>
<p>With this, we drop from 400mb to 4mb! 100 times less. 4mb is still big for the browser, but more feasible...</p>
<h2 id="using-the-model-on-the-browser">Using the model on the browser</h2>
<p>To use the model trained with FastText from the browser, it is necessary to load it <a href="https://github.com/facebookresearch/fastText/tree/master/webassembly">via WebAssembly</a>. However, you don&#39;t require a WebAssembly knowledge as you can use the <code>fasttext.js</code> file which has all the glue code.</p>
<p>We can load the model dynamically with the following function:</p>
<pre><code class="language-js"><span class="hljs-keyword">const</span> [model, setModel] = useState()

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onLoadModel</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { FastText, addOnPostRun } = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&#x27;./fasttext.js&#x27;</span>)
  addOnPostRun(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> ft = <span class="hljs-keyword">new</span> FastText()
    setModel(<span class="hljs-keyword">await</span> ft.loadModel(<span class="hljs-string">&#x27;./model.ftz&#x27;</span>))
  })
}</code></pre>
<p>In the first part of the above example we&#39;ve loaded the fasttext library. Then we&#39;ve loaded the model and saved it, in this case, in the React state, so that we can use it later.</p>
<p>For label prediction through a text, we can use this function:</p>
<pre><code class="language-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">predictLabelsFromText</span>(<span class="hljs-params">text</span>) </span>{
  <span class="hljs-keyword">const</span> threshold = <span class="hljs-number">0.5</span>
  <span class="hljs-keyword">const</span> predictions = []
  <span class="hljs-keyword">const</span> numLabels = <span class="hljs-number">5</span>
  <span class="hljs-keyword">const</span> res = model.predict(normalize(text), numLabels, <span class="hljs-number">0</span>)

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; res.size(); i += <span class="hljs-number">1</span>) {
    predictions.push(res.get(i))
  }

  <span class="hljs-keyword">return</span> predictions
    .filter(<span class="hljs-function">(<span class="hljs-params">[score]</span>) =&gt;</span> score &gt; threshold)
    .sort(<span class="hljs-function">(<span class="hljs-params">[scoreA], [scoreB]</span>) =&gt;</span> scoreB - scoreA)
    .map(<span class="hljs-function">(<span class="hljs-params">[score, label]</span>) =&gt;</span> label.replace(<span class="hljs-string">&#x27;__label__&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>))
}</code></pre>
<p>Given a text, this function returns the 5 related labels (if the probability is higher than 50%, controled by the threshold).</p>
<p>Compared to Tensorflow, the prediction here is very fast.</p>
<h2 id="conclusions">Conclusions</h2>
<p>In this article we have seen how to train a text prediction model easily using FastText and how to use it directly from the browser.</p>
<p>The example used in the article is a real example of a project we developed at Vinissimus, in which, given a text about food, relates to the referenced food labels in order to be able to recommend a wine.</p>
<p>You can test the result in:</p>
<ul>
<li><a href="https://www.vinissimus.co.uk/en/virtual-sommelier/">https://www.vinissimus.co.uk/en/virtual-sommelier/</a> (English)</li>
<li><a href="https://www.vinissimus.com/es/virtual-sommelier/">https://www.vinissimus.com/es/virtual-sommelier/</a> (Spanish)</li>
<li><a href="https://www.italvinus.it/it/virtual-sommelier/">https://www.italvinus.it/it/virtual-sommelier/</a> (Italian)</li>
<li><a href="https://www.vinissimus.fr/fr/virtual-sommelier/">https://www.vinissimus.fr/fr/virtual-sommelier/</a> (French)</li>
<li><a href="https://www.hispavinus.de/de/virtual-sommelier/">https://www.hispavinus.de/de/virtual-sommelier/</a> (German)</li>
</ul>
]]></content:encoded>
            </item>
            <item>
              <title>Next-translate - Version 1.0 Released</title>
              <description>Next-translate is an i18n library to keep the translations as simple as possible in a Next.js environment. Today we announce the release of version 1.0.</description>
              <link>https://dev-blog.vinissimus.com/next-translate-1.0</link>
              <guid isPermaLink="false">https://dev-blog.vinissimus.com/next-translate-1.0/</guid>
              <pubDate>Wed, 09 Dec 2020 00:00:00 GMT</pubDate>
              <content:encoded><![CDATA[<p>Today is the day. The Vinissimus Team is very proud and happy to announce the much-anticipated <a href="https://github.com/vinissimus/next-translate/releases/tag/1.0.0">version 1.0</a> of <a href="https://github.com/vinissimus/next-translate">Next-translate</a> library. It&#39;s been a year since the first <a href="https://aralroca.com/blog/next-translate-released">version 0.1</a> and a lot happened <em>(+160 closed issues)</em>.</p>

<div align="center"><small>Showing version 1.0 when it was experimental</small></div>

<h2 id="what-is-next-translate">What is Next-translate?</h2>
<p><a href="https://github.com/vinissimus/next-translate">Next-translate</a> is a library to keep the translations as simple as possible in a Next.js environment. It arose from the need in <a href="https://www.vinissimus.com">Vinissimus</a> to reduce the bundle size when we realized that the <a href="https://github.com/isaachinman/next-i18next">next-i18next</a> library we used occupied 20 times more than Preact. We decided to create our own library with clear goals. In addition, we took advantage of this to support SSG, since next-i18next required the translations to be loaded into a getInitialProps, sacrificing automatic page optimization.</p>
<h3 id="goals">Goals</h3>
<ul>
<li>Being a small i18n library (~1kb).</li>
<li>Cover the i18n basics: interpolation, plurals, Trans component, t function, nested translations, fallbacks...</li>
<li>Only load the necessary translations for each page and language. If you navigate to <code>/en/about</code>, just load the <code>about</code> namespace in English.</li>
<li>Support automatic page optimization (SSG).</li>
<li>Make it easy to integrate translations on pages.</li>
<li>Make it easy to migrate to future changes in the Next.js core.</li>
</ul>
<h2 id="what-does-version-10-provide">What does version 1.0 provide?</h2>
<h3 id="nextjs-plugin">Next.js plugin</h3>
<p>Last year, to achieve the goals of the previous point, we had to create a workaround by doing a &quot;build step&quot; to generate the static pages with all the languages. We had to work in a different directory than &quot;pages&quot;. It worked, but it was a bit uncomfortable. Today, in version 1.0, we have been able to remove this workaround while maintaining all the goals.</p>
<p>Now, the Next.js plugin is the new toy. It is responsible for loading the necessary translations on each page through a webpack loader. This way, you don&#39;t have to write on each page a getStaticProps, getServerSideProps or any other method you want to use to load the translations. The plugin will take care of it by overwriting the method you have or using a default one (getStaticProps).</p>
<a href="https://dev-blog.vinissimus.com/images/blog-images/example-next-translate-plugin.png">
  <figure align="center">
    <img class="center" src="https://dev-blog.vinissimus.com/images/blog-images/example-next-translate-plugin.png" alt="Labelai logo" />
    <figcaption><small>Working with Next-translate 1.0</small></figcaption>
  </figure>
</a>

<p>The plugin is needed to cover the last two goals mentioned in the previous point:</p>
<ul>
<li>Make it easy to integrate translations on pages.</li>
<li>Make it easy to migrate to future changes in the Next.js core.</li>
</ul>
<blockquote>
<p><em>If you don&#39;t want the plugin to inject the webpack loader so you can have control over how to load the namespaces on each page, you can use the <code>loader=false</code> in the configuration, and then manually load the namespaces with <a href="https://github.com/vinissimus/next-translate/tree/1.0.0#loadnamespaces">loadNamespaces</a>.</em></p>
</blockquote>
<h3 id="improve-plurals-support">Improve plurals support</h3>
<p>In version <code>0.x</code> the support of plurals was quite simple. Now with <code>1.0</code> we&#39;ve <a href="https://github.com/vinissimus/next-translate/tree/1.0.0#5-plurals">improved the support</a> by adding 6 plural forms (taken from <a href="http://cldr.unicode.org/index/cldr-spec/plural-rules">CLDR Plurals page</a>):</p>
<ul>
<li><code>zero</code></li>
<li><code>one</code> (singular)</li>
<li><code>two</code> (dual)</li>
<li><code>few</code> (paucal)</li>
<li><code>many</code> (also used for fractions if they have a separate class)</li>
<li><code>other</code> (required—general plural form—also used if the language only has a single form)</li>
</ul>
<h3 id="consume-translations-outside-pages--components">Consume translations outside pages / components</h3>
<p>We add the <a href="https://github.com/vinissimus/next-translate/tree/1.0.0#gett">getT</a> asynchronous function to load the <code>t</code> function outside components / pages. It works on both server-side and client-side.</p>
<p>Unlike the useTranslation hook, we can use here any namespace. It doesn&#39;t have to be a namespace defined in the &quot;pages&quot; configuration. It will <strong>download the namespace</strong> indicated as a parameter <strong>on runtime</strong>.</p>
<p>Example inside <code>getStaticProps</code>:</p>
<pre><code class="language-js"><span class="hljs-keyword">import</span> getT <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;next-translate/getT&#x27;</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params">{ locale }</span>) </span>{
  <span class="hljs-keyword">const</span> t = <span class="hljs-keyword">await</span> getT(locale, <span class="hljs-string">&#x27;common&#x27;</span>)
  <span class="hljs-keyword">const</span> title = t(<span class="hljs-string">&#x27;title&#x27;</span>)
  <span class="hljs-keyword">return</span> { <span class="hljs-attr">props</span>: { title } }
}</code></pre>
<p>Example inside API Route, ex: <code>/fr/api/example</code>:</p>
<pre><code class="language-js"><span class="hljs-keyword">import</span> getT <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;next-translate/getT&#x27;</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handler</span>(<span class="hljs-params">req, res</span>) </span>{
  <span class="hljs-keyword">const</span> t = <span class="hljs-keyword">await</span> getT(req.query.__nextLocale, <span class="hljs-string">&#x27;common&#x27;</span>)
  <span class="hljs-keyword">const</span> title = t(<span class="hljs-string">&#x27;title&#x27;</span>)

  res.statusCode = <span class="hljs-number">200</span>
  res.setHeader(<span class="hljs-string">&#x27;Content-Type&#x27;</span>, <span class="hljs-string">&#x27;application/json&#x27;</span>)
  res.end(<span class="hljs-built_in">JSON</span>.stringify({ title }))
}</code></pre>
<h2 id="useful-links">Useful links</h2>
<ul>
<li><a href="https://github.com/vinissimus/next-translate/tree/1.0.0#2-getting-started">How to start with Next-translate 1.0</a></li>
<li><a href="https://github.com/vinissimus/next-translate/blob/1.0.0/docs/migration-guide-1.0.0.md">Migration guide 0.x to 1.0</a></li>
<li><a href="https://github.com/vinissimus/next-translate/releases/tag/1.0.0">Release 1.0 notes</a></li>
<li><a href="https://github.com/vinissimus/next-translate/tree/1.0.0/examples">Examples with Next-translate 1.0</a></li>
</ul>
<h2 id="contributors">Contributors</h2>
<p>During 2020, +20 people contributed to the Next-translate codebase, implementing new features, fixing bugs and issues, writing documentation, and so on. The Vinissimus team would like to thank all of you who helped us build Next-translate to become what it is today.</p>
<p><a href="https://github.com/vincentducorps">@vincentducorps</a>, <a href="https://github.com/giovannigiordano">@giovannigiordano</a>, <a href="https://github.com/dnepro">@dnepro</a>,
<a href="https://github.com/BjoernRave">@BjoernRave</a>, <a href="https://github.com/croutonn">@croutonn</a>, <a href="https://github.com/justincy">@justincy</a>, <a href="https://github.com/YannSuissa">@YannSuissa</a>, <a href="https://github.com/thanhlmm">@thanhlmm</a>, <a href="https://github.com/stpch">@stpch</a>, <a href="https://github.com/shunkakinoki">@shunkakinoki</a>, <a href="https://github.com/rekomat">@rekomat</a>, <a href="https://github.com/psanlorenzo">@psanlorenzo</a>, <a href="https://github.com/pgrimaud">@pgrimaud</a>, <a href="https://github.com/lone-cloud">@lone-cloud</a>, <a href="https://github.com/kidnapkin">@kidnapkin</a>, <a href="https://github.com/hibearpanda">@hibearpanda</a>, <a href="https://github.com/ftonato">@ftonato</a>, <a href="https://github.com/dhobbs">@dhobbs</a>, <a href="https://github.com/bickmaev5">@bickmaev5</a>, <a href="https://github.com/Faulik">@Faulik</a>, <a href="https://github.com/josephfarina">@josephfarina</a>, <a href="https://github.com/gurkerl83">@gurkerl83</a>, <a href="https://github.com/aralroca">@aralroca</a></p>
]]></content:encoded>
            </item>
      </channel>
    </rss>