<feed xmlns="http://www.w3.org/2005/Atom"><title>Language and Translation</title><id>https://phabricator.wikimedia.org/phame/blog/feed/22/</id><link rel="self" type="application/atom+xml" href="https://phabricator.wikimedia.org/phame/blog/feed/22/" /><updated>2025-04-08T16:57:14+00:00</updated><entry><title>sentencex: Empowering NLP with Multilingual Sentence Extraction</title><link href="/phame/live/22/post/308/sentencex_empowering_nlp_with_multilingual_sentence_extraction/" /><id>https://phabricator.wikimedia.org/phame/post/view/308/</id><author><name>santhosh (Santhosh Thottingal)</name></author><published>2023-09-27T10:55:18+00:00</published><updated>2023-10-19T06:36:53+00:00</updated><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>Sentence segmentation is a fundamental process in natural language processing. It involves breaking down a given text into individual sentences, a task that finds applications in various contexts. Whether you need to split a paragraph into sentences for further analysis or present sentence boundaries in a user-friendly frontend application, sentence segmentation is crucial.</p>

<p>At first glance, identifying sentence boundaries might seem straightforward – just look for a period or full stop. However, it quickly becomes complex when you consider cases where a period is used for abbreviations such as &quot;Dr.&quot; or in numerical values like &quot;3.14.&quot; This simple punctuation mark doesn&#039;t always signal the end of a sentence.</p>

<p>In many languages, the period isn&#039;t the standard sentence delimiter. For instance, Hindi uses a unique character called the &#039;danda&#039; sign (।) to indicate the end of a sentence. Additionally, sentence segmentation must account for periods inside quotations, which don&#039;t denote sentence boundaries, or periods within email addresses, which certainly aren&#039;t sentence endings.</p>

<p>The challenge lies in finding sentence segmentation libraries that can handle these complexities across a wide range of languages while addressing language-specific intricacies. Most existing libraries fall short in this regard. They tend to focus on English or support only a limited number of languages. Furthermore, they may not be adequately performant or actively maintained.</p>

<p>Here at the language team, we needed a robust sentence segmentation library for our <a href="https://www.mediawiki.org/wiki/MinT" class="remarkup-link remarkup-link-ext" rel="noreferrer">machine translation system</a> and our <a href="https://www.mediawiki.org/wiki/Content_translation/Section_translation" class="remarkup-link remarkup-link-ext" rel="noreferrer">section translation project</a>. The former is a Python project, while the latter is a Node.js project. After a thorough examination of existing libraries, we decided to create our own – not just one, but two libraries: one in Python and another in JavaScript, both serving the same purpose.</p>

<h3 class="remarkup-header">sentencex</h3>

<p>We are introducing &#039;sentencex&#039; library, available in both Python and JavaScript. This remarkable sentence segmentation library boasts extensive language support while emphasizing speed and practicality. When it comes to the balance between linguistic precision and versatility for various applications, sentencex prioritizes usability.</p>

<p>In situations where ambiguity arises and linguistic insight is needed, our library errs on the side of caution, avoiding unnecessary splits rather than risking incorrect segmentations. Our performance benchmarks reveal that this library not only delivers impressive speed but also excels in evaluation datasets.</p>

<p>The name sentencex stands for <strong>sentence</strong> <strong>ex</strong>traction or sentence✂️</p>

<p>Our overarching goal is to provide support for all languages that have a presence on Wikipedia. Instead of defaulting to English for languages not explicitly defined in the library, we&#039;ve implemented a fallback chain mechanism. This means that the closest language included in the library will be utilized. We&#039;ve defined fallbacks for approximately 244 languages, and we also have abbreviation data available for around 30 languages.</p>

<p><div class="phabricator-remarkup-embed-layout-left"><a href="https://phab.wmfusercontent.org/file/data/4qrkhv3nedwvkriuxidl/PHID-FILE-6pazvdokmusxjso6qhvf/image.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_0"><img src="https://phab.wmfusercontent.org/file/data/4qrkhv3nedwvkriuxidl/PHID-FILE-6pazvdokmusxjso6qhvf/image.png" height="938" width="1304" loading="lazy" alt="image.png (938×1 px, 509 KB)" /></a></div></p>

<h3 class="remarkup-header">sentencex python library</h3>

<p>Souce code: <a href="https://github.com/wikimedia/sentencex" class="remarkup-link remarkup-link-ext" rel="noreferrer">github repository</a>. Please refer the documentation for usage examples<br />
Python package:  <a href="https://pypi.org/project/sentencex" class="remarkup-link remarkup-link-ext" rel="noreferrer">sentencex</a><br />
Demo: <a href="https://wikimedia.github.io/sentencex/" class="remarkup-link remarkup-link-ext" rel="noreferrer">https://wikimedia.github.io/sentencex/</a></p>

<h3 class="remarkup-header">sentencex js library</h3>

<p>Souce code: <a href="https://github.com/wikimedia/sentencex-js" class="remarkup-link remarkup-link-ext" rel="noreferrer">github repository</a>. Please refer the documentation for usage examples<br />
NPM package:  <a href="https://www.npmjs.com/package/sentencex" class="remarkup-link remarkup-link-ext" rel="noreferrer">sentencex</a><br />
Demo: <a href="https://wikimedia.github.io/sentencex-js" class="remarkup-link remarkup-link-ext" rel="noreferrer">https://wikimedia.github.io/sentencex-js</a></p>

<p><div class="phabricator-remarkup-embed-layout-left"><a href="https://phab.wmfusercontent.org/file/data/ggzont2uquidqurm5lfx/PHID-FILE-rdr7eoked4qxui2lyrk6/image.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_1"><img src="https://phab.wmfusercontent.org/file/data/ggzont2uquidqurm5lfx/PHID-FILE-rdr7eoked4qxui2lyrk6/image.png" height="938" width="1304" loading="lazy" alt="image.png (938×1 px, 1008 KB)" /></a></div></p>

<p>This library is already in use in <a href="https://www.mediawiki.org/wiki/MinT" class="remarkup-link remarkup-link-ext" rel="noreferrer">MinT</a> project. It is also replacing the minimal sentence segmentation libray we had in <a href="/tag/cx-cxserver/" class="phui-tag-view phui-tag-type-shade phui-tag-disabled phui-tag-shade phui-tag-icon-view " data-sigil="hovercard" data-meta="0_3"><span class="phui-tag-core "><span class="visual-only phui-icon-view phui-font-fa fa-briefcase" data-meta="0_2" aria-hidden="true"></span>CX-cxserver</span></a>  project.  As we start using it in more project, we hope to support more languages and existing languages better.</p></div></content></entry><entry><title>Section translation migrated to Vue 3</title><link href="/phame/live/22/post/282/section_translation_migrated_to_vue_3/" /><id>https://phabricator.wikimedia.org/phame/post/view/282/</id><author><name>santhosh (Santhosh Thottingal)</name></author><published>2022-04-26T05:55:21+00:00</published><updated>2022-04-26T22:58:41+00:00</updated><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p><a href="https://www.mediawiki.org/wiki/Content_translation/Section_translation" class="remarkup-link remarkup-link-ext" rel="noreferrer">Section translation</a> is a mobile first adaptation of ContentTranslation tool. It helps editors to translate sections from a source article to its corresponding article in another language using easy to use UI in a mobile interface. Translating content involves many steps such as choosing the right article, languages, sections to translate, cross checking with the existing article, selecting sentence, translating with the help of Machine translation,  editing it and finally publishing. Designing and building such a complex workflow in the small mobile screen is a very challenging project.</p>

<p>This project started about the same time Wikimedia Foundation chose Vue as its frontend library. So the language team decided to build this application as a Vue SPA delivered through a MediaWiki SpecialPage. This was also one of the early Vue applications that used a build step rather than delivering Vue compiler to the browser. Vue 2 was the major Vue version at that time. We used Vue-CLI for building, Vuex for state management, Vue-Router for routing, <a href="https://github.com/santhoshtr/vue-banana-i18n" class="remarkup-link remarkup-link-ext" rel="noreferrer">Vue-banana-i18n</a> for i18n. There was no Vue UI library available at that time, so we build UI components based on our demands and adhering to <a href="https://design.wikimedia.org/style-guide/components/links.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">WMF design guidelines</a>.  We had a Hot Module Reloading feature by using webpack HMR too. This overall architecture helped us to build the application relatively faster and with a better developer experience.</p>

<p><a href="https://phabricator.wikimedia.org/T299622" class="remarkup-link" rel="noreferrer">Section translation was migrated to Vue 3</a> along with other library upgrades recently and is now in production. In this blog post we are documenting the process we followed.</p>

<p><video controls="controls" preload="auto" class="phabricator-media"><source src="https://phab.wmfusercontent.org/file/data/lva3umz7wq5nri6uraxo/PHID-FILE-e26lwggvyimcjapfeyd5/SX-Vue3-2.webm" /></video></p>

<p>We had these objectives:</p>

<ul class="remarkup-list">
<li class="remarkup-list-item">Upgrade Vue to 3.x</li>
<li class="remarkup-list-item">Upgrade Vuex to 4.x</li>
<li class="remarkup-list-item">Upgrade Vue router to 4.x</li>
<li class="remarkup-list-item">Upgrade Vue banana i18n to Vue 3 version</li>
<li class="remarkup-list-item">Explore replacement of Vue-cli/webpack with Vite</li>
<li class="remarkup-list-item">Integrate HMR with the new tooling</li>
</ul>



<h3 class="remarkup-header">Vite replaces Vue-CLI</h3>

<p>We <a href="https://gerrit.wikimedia.org/r/767618" class="remarkup-link remarkup-link-ext" rel="noreferrer">started</a> with replacing Vue-CLI with Vite without upgrading Vue. This was easy, but our jest based unit tests did not work well with Vite. So it was disabled temporary till we replace Vue 2 in Vue 3 in later step. Vite requires an explicit <tt class="remarkup-monospaced">.vue</tt> file extension for Vue files, but for now, instead of fixing it, we  used <tt class="remarkup-monospaced">resolve.extension</tt> configuration in vite.config.js. We were also using <tt class="remarkup-monospaced">@</tt> alias for top level <tt class="remarkup-monospaced">src</tt> directory for avoiding relative file imports. Both of these were supported in Vite with the following configuration</p>

<div class="remarkup-code-block" data-code-lang="js" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="nx">resolve</span><span class="o">:</span> <span class="p">{</span>
  <span class="nx">alias</span><span class="o">:</span> <span class="p">[</span>
    <span class="p">{</span>
      <span class="nx">find</span><span class="o">:</span> <span class="s2">&quot;@&quot;</span><span class="p">,</span>
      <span class="nx">replacement</span><span class="o">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="s2">&quot;src&quot;</span><span class="p">)</span>
    <span class="p">},</span>
    <span class="p">{</span>
      <span class="nx">find</span><span class="o">:</span> <span class="sr">/~(.+)/</span><span class="p">,</span>
      <span class="nx">replacement</span><span class="o">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">cwd</span><span class="p">(),</span> <span class="s2">&quot;node_modules/$1&quot;</span><span class="p">)</span>
    <span class="p">}</span>
  <span class="p">],</span>
  <span class="c1">// FIXME: Avoid this configuration and change files to use .vue extension.</span>
  <span class="nx">extensions</span><span class="o">:</span> <span class="p">[</span><span class="s2">&quot;.mjs&quot;</span><span class="p">,</span> <span class="s2">&quot;.js&quot;</span><span class="p">,</span> <span class="s2">&quot;.ts&quot;</span><span class="p">,</span> <span class="s2">&quot;.jsx&quot;</span><span class="p">,</span> <span class="s2">&quot;.tsx&quot;</span><span class="p">,</span> <span class="s2">&quot;.json&quot;</span><span class="p">,</span> <span class="s2">&quot;.vue&quot;</span><span class="p">]</span>
<span class="p">},</span></pre></div>

<p>It is interesting to notice, that this migration to Vite, decreased the build time by a factor of 10.</p>

<h3 class="remarkup-header">Getting Hot Module Reloading working</h3>

<p>Since we are going to change lot of source code in the migration it was important for us to retain the Hot Module Reloading very early to save developer time. Vite Dev server runs at <a href="http://localhost:3000" class="remarkup-link remarkup-link-ext" rel="noreferrer">http://localhost:3000</a> and uses Websockets for HMR. Wiring this HMR through ResourceLoader to get it working in a MediaWiki Special page required some exploration, but we got it working.  We introduced a configuration to indicate whether in development mode or production mode. In development mode, the main Resource loader module need to be swapped with <a href="http://localhost:3000/src/main.js" class="remarkup-link remarkup-link-ext" rel="noreferrer">http://localhost:3000/src/main.js</a>. This is done by a ResourceLoader packagefile callback function. But since <a href="http://localhost:3000/src/main.js" class="remarkup-link remarkup-link-ext" rel="noreferrer">http://localhost:3000/src/main.js</a> is an ES module, it cannot be loaded using Resource loader. To avoid this issue, we create a simple js file that does import call like this:</p>

<div class="remarkup-code-block" data-code-lang="js" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="c1">// Doing imports like this overcomes the limitation of resource loader about ES Module imports</span>
<span class="kr">import</span><span class="p">(</span><span class="s2">&quot;http://localhost:3000/src/main.js&quot;</span><span class="p">).</span><span class="k">catch</span><span class="p">((</span><span class="nx">err</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span>
    <span class="s2">&quot;Dev server connection failed. Please check if vite dev server is running.&quot;</span>
  <span class="p">);</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
<span class="p">});</span></pre></div>

<p>This <tt class="remarkup-monospaced">main.dev.js</tt> is used instead of  <a href="http://localhost:3000/src/main.js" class="remarkup-link remarkup-link-ext" rel="noreferrer">http://localhost:3000/src/main.js</a> in packagefile callback:</p>

<div class="remarkup-code-block" data-code-lang="json" data-sigil="remarkup-code-block"><pre class="remarkup-code">packageFiles<span class="s">&quot;: [</span>
<span class="s">				{</span>
<span class="s">					&quot;</span>name<span class="s">&quot;: &quot;</span>dist/cx<span class="mf">3</span>.es.js<span class="s">&quot;,</span>
<span class="s">					&quot;</span>callback<span class="s">&quot;: &quot;</span>ContentTranslation\\Hooks<span class="o">::</span>devModeCallback<span class="s">&quot;,</span>
<span class="s">					&quot;</span>callbackParam<span class="s">&quot;: [</span>
<span class="s">						&quot;</span>dist/cx<span class="mf">3</span>.es.js<span class="s">&quot;,</span>
<span class="s">						&quot;</span>src/main.dev.js<span class="s">&quot;</span>
<span class="s">					]</span>
<span class="s">				}</span>
<span class="s">			],</span></pre></div>

<p>This is the packagefile callback:</p>

<div class="remarkup-code-block" data-code-lang="php" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="no">devModeCallback</span><span class="o">(</span> <span class="no">ResourceLoaderContext</span> <span class="nv">$context</span><span class="o">,</span> <span class="no">Config</span> <span class="nv">$config</span><span class="o">,</span> <span class="k">array</span> <span class="nv">$paths</span> <span class="o">)</span> <span class="o">{</span>
		<span class="k">list</span><span class="o">(</span> <span class="nv">$buildPath</span><span class="o">,</span> <span class="nv">$devPath</span> <span class="o">)</span> <span class="o">=</span> <span class="nv">$paths</span><span class="o">;</span>
		<span class="nv">$file</span> <span class="o">=</span> <span class="nv">$buildPath</span><span class="o">;</span>
		<span class="k">if</span> <span class="o">(</span> <span class="nv">$config</span><span class="o">-&gt;</span><span class="na" data-symbol-name="get">get</span><span class="o">(</span> <span class="s1">&#039;ContentTranslationDevMode&#039;</span> <span class="o">)</span> <span class="o">)</span> <span class="o">{</span>
			<span class="nv">$file</span> <span class="o">=</span> <span class="nv">$devPath</span><span class="o">;</span>
		<span class="o">}</span>
		<span class="k">return</span> <span class="k">new</span> <span class="nc" data-symbol-name="ResourceLoaderFilePath">ResourceLoaderFilePath</span><span class="o">(</span> <span class="nv">$file</span> <span class="o">);</span>
	<span class="o">}</span></pre></div>



<h3 class="remarkup-header">Vue compat build</h3>

<p>Even before our plan to migrate to Vue3, we had started to use Vue composition API (it&#039;s port to Vue2) to manage the complexity of the application. That helped us in the migration, but still there are so many things to fix to get everything work with Vue 3. So <a href="https://gerrit.wikimedia.org/r/c/mediawiki/extensions/ContentTranslation/+/771644/1" class="remarkup-link remarkup-link-ext" rel="noreferrer">we replaced Vue 2 with Vue 3 compatibility build</a> as a temporary way to get everything working, but allowing us to do iterative migration. Note that we have not merged any code yet, we were doing everything in unmerged commit chains.</p>

<h3 class="remarkup-header">Vuex 4</h3>

<p>As part of this migration, we also needed to <a href="https://vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">use the Vuex 4 version</a> which is compatible with Vue 3. Luckily, almost all Vuex APIs have remained unchanged from Vuex 3 and the migration process was very smooth. Additionally, Vuex 4 comes with a nice new feature that is very handy for us: the <tt class="remarkup-monospaced">useStore</tt> composition function that can be used retrieve the store within the component <a href="https://vuejs.org/api/composition-api-setup.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">setup</a> hook.</p>

<h3 class="remarkup-header">Vue Router 4</h3>

<p>Similarly to Vuex, we upgraded <a href="https://router.vuejs.org/guide/migration/" class="remarkup-link remarkup-link-ext" rel="noreferrer">Vue Router to version 4</a>, so that is compatible with Vue 3. Once again, most Vue Router APIs remained unchanged for this upgrade and there were only some trivial modifications needed to complete the upgrade for this package. Finally, Vue Router 4 also provides its own <tt class="remarkup-monospaced">useRouter</tt> and <tt class="remarkup-monospaced">useRoute</tt> composition functions to access the application router and the current route respectively. These functions are very useful for us, given that we base most of our Vue components on Vue 3 Composition API.</p>

<h3 class="remarkup-header">i18n</h3>

<p>When it comes to internationalization, Section Translation relies on <a href="https://github.com/santhoshtr/vue-banana-i18n" class="remarkup-link remarkup-link-ext" rel="noreferrer">Vue Banana i18n</a> library. This library was created by <a href="https://phabricator.wikimedia.org/p/santhosh/" class="phui-tag-view phui-tag-type-person " data-sigil="hovercard" data-meta="0_4"><span class="phui-tag-core phui-tag-color-person">@santhosh</span></a>  and is actively maintained by <a href="https://phabricator.wikimedia.org/p/santhosh/" class="phui-tag-view phui-tag-type-person " data-sigil="hovercard" data-meta="0_5"><span class="phui-tag-core phui-tag-color-person">@santhosh</span></a> himself and the Language team. It is basically a Vue wrapper for <a href="https://github.com/wikimedia/banana-i18n" class="remarkup-link remarkup-link-ext" rel="noreferrer">Banana i18n</a> library. That means that this library supports <a href="https://www.mediawiki.org/wiki/Localisation" class="remarkup-link remarkup-link-ext" rel="noreferrer">Mediawiki Internationalization</a> message system, while at the same time it provides out-of-the-box template directives (<tt class="remarkup-monospaced">v-i18n</tt>, <tt class="remarkup-monospaced">v-i18n-html</tt>) and a composition function (<tt class="remarkup-monospaced">useI18n</tt>) to retrieve the bananaI18n utility object. In this case, we had to upgrade the vue-banana-i18n package from version 1.x (which supports Vue 2) to version 2.x that provides Vue 3 support.</p>

<h6 class="remarkup-header">Upgrade went fine but code was broken</h6>

<p>Although we upgraded the library, we noticed that the <tt class="remarkup-monospaced">useI18n</tt> composition function didn&#039;t work as expected and the code was broken in all the places where we retrieved the <tt class="remarkup-monospaced">bananaI18n</tt> object, even though we only used it inside composition <tt class="remarkup-monospaced">setup</tt> hook. After some research we realized that the reason behind this weird behaviour was that Vue Banana i18n library bundled its own version of Vue in its dist files, and it didn&#039;t externalize the Vue dependency. Because of that, the bananaI18n object relied on a different Vue instance leading to this unexpected behaviour. In order to fix this issue, we updated <a href="https://github.com/santhoshtr/vue-banana-i18n/commit/a5a8fdfe898003bb5c03cb8bf45f60c8bac511ed" class="remarkup-link remarkup-link-ext" rel="noreferrer">the vite configuration inside the library</a> to avoid bundling Vue as a dependency, and the issue got fixed.</p>

<h3 class="remarkup-header">CSS logical properties</h3>

<p>The Right to left support in Mediawiki applications is usually done using CSS flipping using CSSJanus. This is happened during the ResourceLoader module request with a given language and direction. Since our development setup does not include ResourceLoader at all we were not using CSSJanus. The section translation application is written using CSS logical properties to support script directionality as our browser support criteria enables it. In production, to bypass CSS flip by ResourceLoader, we use <tt class="remarkup-monospaced">noflip: true</tt> option in the module definition in extension.json.</p>

<h3 class="remarkup-header">Addressing breaking changes in Vue 3</h3>

<h6 class="remarkup-header">Plugins</h6>

<p>One of most important changes in Vue 3 is <a href="https://vuejs.org/guide/reusability/plugins.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">the way that application-level functionalities are added</a> to Vue. In Vue 2, application-level functionalities were added by <a href="https://v2.vuejs.org/v2/guide/plugins.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">using plugins</a> to register <a href="https://v2.vuejs.org/v2/guide/mixins" class="remarkup-link remarkup-link-ext" rel="noreferrer">mixins</a> or Vue instance methods. However, Vue 3 brings a new feature that serves as a dependency injection mechanism, called <a href="https://vuejs.org/guide/components/provide-inject.html#provide-inject" class="remarkup-link remarkup-link-ext" rel="noreferrer">&quot;provide/inject&quot;</a>. This feature is also a standard way to provide application-level functionality by making a resource injectable inside Vue plugins.</p>

<p>For this reason, we had to refactor our application plugins, to remove the usage of mixins and use this new &quot;provide/inject&quot; pattern. An important property of the &quot;provide/inject&quot; feature is that &quot;inject&quot; is not available outside the <tt class="remarkup-monospaced">setup</tt> hook of the Vue Composition API. For this reason, we had also to make sure that there were no invalid assumptions about the availability of the injected resources inside composables that could be executed outside <tt class="remarkup-monospaced">setup</tt> hooks.</p>

<h6 class="remarkup-header">v-model usage</h6>

<p>Another breaking change we had to deal with was <a href="https://v3-migration.vuejs.org/breaking-changes/v-model.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">the reworked usage of v-model directive</a> inside templates and the replacement of <tt class="remarkup-monospaced">v-bind.sync</tt>. In order to limit the changes for this migration as much as possible, we decided to use the <tt class="remarkup-monospaced">v-model:property</tt> syntax both for replacing the occurrences of  <tt class="remarkup-monospaced">v-bind.sync</tt> directives and old Vue 2 <tt class="remarkup-monospaced">v-model</tt> syntax. Restoring the usage of <tt class="remarkup-monospaced">v-model</tt> in its simplest form would require renaming the props from &quot;value&quot; to &quot;modelValue&quot; and replacing the <tt class="remarkup-monospaced">@input</tt> events with <tt class="remarkup-monospaced">update:modelValue</tt> events. Since this is not essential for our application to work properly, we deferred these changes for later, when we would have completed the Vue 3 migration.</p>

<h6 class="remarkup-header">Vue.set</h6>

<p>A very interesting change in Vue 3 is that <a href="https://v3-migration.vuejs.org/breaking-changes/global-api-treeshaking.html#affected-apis" class="remarkup-link remarkup-link-ext" rel="noreferrer">Vue.set global function and the &quot;$set&quot; instance method has been removed</a>. This became possible because Vue 3 leverages <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" class="remarkup-link remarkup-link-ext" rel="noreferrer">Javascript Proxies</a> for reactivity (unlike Vue 2 that only used getters/setters). So, in Vue 3 we could (and should) get rid of all <tt class="remarkup-monospaced">Vue.set</tt> occurrences and use regular JS assignments instead.</p>

<h6 class="remarkup-header">Transitions</h6>

<p>In Vue 3 <a href="https://v3-migration.vuejs.org/breaking-changes/transition.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">some transition classes has been changed</a> and <tt class="remarkup-monospaced">&lt;transition&gt;</tt> elements can only have one single child. Hence, we also had to fix our animation CSS classes and the related issues inside Vue templates.</p>

<h6 class="remarkup-header">Template refs, nextTick and slots</h6>

<p>Finally, Vue 3 introduces some new syntax for several features. Some of them were affecting Section Translation application, so we had to use the updated syntax for template references, <a href="https://v3-migration.vuejs.org/breaking-changes/global-api-treeshaking.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">replace the &quot;nextTick&quot; instance method</a> that was used for Vue 2 (using <tt class="remarkup-monospaced">this.$nextTick</tt>) with the new <tt class="remarkup-monospaced">nextTick</tt> method from the Global Vue API and also replace the deprecated <tt class="remarkup-monospaced">slot=&quot;slotName&quot;</tt> syntax with the new <tt class="remarkup-monospaced">#slotName</tt> syntax. All these changes were easy to find out and fix, given the warnings coming from the Vue 3 compatibility build that we were using during this migration.</p>

<h6 class="remarkup-header">Replace Vue Compat Build with Vue 3</h6>

<p>Once we fixed all above issues for this migration, we had reached a point where we were actually blocked by the Vue Compatibility build. Although, most issues have indeed been fixed by then, there were some other (<tt class="remarkup-monospaced">v-on.native</tt> modifier, <tt class="remarkup-monospaced">$listeners</tt> object) that were not supported by the compatibility build, either in the old Vue 2 syntax or the new Vue 3 syntax. This led us to remove the compatibility build and switch to Vue 3 entirely. Given the fact that we didn&#039;t care about merging the changes piece-by-piece, it was perfectly ok for us to make this change, even if the application still wasn&#039;t working properly on Vue 3. However, the switch to Vue 3 enabled us to resolve the remaining issues, that are listed next.</p>

<h6 class="remarkup-header">$listeners</h6>

<p><a href="https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">The $listeners object has been removed in Vue 3</a> and event listeners are now part of the <tt class="remarkup-monospaced">$attrs</tt> object. For this reason, we had to remove all the component event propagation with <tt class="remarkup-monospaced">v-on=&quot;$listeners&quot;</tt> directive and either use the <tt class="remarkup-monospaced">v-bind=&quot;$attrs&quot;</tt> directive or directly emit the events that should be passed to parent components.</p>

<h6 class="remarkup-header"><tt class="remarkup-monospaced">emits</tt> option and native click handlers</h6>

<p><a href="https://v3-migration.vuejs.org/breaking-changes/emits-option.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">Vue 3 introduces a new &quot;emits&quot; component-level option</a>, similar to the existing <tt class="remarkup-monospaced">props</tt> option. This option is used to define the events that a component can emit to its parent. At the same time, <a href="https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed.html" class="remarkup-link remarkup-link-ext" rel="noreferrer">the &quot;v-on.native&quot; modifier has been removed in Vue 3</a> and Vue will now add all event listeners that are not defined as component-emitted events in the child, as native event listeners to the child&#039;s root element. Consequently, we had to explicitly add any custom component-emitted event inside the <tt class="remarkup-monospaced">emits</tt> option of each component and remove the <tt class="remarkup-monospaced">.native</tt> modifier. This went quite smoothly, since it&#039;s a straightforward change.</p>

<h3 class="remarkup-header">Unit testing - Jest migration</h3>

<p>Vue-CLI had wrapper for unit testing with Jest. With the Vite migration, and Vue 3 upgrade we used the <a href="https://github.com/vuejs/vue-jest" class="remarkup-link remarkup-link-ext" rel="noreferrer">@vue/vue3-jest</a> package. The underlying Vue-testutils package was also upgraded to 2.x versions. The migration is <a href="https://test-utils.vuejs.org/migration/" class="remarkup-link remarkup-link-ext" rel="noreferrer">documented</a> well in Vue test utils website. <a href="https://gerrit.wikimedia.org/r/c/mediawiki/extensions/ContentTranslation/+/772393" class="remarkup-link remarkup-link-ext" rel="noreferrer">We just followed it</a> and the migration was smooth, but the snapshots generated by this version had some alignment and such minor changes. We had to update the snapshots and make sure no importance changes happening there.</p>

<h3 class="remarkup-header">Merging</h3>

<p>We had a 29 commits long chain of patches. <a href="https://phabricator.wikimedia.org/p/santhosh/" class="phui-tag-view phui-tag-type-person " data-sigil="hovercard" data-meta="0_6"><span class="phui-tag-core phui-tag-color-person">@santhosh</span></a> and <a href="https://phabricator.wikimedia.org/p/ngkountas/" class="phui-tag-view phui-tag-type-person " data-sigil="hovercard" data-meta="0_7"><span class="phui-tag-core phui-tag-color-person">@ngkountas</span></a>  were reviewing patches each other and improving them as required. Only the last one in the chain passes CI tests. How to merge them? We squashed all of them in to <a href="https://gerrit.wikimedia.org/r/c/mediawiki/extensions/ContentTranslation/+/773385" class="remarkup-link remarkup-link-ext" rel="noreferrer">a single large commit</a>. And abandoned all the other commits in gerrit.</p>

<h3 class="remarkup-header">Everything went well..wait..</h3>

<p>We noticed that certain part of code does not work in production mode while working fine in dev mode. Strangely adding some console statements make it working again. We got puzzled by this behaviour for a while, but looking at the minified code that was supplied by RL gave a clue. RL was minifying the vite generated bundle and was failing with some of the ES features like async. <a href="https://phabricator.wikimedia.org/T277675" class="remarkup-link" rel="noreferrer">Resource Loader minification yet to support them</a></p>

<p>We had this in place for Vue2, but somehow missed to retain in the migration process. We quickly fixed it as follows in vite configuration:</p>

<div class="remarkup-code-block" data-code-lang="js" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="nx">esbuild</span><span class="o">:</span> <span class="p">{</span>
    <span class="c1">// Avoid ResourceLoader minification</span>
    <span class="nx">banner</span><span class="o">:</span> <span class="s2">&quot;/*@nomin*/&quot;</span><span class="p">,</span>
  <span class="p">},</span></pre></div>

<p><tt class="remarkup-monospaced">/*@nomin*/</tt> is a special indicator for Resource Loader. Having that at the top of the file tells it to skip minification.</p>

<h3 class="remarkup-header">Left overs</h3>

<p>We have been using storybook for the development and demo of UI components. Storybook comes with a long list of dependencies, including react. Migrating that to vite based tooling is currently difficult. The upstream is still working on such tooling integration. For now we just disabled storybook building as we are not modifying the UI components much and probably going to use Codex once it is ready.</p>

<p>We also had an experimental set up for integration test with Cypress and its Vue binding. We disabled that as well since we were not writing any actual integration tests so far.</p>

<h3 class="remarkup-header">Testing</h3>

<p>The testing was done by language team members in our test instance in wmflabs. Our team did not have a QA person to do detailed testing.</p>

<h3 class="remarkup-header">Conclusion</h3>

<p>We finished the whole migration in 3 weeks time. We had a couple of <a href="https://phabricator.wikimedia.org/T299622#7806239" class="remarkup-link" rel="noreferrer">minor visual regressions</a> but nothing that impacts the application usage. The developer experience is now much improved, thanks to unbelievably fast HMR using Vite devserver. The build time is also in the range of seconds , thanks to esbuild based building. Removing vue-cli and storybook related dependencies reduced the total space taken by npm modules from 0.5GB to 128MB.</p>

<blockquote><p>This blog post is authored by <a href="https://phabricator.wikimedia.org/p/ngkountas/" class="phui-tag-view phui-tag-type-person " data-sigil="hovercard" data-meta="0_8"><span class="phui-tag-core phui-tag-color-person">@ngkountas</span></a> and <a href="https://phabricator.wikimedia.org/p/santhosh/" class="phui-tag-view phui-tag-type-person " data-sigil="hovercard" data-meta="0_9"><span class="phui-tag-core phui-tag-color-person">@santhosh</span></a></p></blockquote></div></content></entry></feed>