{"id":109686,"date":"2022-11-04T03:51:00","date_gmt":"2022-11-04T02:51:00","guid":{"rendered":"https:\/\/phrase.com\/blog\/posts\/ein-umfassender-leitfaden-zur-vue-lokalisierung\/"},"modified":"2025-09-18T15:29:56","modified_gmt":"2025-09-18T13:29:56","slug":"ultimate-guide-to-vue-localization-with-vue-i18n","status":"publish","type":"post","link":"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/","title":{"rendered":"Ein umfassender Leitfaden zur Lokalisierung mit Vue"},"content":{"rendered":"<p>Unter den gro\u00dfen drei UI-Frameworks ist Evan You\u2019s <a href=\"https:\/\/vuejs.org\/\">Vue<\/a> wahrscheinlich das zug\u00e4nglichste und scheint ein unwahrscheinlicher Konkurrent gegen\u00fcber den Giganten wie Meta\u2019s React und Google\u2019s Angular zu sein. Doch diese Idee eines Einzelnen hat eine <a href=\"https:\/\/npmtrends.com\/@angular\/core-vs-react-vs-vue\">Verbreitung, die mit der von Angular mithalten kann<\/a>, dank sanfter Lernkurve, erstklassiger Entwicklererfahrung und produktionsbereiter Funktionen.<\/p>\n<p>Dank seiner Beliebtheit hat Vue ein reichhaltiges \u00d6kosystem an Plugins, Erweiterungen und Diensten hervorgebracht. Die Internationalisierung von Vue-Apps (i18n) \u2013 vermutlich der Grund, warum du das hier liest \u2013 sieht das robuste <a href=\"https:\/\/vue-i18n.intlify.dev\/\">Vue I18n<\/a> Plugin von Drittanbietern als die <a href=\"https:\/\/npmtrends.com\/vue-i18n-vs-vue-i18next-vs-vuex-i18n\">offensichtliche erste Wahl<\/a>. In diesem praktischen Leitfaden wird Vue I18n verwendet, um eine kleine Demo-App zu internationalisieren, und es wird alles abgedeckt, was ben\u00f6tigt wird, um mit der Vue-Lokalisierung zu beginnen. Los geht&#8217;s.<\/p>\n<p>\u270b <em>Achtung \u00bb<\/em> Dieser Artikel behandelt die Lokalisierung von Vue 3. Bei Interesse an Vue 2 lohnt sich ein Blick auf <a href=\"https:\/\/phrase.com\/blog\/posts\/vue-2-localization\/\">Vue 2 Lokalisierung mit Vue I18n: Eine Schritt-f\u00fcr-Schritt-Anleitung<\/a>.<\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb <\/em>In diesem Artikel wird die Vue I18n Bibliothek verwendet. Falls lieber i18next verwendet werden soll, k\u00f6nnte unser <a href=\"https:\/\/phrase.com\/blog\/posts\/vue-translation-with-vue-i18next\/\">Deep Dive: Die \u00dcbersetzung mit vue-i18next<\/a> n\u00fctzlich sein.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_81 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Overview<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#verwendete-bibliotheksversionen\" >Verwendete Bibliotheksversionen<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#unsere-demo\" >Unsere Demo<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#attributionen\" >Attributionen<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#wie-wird-vue-i18n-installiert-und-eingerichtet\" >Wie wird Vue I18n installiert und eingerichtet?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#nachrichten-in-komponenten-uebersetzen-%e2%80%93-so-gehts\" >Nachrichten in Komponenten \u00fcbersetzen \u2013 so geht&#8217;s<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#so-funktioniert-der-umgang-mit-dynamischen-werten-in-uebersetzungsnachrichten\" >So funktioniert der Umgang mit dynamischen Werten in \u00dcbersetzungsnachrichten<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#so-wirds-gemacht-strings-in-component-javascript-uebersetzen\" >So wird&#8217;s gemacht: Strings in Component-JavaScript \u00fcbersetzen<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#arbeiten-mit-html-in-uebersetzungsnachrichten-%e2%80%93-so-gehts\" >Arbeiten mit HTML in \u00dcbersetzungsnachrichten \u2013 so geht&#8217;s<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#wie-wird-mit-pluralen-in-uebersetzungen-umgegangen\" >Wie wird mit Pluralen in \u00dcbersetzungen umgegangen?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#lokalisierte-zahlen-formatieren-%e2%80%93-so-gehts\" >Lokalisierte Zahlen formatieren \u2013 so geht&#8217;s<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#formatierung-von-lokalisierten-datums-und-zeitangaben-%e2%80%93-so-gehts\" >Formatierung von lokalisierten Datums- und Zeitangaben \u2013 so geht\u2019s<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#wie-kann-die-aktive-spracheinstellung-abgerufen-werden\" >Wie kann die aktive Spracheinstellung abgerufen werden?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#umgestaltung-der-i18n-bibliothek\" >Umgestaltung der i18n Bibliothek<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#wie-kann-die-lokalisierung-von-routen-erfolgen\" >Wie kann die Lokalisierung von Routen erfolgen?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#so-erstellt-man-eine-wiederverwendbare-lokalisierte-link-komponente\" >So erstellt man eine wiederverwendbare lokalisierte Link-Komponente.<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#eine-sprachumschalter-oberflaeche-erstellen-%e2%80%93-so-gehts\" >Eine Sprachumschalter-Oberfl\u00e4che erstellen \u2013 so geht&#8217;s<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#direkt-an-i18nlocale-anbinden\" >Direkt an i18n.locale anbinden<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#uebersetzungsdateien-asynchron-laden-%e2%80%93-so-gehts\" >\u00dcbersetzungsdateien asynchron laden \u2013 so geht&#8217;s<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#arbeiten-mit-spracheinstellungs-fallbacks-%e2%80%93-so-gehts\" >Arbeiten mit Spracheinstellungs-Fallbacks \u2013 so geht&#8217;s<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#eine-vue-app-mit-der-composition-api-lokalisieren-%e2%80%93-so-gehts\" >Eine Vue-App mit der Composition API lokalisieren \u2013 so geht\u2019s<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/phrase.com\/de\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/#refactoring-von-i18n-unter-verwendung-der-composition-api\" >Refactoring von i18n unter Verwendung der Composition API<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"verwendete-bibliotheksversionen\"><\/span>Verwendete Bibliotheksversionen<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>In diesem Artikel wurden die folgenden NPM-Pakete verwendet (Versionen in Klammern).<\/p>\n<ul>\n<li>Vue (3.2) \u2013 das UI-Framework<\/li>\n<li>Vue Router (4.1) \u2013 der offizielle Router f\u00fcr Vue-SPAs<\/li>\n<li>Vue I18n (9.2) \u2013 die Drittanbieter-Bibliothek f\u00fcr Internationalisierung von Vue<\/li>\n<li>Tailwind CSS (3.1) \u2013 f\u00fcrs Styling verwendet und optional f\u00fcr unsere Zwecke<\/li>\n<\/ul>\n<p>\ud83d\uddd2 <em>Hinweis \u00bb<\/em> Um den Fokus auf i18n zu legen, wird in diesem Artikel kein CSS-Styling gezeigt. Alle Styling-Codes sind im <a href=\"https:\/\/github.com\/PhraseApp-Blog\/vue3-i18n-2022\">vollst\u00e4ndigen Code unseres Artikels<\/a> auf GitHub zu finden.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"unsere-demo\"><\/span>Unsere Demo<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Unsere bescheidene Demo, <em>Mushahed<\/em>, basiert auf Daten der <a href=\"http:\/\/open-notify.org\/\">Open Notify<\/a> Space-APIs.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-35546\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/before-i18n.jpg\" alt=\"In der Demo-App werden die mutigen Astronauten weltweit gefeiert\" width=\"1170\" height=\"1238\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/before-i18n.jpg 1170w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/before-i18n-284x300.jpg 284w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/before-i18n-968x1024.jpg 968w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/before-i18n-768x813.jpg 768w\" sizes=\"auto, (max-width: 1170px) 100vw, 1170px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">In der Demo-App werden die mutigen Astronauten der Welt gefeiert<\/span><\/p>\n<h3><span class=\"ez-toc-section\" id=\"attributionen\"><\/span>Attributionen<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Ein Dankesch\u00f6n geht an die folgenden Personen und Organisationen, die ihre Materialien kostenlos zur Verf\u00fcgung gestellt haben.<\/p>\n<ul>\n<li>F\u00fcr das Demo-Brand-Icon wurde das <a href=\"https:\/\/thenounproject.com\/icon\/satellite-2801958\/\">Satellit<\/a>-Symbol von <a href=\"https:\/\/thenounproject.com\/akritibhusal\/\">Akriti Bhusal<\/a> auf The Noun Project verwendet.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Cai_Xuzhe#\/media\/File:%E8%88%AA%E5%A4%A9%E5%91%98%E8%94%A1%E6%97%AD%E5%93%B2_Cai_Xuzhe.jpg\">Das Foto von Cai Xuzhe<\/a> und <a href=\"https:\/\/en.wikipedia.org\/wiki\/Chen_Dong_(taikonaut)#\/media\/File:%E8%88%AA%E5%A4%A9%E5%91%98%E9%99%88%E5%86%AC_Chen_Dong.jpg\">das Foto von Chen Dong<\/a> sind urheberrechtlich gesch\u00fctzt durch den China News Service und werden unter der <a href=\"https:\/\/creativecommons.org\/licenses\/by\/3.0\/\">CC BY 3.0<\/a>-Lizenz genutzt.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Liu_Yang_(taikonaut)#\/media\/File:Liu_Yang_-_UNOOSA_50_Years_of_Women_in_Space_NHM_Vienna_2013_b.jpg\">Das Foto von Liu Yang<\/a> ist urheberrechtlich gesch\u00fctzt von Manfred Werner (<a href=\"https:\/\/commons.wikimedia.org\/wiki\/User:Tsui\">Tsui<\/a>) und wird unter der <a href=\"https:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/\">CC BY-SA 3.0<\/a>-Lizenz genutzt.<\/li>\n<li>Alle anderen Astronautenfotos sind gemeinfrei.<\/li>\n<\/ul>\n<p>Unsere Demo ist eine Vue SPA, die mit <code>npm init vue@latest<\/code> hochgefahren wurde. Wir haben Router-Unterst\u00fctzung hinzugef\u00fcgt und uns gegen TypeScript entschieden, als das Projekt aufgesetzt wurde. Nachdem die vom Scaffold-Tool hinzugef\u00fcgten Boilerplate-Komponenten entfernt wurden, haben wir diese kleine Hierarchie aufgebaut:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-linenumbers=\"false\">.\n\u2514\u2500\u2500 src\/\n    \u251c\u2500\u2500 components\/\n    \u2502   \u251c\u2500\u2500 AstroCard.vue\n    \u2502   \u251c\u2500\u2500 Astronauts.vue\n    \u2502   \u251c\u2500\u2500 Coords.vue\n    \u2502   \u251c\u2500\u2500 Footer.vue\n    \u2502   \u2514\u2500\u2500 Nav.vue\n    \u251c\u2500\u2500 router\/\n    \u2502   \u2514\u2500\u2500 index.js\n    \u251c\u2500\u2500 views\/\n    \u2502   \u251c\u2500\u2500 HomeView.vue\n    \u2502   \u2514\u2500\u2500 AboutView.vue\n    \u2514\u2500\u2500 App.vue\n\n<\/pre>\n<div><\/div>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-35585\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/components.png\" alt=\"Die einzelnen Komponenten unserer Demo\" width=\"1170\" height=\"720\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/components.png 1170w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/components-300x185.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/components-1024x630.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/components-768x473.png 768w\" sizes=\"auto, (max-width: 1170px) 100vw, 1170px\" \/><\/div>\n<div><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Die Komponenten\u00fcbersicht unserer Demo<\/span><\/div>\n<div><\/div>\n<div><\/div>\n<p>\ud83d\uddd2 <em>Hinweis \u00bb<\/em> In der <code>App<\/code> ist eine Vue <code>&lt;RouterView&gt;<\/code> vorhanden und <code>&lt;RouterLink&gt;<\/code> wird in der Navigation genutzt. Das Routing wird sp\u00e4ter betrachtet.<\/p>\n<p>Werfen wir einen genauere Blick auf die <code>&lt;Astronauts&gt;<\/code>-Komponente geworfen.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-group=\"f46a1c80-3b46-4d90-b3ce-21c32a0f7375\" data-enlighter-title=\"src\/components\/Astronauts.vue\" data-enlighter-linenumbers=\"false\">&lt;script&gt;\nimport AstroCard from '.\/AstroCard.vue'\n\nexport default {\n  components: { AstroCard },\n\n  data() {\n    return {\n      loading: true,\n      astros: [],\n    }\n  },\n\n  created() {\n    fetch('\/data\/astronauts.json')\n      .then((res) =&gt; res.json())\n      .then((data) =&gt; {\n        this.astros = data\n        this.loading = false\n      })\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;!-- No i18n: Fest eingestelltes Englisch --&gt;\n  &lt;p v-if=\"loading\"&gt;Wird geladen...&lt;\/p&gt;\n\n  &lt;div v-else&gt;\n    &lt;div&gt;\n      &lt;h2&gt;\n        &lt;!-- No i18n: Fest codierter Plural --&gt;\n        \ud83e\uddd1\u200d\ud83d\ude80 {{ astros.length }} Personen im All\n      &lt;\/h2&gt;\n\n      &lt;p&gt;\n       &lt;!-- No i18n: Fest codiertes Datum --&gt;\n        Aktualisiert am 26. Juli 2022\n      &lt;\/p&gt;\n    &lt;\/div&gt;\n\n    &lt;div&gt;\n      &lt;AstroCard\n        v-for=\"astro in astros\"\n        :key=\"astro.id\"\n        :name=\"astro.name\"\n        :nationality=\"astro.nationality\"\n        :craft=\"astro.craft\"\n        :photoUrl=\"astro.photoUrl\"\n      \/&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Bei der Erstellung von <code>&lt;Astronauts&gt;<\/code> werden die Astronautendaten aus <code>public\/data\/astronauts.json<\/code> geladen und an Instanzen der <code>&lt;AstroCard&gt;<\/code> weitergegeben.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"a8b510ef-8483-4a34-8566-a9a67505f7f9\" data-enlighter-title=\"public\/data\/astronauts.json\">[\n  \/\/ ...\n\n  {\n    \"id\": 7,\n    \"name\": \"Jessica Watkins\",\n    \"photoUrl\": \"j-watkins.jpg\",\n    \"nationality\": \"USA \ud83c\uddfa\ud83c\uddf8\",\n    \"craft\": \"ISS\"\n  },\n\n  \/\/ ...\n]\n\n<\/pre>\n<div><\/div>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-35670\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/astro-cards.png\" alt=\"Unsere &lt;AstroCard&gt;-Instanzen stellen Astronautendaten dar\" width=\"1170\" height=\"348\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/astro-cards.png 1170w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/astro-cards-300x89.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/astro-cards-1024x305.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/astro-cards-768x228.png 768w\" sizes=\"auto, (max-width: 1170px) 100vw, 1170px\" \/><\/div>\n<div><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Die &lt;AstroCard&gt;-Instanzen rendern Astronautendaten<\/span><\/div>\n<div><\/div>\n<div><\/div>\n<p>Zurzeit sind unsere UI-Strings alle fest in Englisch codiert. K\u00fcmmern wir uns darum und lokalisieren wir die App.<\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Ein Gro\u00dfteil des Demo-Startercodes wird der K\u00fcrze halber weggelassen. Alles kann aus <a href=\"https:\/\/github.com\/PhraseApp-Blog\/vue3-i18n-2022\/tree\/start-options\">dem start-options-Branch unseres GitHub-Repos<\/a> geholt werden.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"wie-wird-vue-i18n-installiert-und-eingerichtet\"><\/span>Wie wird Vue I18n installiert und eingerichtet?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Das wird dich \u00fcberraschen: Zuerst wird eine NPM-Installation in der Kommandozeile aus dem Stammverzeichnis des Vue-Projekts durchgef\u00fchrt.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">$ npm install vue-i18n@9 \n\n<\/pre>\n<p>\ud83d\uddd2 <em>Hinweis \u00bb<\/em> Es wird v9+ von Vue I18n ben\u00f6tigt, wenn mit Vue 3 gearbeitet wird. Vue 2 verwendet Vue i18n v8.<\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Hier sind <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/installation.html\">alle M\u00f6glichkeiten zur Installation von Vue I18n in der offiziellen Dokumentation<\/a> zu finden.<\/p>\n<p>Sobald NPM fertig ist, muss eine Vue I18n-Instanz erstellt, konfiguriert und als Plugin bei der Vue-Instanz registriert werden. Die Vue I18n-Instanz wird in einem neuen Modul konstruiert. Ein Verzeichnis namens <code>src\/i18n<\/code> wird erstellt und eine <code>index.js<\/code>-Datei darin abgelegt.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"8b3f170d-82c6-456a-be67-f069c7e3b6d0\" data-enlighter-title=\"src\/i18n\/index.js\">import { createI18n } from 'vue-i18n'\n\nconst i18n = createI18n({\n  \/\/ Standard-Sprache\n  locale: 'en',\n\n  \/\/ \u00dcbersetzungen\n  messages: {\n    en: {\n      appTitle: 'Mushahed',\n    },\n    ar: {\n      appTitle: '\u0645\u0634\u0627\u0647\u062f',\n    },\n  },\n})\n\nexport default i18n\n\n<\/pre>\n<p>Die \u00dcbersetzung <code>messages<\/code> wird an das i18n-Objekt weitergegeben, das mit <code>createI18n()<\/code> erstellt wird. Die anf\u00e4ngliche Locale, auf die die App beim ersten Laden standardm\u00e4\u00dfig zur\u00fcckgreift, wird \u00fcber die <code>locale<\/code>-Konfigurationsoption festgelegt.<\/p>\n<p>\ud83d\uddd2 <em>Hinweis \u00bb<\/em> In der App werden Englisch (<code>en<\/code>) und Arabisch (<code>ar<\/code>) unterst\u00fctzt. Aber nat\u00fcrlich k\u00f6nnen auch beliebige andere Sprachen gew\u00e4hlt werden. Zur Identifizierung der \u00dcbersetzungs-Lokalisierungen kann ein standardm\u00e4\u00dfiges <a href=\"https:\/\/en.wikipedia.org\/wiki\/Codes_for_constructed_languages\">BCP 47<\/a> Sprach-Tag (wie <code>en<\/code>) oder ein Sprach-Tag mit einem Regionsuntertag (wie <code>en-US<\/code>) verwendet werden.<\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Alle Konfigurationsoptionen f\u00fcr <code>createI18n()<\/code> sind in der <a href=\"https:\/\/vue-i18n.intlify.dev\/api\/general.html#createi18n\">offiziellen API-Dokumentation<\/a> verf\u00fcgbar.<\/p>\n<p>Nun muss das <code>i18n<\/code> Objekt als Plugin mit einem <code>use()<\/code> Aufruf bei der Vue-Instanz registriert werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"41e4b20f-dbec-4421-b11b-8245a1cc9f3d\" data-enlighter-title=\"src\/main.js\" data-enlighter-highlight=\"4,10\">import { createApp } from 'vue'\nimport App from '.\/App.vue'\nimport router from '.\/router'\nimport i18n from '.\/i18n'\n\nimport '.\/assets\/main.css'\n\nconst app = createApp(App)\n\napp.use(i18n)\napp.use(router)\n\napp.mount('#app')\n\n<\/pre>\n<p>Damit w\u00e4re unser Setup komplett. Die i18n wird getestet, indem der App-Titel in der <code>&lt;Nav&gt;<\/code>-Komponente internationalisiert wird.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"f8cab4c7-66b5-4098-a044-1af525ade08d\" data-enlighter-title=\"src\/components\/Nav.vue\" data-enlighter-highlight=\"10\">&lt;script setup&gt;\nimport { RouterLink } from 'vue-router'\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;nav&gt;\n    &lt;img alt=\"Mushahed logo\" src=\"@\/assets\/logo.svg\" \/&gt;\n\n    &lt;!-- Der App-Titel ist fest auf Englisch eingestellt --&gt;\n    &lt;span&gt;Mushahed&lt;\/span&gt;\n\n    &lt;RouterLink to=\"\/\"&gt;Home&lt;\/RouterLink&gt;\n    &lt;RouterLink to=\"\/about\"&gt;\u00dcber&lt;\/RouterLink&gt;\n  &lt;\/nav&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Der festgelegte Text wird durch das Folgende ersetzt.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"b3993f31-5b3c-4784-a7ef-c05a4912e72b\" data-enlighter-title=\"src\/components\/Nav.vue\" data-enlighter-highlight=\"9\">&lt;script setup&gt;\nimport { RouterLink } from 'vue-router'\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;nav&gt;\n    &lt;!-- ... --&gt;\n\n    &lt;span&gt;{{ $t('appTitle') }}&lt;\/span&gt;\n\n    &lt;!-- ... --&gt;\n  &lt;\/nav&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Jetzt ist die \u00dcbersetzungsfunktion <code>$t()<\/code> von Vue I18n f\u00fcr alle Komponenten verf\u00fcgbar. Wenn <code>$t('appTitle')<\/code> aufgerufen wird und die aktive Sprache Englisch (<code>en<\/code>) ist, liefert <code>$t()<\/code> die Nachricht, die wir oben unter <code>messages.en.appTitle<\/code> angegeben haben. Wenn die aktive Sprache Arabisch (<code>ar<\/code>) ist, wird <code>messages.ar.appTitle<\/code> zur\u00fcckgegeben.<\/p>\n<p>\ud83e\udd3f <em>Mehr erfahren \u00bb<\/em> Es gibt unz\u00e4hlige M\u00f6glichkeiten, <code>$t()<\/code> in der <a href=\"https:\/\/vue-i18n.intlify.dev\/api\/injection.html#t-key\">offiziellen API-Liste<\/a> zu nutzen.<\/p>\n<p>Wird die App jetzt neu geladen, sollte keine Ver\u00e4nderung sichtbar sein: Das liegt daran, dass die Anfangssprache auf Englisch eingestellt ist. Es wird auf Arabisch gewechselt.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"0fec982d-5b87-4680-aa27-20007be78617\" data-enlighter-title=\"src\/i18n\/index.js\" data-enlighter-highlight=\"4\">import { createI18n } from 'vue-i18n'\n\nconst i18n = createI18n({\n  locale: 'ar',\n  messages: {\n\ten: {\n      appTitle: 'Mushahed',\n    },\n    ar: {\n      appTitle: '\u0645\u0634\u0627\u0647\u062f',\n    },\n  },\n})\n\nexport default i18n\n\n<\/pre>\n<p>Et voil\u00e0!<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35677\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-app-name.png\" alt=\"Der App-Name auf Arabisch \u00fcbersetzt\" width=\"236\" height=\"139\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Der App-Name auf Arabisch \u00fcbersetzt<\/span><\/p>\n<p>Das ist alles, was ben\u00f6tigt wird, um mit Vue I18n in unseren Apps loszulegen. Nat\u00fcrlich will man wahrscheinlich weiterhin \u00dcbersetzungsnachrichten hinzuf\u00fcgen, w\u00e4hrend die App weiterentwickelt wird. Um alles ordentlich zu halten, sollte das <code>messages<\/code> Objekt in ein eigenes Modul umstrukturiert werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"d477649c-4a69-4832-8439-62925ce475ee\" data-enlighter-title=\"src\/i18n\/messages.js\">export default {\n  en: {\n    appTitle: 'Mushahed',\n  },\n  ar: {\n    appTitle: '\u0645\u0634\u0627\u0647\u062f',\n  },\n}\n\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"3aaa9847-5b45-4f7f-a82a-80bfd89d10ad\" data-enlighter-title=\"src\/i18n\/index.js\" data-enlighter-highlight=\"2,6\">import { createI18n } from 'vue-i18n'\nimport messages from '.\/messages'\n\nconst i18n = createI18n({\n  locale: 'ar',\n  messages,\n})\n\nexport default i18n\n<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"nachrichten-in-komponenten-uebersetzen-%e2%80%93-so-gehts\"><\/span>Nachrichten in Komponenten \u00fcbersetzen \u2013 so geht&#8217;s<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Das kam bereits zur Sprache, als die Vue I18n Installation weiter oben getestet wurde \u2013 aber es schadet nicht, das Ganze zu wiederholen. Es sind nur zwei Schritte n\u00f6tig:<\/p>\n<ol>\n<li>Im <code>messages<\/code>-Objekt, unter jeder unserer Lokalisierungen, f\u00fcgen wir \u00dcbersetzungen mit einem gemeinsamen Schl\u00fcssel hinzu.<\/li>\n<li><code>$t(key)<\/code> wird in den Komponentenvorlagen verwendet, um die \u00dcbersetzung entsprechend der aktiven Lokalisierung darzustellen.<\/li>\n<\/ol>\n<p>Das wird angewendet, indem der Rest der <code>&lt;Nav&gt;<\/code>-Komponente lokalisiert wird. Ein paar weitere \u00dcbersetzungen sind n\u00f6tig, um zu starten.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"1d01209e-9184-4ffc-9bf5-f7051a386d99\" data-enlighter-title=\"src\/i18n\/messages.js\">export default {\n  en: {\n    \/\/ Dieselben Schl\u00fcssel wie ar verwenden\n    appTitle: 'Mushahed',\n    Logo: 'Mushahed-Logo'\n    home: 'Home',\n    about: 'About',\n  },\n  ar: {\n    \/\/ Dieselben Schl\u00fcssel wie en verwenden\n    appTitle: '\u0645\u0634\u0627\u0647\u062f',\n    logo: '\u0631\u0645\u0632 \u0645\u0634\u0627\u0647\u062f',\n    home: '\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629',\n    about: '\u0646\u0628\u0630\u0629 \u0639\u0646\u0627',\n  },\n}\n\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"43ce4119-f446-47a0-9501-75c1aa9f8716\" data-enlighter-title=\"src\/components\/Nav.vue\" data-enlighter-highlight=\"7,10,11\">&lt;script setup&gt;\nimport { RouterLink } from 'vue-router'\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;nav&gt;\n    &lt;img :alt=\"$t('logo')\" src=\"@\/assets\/logo.svg\" \/&gt;\n    &lt;span&gt;{{ $t('appTitle') }}&lt;\/span&gt;\n\n    &lt;RouterLink to=\"\/\"&gt;{{ $t('home') }}&lt;\/RouterLink&gt;\n    &lt;RouterLink to=\"\/about\"&gt;{{ $t('about') }}&lt;\/RouterLink&gt;\n  &lt;\/nav&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Mit <code>$t()<\/code> und der <code>{{ }}<\/code> Syntax l\u00e4sst sich der innere Text eines Elements \u00fcbersetzen. Die <code>:attribute<\/code> Bindungsabk\u00fcrzung ist beim \u00dcbersetzen eines Attributs sehr n\u00fctzlich.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35684\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-basic-translation.png\" alt=\"Die \u00dcbersetzung unseres App-Namens ins Arabische\" width=\"546\" height=\"140\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-basic-translation.png 546w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-basic-translation-300x77.png 300w\" sizes=\"auto, (max-width: 546px) 100vw, 546px\" \/><\/p>\n<div>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Die Navigationskomponente bei aktiviertem Arabisch<\/span><\/p>\n<\/div>\n<h2><span class=\"ez-toc-section\" id=\"so-funktioniert-der-umgang-mit-dynamischen-werten-in-uebersetzungsnachrichten\"><\/span>So funktioniert der Umgang mit dynamischen Werten in \u00dcbersetzungsnachrichten<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Ein h\u00e4ufiger Anwendungsfall: Werte, die sich zur Laufzeit in Nachrichten \u00e4ndern, lassen sich mit Vue I18n einfach interpolieren. Unsere <code>&lt;Coords&gt;<\/code> Komponente, die die Koordinaten der Internationalen Raumstation (ISS) zu einem gegebenen Zeitpunkt anzeigt, ist ideal, um dies zu demonstrieren.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"dc1f8952-7379-4a1d-810b-8f42370a1e52\" data-enlighter-title=\"src\/components\/Coords.vue\">&lt;script&gt;\nexport default {\n  data() {\n    return {\n      coords: {\n        latitude: 49.5908,\n        longitude: 122.8927,\n      },\n      datetime: new Date(1658828129000),\n    }\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;p&gt;\n    Die ISS war \u00fcber {{ coords.latitude }}\u00b0 N, {{ coords.longitude }}\u00b0 E am {{ datetime }}\n  &lt;\/p&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<div><\/div>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-35691\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-interpolation.png\" alt=\"Hartkodierte englische Interpolation\" width=\"1952\" height=\"168\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-interpolation.png 1952w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-interpolation-300x26.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-interpolation-1024x88.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-interpolation-768x66.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-interpolation-1536x132.png 1536w\" sizes=\"auto, (max-width: 1952px) 100vw, 1952px\" \/><\/p>\n<p>Die Koordinaten- und Datumswerte wurden oben zur Klarheit fest codiert, aber in einer echten App w\u00fcrden diese wahrscheinlich von einer API abgerufen und bei der Erstellung der Komponente <code>aktualisiert<\/code>. Vue I18n ber\u00fccksichtigt diese dynamischen Werte in seinen Nachrichten durch eine <code>{placeholder}<\/code>-Syntax.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-group=\"1cf76188-52c0-431b-aec6-195f6f0e91be\" data-enlighter-title=\"src\/i18n\/messages.js\" data-enlighter-highlight=\"4,8,9\" data-enlighter-linenumbers=\"false\">export default {\n  en: {\n    \/\/ ...\n    issPosition: 'Die ISS befand sich \u00fcber {latitude}\u00b0 N, {longitude}\u00b0 E am {datetime}.'\n  },\n  ar: {\n    \/\/ ...\n    issPosition:\n      'Die Internationale Raumstation war \u00fcber {latitude} Grad n\u00f6rdlicher Breite und {longitude} Grad \u00f6stlicher L\u00e4nge am {datetime}.'\n  },\n}\n\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"166ceb1f-120d-4745-a8b6-aab60e321576\" data-enlighter-title=\"src\/components\/Coords.vue\" data-enlighter-highlight=\"17,18,19,20,21\">&lt;script&gt;\nexport default {\n  data() {\n    return {\n      coords: {\n        latitude: 49.5908,\n        longitude: 122.8927,\n      },\n      datetime: new Date(1658828129000),\n    }\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;p&gt;\n    {{ $t('issPosition', {\n        latitude: coords.latitude,\n        longitude: coords.longitude,\n        datetime,\n      }) }}\n  &lt;\/p&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Ein zweites Argument an <code>$t()<\/code> \u00fcbergeben \u2013 eine Map von Schl\u00fcssel\/Wert-Paaren, bei denen die Schl\u00fcssel mit denen in den \u00dcbersetzungsnachrichten \u00fcbereinstimmen \u2013 l\u00e4sst diese Nachrichten mit den eingef\u00fcgten Werten rendern.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35698\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-interpolation.png\" alt=\"Eine arabische Nachricht mit interpolierten dynamischen Werten\" width=\"1838\" height=\"132\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-interpolation.png 1838w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-interpolation-300x22.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-interpolation-1024x74.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-interpolation-768x55.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-interpolation-1536x110.png 1536w\" sizes=\"auto, (max-width: 1838px) 100vw, 1838px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Eine arabische Nachricht mit interpolierten dynamischen Werten<\/span><\/p>\n<p>\ud83d\uddd2 <em>Hinweis \u00bb<\/em> Die Zahlen und das Datum oben sind <em>nicht<\/em> auf Arabisch. Das wird sp\u00e4ter erledigt.<\/p>\n<p>\ud83e\udd3f <em>Mehr erfahren \u00bb<\/em> <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/syntax.html#interpolations\">Alle M\u00f6glichkeiten zur Interpolation in Nachrichten<\/a> aus der offiziellen Vue I18n Dokumentation kennenlernen.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"so-wirds-gemacht-strings-in-component-javascript-uebersetzen\"><\/span>So wird&#8217;s gemacht: Strings in Component-JavaScript \u00fcbersetzen<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Die <code>$t()<\/code>-Funktion steht in unserem Komponenten-JavaScript \u00fcber <code>this.$t()<\/code> zur Verf\u00fcgung. Mit diesem wird die <code>&lt;Coords&gt;<\/code>-Komponente \u00fcberarbeitet und der umfangreiche <code>$t()<\/code>-Aufruf in das <code>&lt;script&gt;<\/code> der Komponente verschoben.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"30cbef27-76f1-4cfb-a6fc-5a2df8a863e7\" data-enlighter-title=\"src\/components\/Coords.vue\" data-enlighter-highlight=\"13,14,15,16,17,18,19,20,21,26\">&lt;script&gt;\nexport default {\n  data() {\n    return {\n      coords: {\n        latitude: 49.5908,\n        longitude: 122.8927,\n      },\n      datetime: new Date(1658828129000),\n    }\n  },\n\n  computed: {\n    issPosition() {\n      return this.$t('issPosition', {\n        latitude: this.coords.latitude,\n        longitude: this.coords.longitude,\n        datetime: this.datetime,\n      })\n    },\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;p&gt;{{ issPosition }}&lt;\/p&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"arbeiten-mit-html-in-uebersetzungsnachrichten-%e2%80%93-so-gehts\"><\/span>Arbeiten mit HTML in \u00dcbersetzungsnachrichten \u2013 so geht&#8217;s<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Manchmal muss HTML in unseren \u00dcbersetzungsnachrichten platziert werden. Unser <code>&lt;Footer&gt;<\/code> ist ein gutes Beispiel.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"ca92d139-d0be-4cef-ae6b-be2d308182ae\" data-enlighter-title=\"src\/components\/Footer.vue\">&lt;template&gt;\n  &lt;p&gt;\n    Erstellt mit\n    &lt;a href=\"https:\/\/vuejs.org\/\"&gt;Vue&lt;\/a&gt; f\u00fcr ein\n    &lt;a href=\"https:\/\/phrase.com\/blog\"&gt;Phrase Blog&lt;\/a&gt;\n    Tutorial.\n  &lt;\/p&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Es ist nicht einfach, diesen Text zu lokalisieren, da die Positionen der eingebetteten Links je nach \u00dcbersetzungssprache variieren k\u00f6nnen. Es w\u00e4re m\u00f6glich, die <code>&lt;a&gt;<\/code> Tags direkt in die \u00dcbersetzungsnachrichten einzuf\u00fcgen und die unsichere <code>v-html<\/code> Direktive von Vue zu nutzen, um die \u00dcbersetzungen auszugeben. Allerdings k\u00f6nnte das bei unvorsichtiger Vorgehensweise zu XSS-Angriffen f\u00fchren.<\/p>\n<p>Vue I18n bietet eine bessere L\u00f6sung: Mit der <code>&lt;i18n-t&gt;<\/code> Komponente k\u00f6nnen die untergeordneten Elemente, einschlie\u00dflich HTML-Elemente, innerhalb der Nachrichten \u00fcber Platzhalter dargestellt werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-group=\"21ed5283-7971-4493-9a53-22c2f012c89f\" data-enlighter-title=\"src\/i18n\/messages.js\" data-enlighter-linenumbers=\"false\">export default {\n  en: {\n    \/\/ ...\n    footer: 'Mit {0} f\u00fcr eine {1} erstellt.'\n    vue: 'Vue',\n    phraseBlogTutorial: 'Phrase-Blog-Tutorial'\n  },\n  ar: {\n    \/\/ ...\n    footer: '.\u062a\u0645 \u0625\u0646\u0634\u0627\u0626\u0647 \u0628\u0648\u0627\u0633\u0637\u0629 {0} \u0644\u064a\u0635\u0627\u062d\u0628 {1}',\n    vue: '\u06a4\u064a\u0648',\n    phraseBlogTutorial: 'Tutorial auf dem Phrase-Blog',\n  },\n}\n\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"f050d163-5460-491b-b10c-9612ba3e35af\" data-enlighter-title=\"src\/components\/Footer.vue\">&lt;script&gt;\nexport default {\n  data() {\n    return {\n      vueUrl: 'https:\/\/vuejs.org\/',\n      phraseBlogUrl: 'https:\/\/phrase.com\/blog',\n    }\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;i18n-t keypath=\"footer\" tag=\"p\" scope=\"global\"&gt;\n    &lt;a :href=\"vueUrl\"&gt;{{ $t('vue') }}&lt;\/a&gt;\n    &lt;a :href=\"phraseBlogUrl\"&gt;{{ $t('phraseBlogTutorial') }}&lt;\/a&gt;\n  &lt;\/i18n-t&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Eine <code>keypath<\/code> Eigenschaft mit dem Schl\u00fcssel der \u00fcbergeordneten \u00dcbersetzungsnachricht wird \u00fcbergeben, in diesem Fall ist es <code>footer<\/code>. Beim Rendering wird <code>&lt;i18n-t&gt;<\/code> darauf hingewiesen, dass ein umgebendes <code>&lt;p&gt;<\/code> \u00fcber die <code>tag<\/code>-Eigenschaft ausgegeben werden soll.<\/p>\n<p>In der Hauptnachricht werden Platzhalter mittels <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/syntax.html#list-interpolation\">list interpolation<\/a> festgelegt, das bedeutet, es wird mit <code>{0}<\/code> angefangen und dann zu <code>{1}<\/code> usw. fortgefahren. Hier kommt es auf die Reihenfolge an: Das erste <code>&lt;a&gt;<\/code> in <code>&lt;i18n-t&gt;<\/code> ersetzt den <code>{0}<\/code> Platzhalter, das zweite ersetzt <code>{1}<\/code>, und so weiter. So l\u00e4sst sich die Reihenfolge der HTML-Elemente in der \u00dcbersetzungsnachricht jeder Sprache steuern.<\/p>\n<p>\ud83d\uddd2 <em>Hinweis \u00bb<\/em> Wenn die <code>scope=\"global\"<\/code> Eigenschaft nicht explizit auf der <code>&lt;i18n-t&gt;<\/code> Komponente gesetzt wird, erscheint eine Konsolenwarnung: \u201e[intlify] \u00dcbergeordneter Bereich nicht gefunden. Es wird der globale Bereich verwendet.\u201c<\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Im Abschnitt <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/advanced\/component.html\">Component Interpolation <\/a> des offiziellen Leitfadens wird die <code>&lt;i18n-t&gt;<\/code> Komponente genauer erl\u00e4utert.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"wie-wird-mit-pluralen-in-uebersetzungen-umgegangen\"><\/span>Wie wird mit Pluralen in \u00dcbersetzungen umgegangen?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Die beiden englischen Pluralformen sind einfach: \u201c<em>Ein Satellit<\/em> <em>kreist<\/em> oben\u201d; \u201c<em>Drei Satelliten<\/em> <em>kreisen<\/em> oben\u201d. Andere Sprachen sind deutlich komplexer. Manche haben vier Pluralformen. Walisisch und Arabisch haben sechs. Vue I18n unterst\u00fctzt einfache Pluralformen, wie die im Englischen, direkt nach der Installation. Das Plugin kann erweitert werden, um komplexe Pluralformen zu handhaben. Im Folgenden werden sowohl einfache als auch komplexe Pluralformen behandelt.<\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Die <a href=\"https:\/\/unicode-org.github.io\/cldr-staging\/charts\/latest\/supplemental\/language_plural_rules.html\">CLDR Language Plural Rules<\/a> Referenz ist ma\u00dfgeblich f\u00fcr die Pluralformen von Sprachen.<\/p>\n<p>Der Header unserer <code>&lt;Astronauts&gt;<\/code>-Komponente wird nochmal betrachtet.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35706\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-astros-header.png\" alt=\"Ein Astronautenz\u00e4hler\" width=\"532\" height=\"128\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-astros-header.png 532w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-astros-header-300x72.png 300w\" sizes=\"auto, (max-width: 532px) 100vw, 532px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Ein Astronautenz\u00e4hler<\/span><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"0f82150c-6c88-4149-88a4-d5a1610ac55a\" data-enlighter-title=\"src\/components\/Astronauts.vue\" data-enlighter-highlight=\"7\">&lt;script&gt;\n\/\/ Hier wird das astros-Array gef\u00fcllt...\n&lt;\/script&gt;\n&lt;template&gt;\n  &lt;div&gt;\n    &lt;div&gt;\n      &lt;h2&gt;\ud83e\uddd1\u200d\ud83d\ude80 {{ astros.length }} Personen im Weltraum&lt;\/h2&gt;\n\n      &lt;p&gt;Aktualisiert am 26. Juli 2022&lt;\/p&gt;\n    &lt;\/div&gt;\n\n    &lt;!-- ... --&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Unser Astronautenz\u00e4hler ist fest codiert und bereit f\u00fcr die Lokalisierung. F\u00fcgen wir eine englische Nachricht daf\u00fcr hinzu.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"1635ae22-202e-4e23-bdcb-682942132b8e\" data-enlighter-title=\"src\/i18n\/messages.js\">export default {\n  en: {\n    \/\/ ...\n    peopleInSpace:\n      '{n} Person im All | {n} Personen im All',\n  },\n  ar: {\n    \/\/ ...\n  }\n}\n\n<\/pre>\n<p>Es wird erwartet, dass Vue I18n die Pluralformen mit einem Pipe-Zeichen (<code>|<\/code>) trennt. Oben sind die beiden Pluralformen f\u00fcr Englisch angegeben. Wenn die Pluralnachricht abgerufen wird, wird der <code>{n}<\/code>-Platzhalter durch einen ganzzahligen Z\u00e4hler ersetzt.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"9ceae467-bec3-42d2-9409-66737ccd09ed\" data-enlighter-title=\"src\/components\/Astronauts.vue\" data-enlighter-highlight=\"7\">&lt;script&gt;\n\/\/ Hier wird das astros-Array gef\u00fcllt...\n&lt;\/script&gt;\n&lt;template&gt;\n  &lt;div&gt;\n    &lt;div&gt;\n      &lt;h2&gt;\ud83e\uddd1\u200d\ud83d\ude80 {{ $tc('peopleInSpace', astros.length) }}&lt;\/h2&gt;\n\n      &lt;p&gt;Aktualisiert am 26. Juli 2022&lt;\/p&gt;\n    &lt;\/div&gt;\n\n    &lt;!-- ... --&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p><code>$tc()<\/code>, eine weitere \u00dcbersetzungsfunktion, die von Vue i18n in alle Komponenten eingef\u00fcgt wird, w\u00e4hlt die passende Pluralform basierend auf dem zweiten Parameter, dem Integer-Z\u00e4hler.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35711\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-plurals.png\" alt=\"Darstellungen englischer Pluralformen. Beachte, dass {n} durch den Z\u00e4hler ersetzt wird.\" width=\"494\" height=\"344\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-plurals.png 494w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/en-plurals-300x209.png 300w\" sizes=\"auto, (max-width: 494px) 100vw, 494px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Darstellungen englischer Pluralformen. Beachte, dass {n} durch den Z\u00e4hler ersetzt wird.<\/span><\/p>\n<p>Zwei Formen funktionieren gut f\u00fcr Englisch, aber unsere arabische \u00dcbersetzung ben\u00f6tigt sechs Pluralvarianten.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"13d09d7a-6642-4f18-8bb1-7c8d5c2896be\" data-enlighter-title=\"src\/i18n\/messages.js\">export default {\n  en: {\n    \/\/ ...\n  },\n  ar: {\n    \/\/ ...\n    peopleInSpace:\n      'Es gibt niemanden im Weltraum | Es gibt {n} Person im Weltraum | Es gibt zwei Personen im Weltraum | Es gibt {n} Personen im Weltraum | Es gibt {n} Person im Weltraum | Es gibt {n} Person im Weltraum'\n  },\n}\n\n<\/pre>\n<p>Alleine kann Vue I18n nur mit Pluralformen umgehen, die dem Englischen \u00e4hneln. Daher ist es notwendig, eine spezielle Erweiterungsfunktion hinzuzuf\u00fcgen, die die sechs Formen des Arabischen ber\u00fccksichtigt.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"024323cc-bd26-473b-a267-d6dbdb1f8ae1\" data-enlighter-title=\"src\/i18n\/plurals.js\">export function arabicPluralRules(auswahl) {\n  const name = new Intl.PluralRules('ar').select(choice)\n\n  return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name]\n}\n\n<\/pre>\n<p>Das Standardobjekt <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/PluralRules\/PluralRules\">Intl.PluralRules<\/a> von JavaScript meistert komplexe Plurale wunderbar. Es muss nur ein Locale beim Erstellen angegeben und dann die <code>select()<\/code>-Methode mit einem Integer-Z\u00e4hler aufgerufen werden. Die Methode liefert den Namen der richtigen Form f\u00fcr die jeweilige Sprache. Zum Beispiel liefert <code>new Intl.PluralRules('ar').select(5)<\/code> die korrekte <code>few<\/code> Form.<\/p>\n<p>Vue I18n ben\u00f6tigt einen Integer-Index, um die passende Form in unseren \u00dcbersetzungsnachrichten auszuw\u00e4hlen. Daher muss der individuelle Pluralw\u00e4hler den CLDR Pluralformnamen (<code>few<\/code>) auf einen nullbasierten Index (<code>3<\/code>) abbilden. Der Index w\u00e4hlt aus unserer durch | getrennten Nachricht aus. So w\u00fcrde <code>3<\/code> unsere vierte Variante aus der <code>peopleInSpace<\/code>-Nachricht oben ausw\u00e4hlen.<\/p>\n<p>Jetzt muss nur noch der arabische Pluralregelw\u00e4hler beim Erstellen der Vue I18n Instanz angeschlossen werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"20b93d17-a714-4f37-9fbb-cd5f47fbe91b\" data-enlighter-title=\"src\/i18n\/index.js\" data-enlighter-highlight=\"3,11,12,13\">import { createI18n } from 'vue-i18n'\nimport messages from '.\/messages'\nimport { arabicPluralRules } from '.\/plurals'\n\nconst i18n = createI18n({\n  locale: 'ar',\n  messages,\n  \/\/ Mit Vue I18n kann die Pluralisierung erweitert werden\n  \/\/ Formatierung durch Bereitstellung eines Formularselektors\n  \/\/ Funktion pro Lokalit\u00e4t\n  pluralizationRules: {\n    ar: arabicPluralRules,\n  },\n})\n\nexport default i18n\n\n<\/pre>\n<p>Mit dem angeschlossenen Selektor sollten die arabischen Pluralformen problemlos funktionieren.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35716\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-plurals.png\" alt=\"Darstellungen arabischer Pluralformen. Beachten, dass {n} durch den Z\u00e4hler ersetzt wird.\" width=\"1000\" height=\"704\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-plurals.png 1000w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-plurals-300x211.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-plurals-768x541.png 768w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Darstellungen von arabischen Pluralformen. Beachte, dass {n} durch den Z\u00e4hler ersetzt wird.<\/span><\/p>\n<p>\u270b <em>Hinweis \u00bb<\/em> Es k\u00f6nnte aufgefallen sein, dass der interpolierte Z\u00e4hler in westlichen arabischen Ziffern (1, 2 usw.) angezeigt wird. Arabisch verwendet jedoch \u00f6stliche arabische Ziffern (\u0661\u060c \u0662\u060c \u0663\u060c usw.). Obwohl es kein Showstopper ist, ist dieses Problem auf dem Vue i18n GitHub geloggt, falls Interesse besteht, es zu verfolgen.<\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Mehr Informationen im <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/pluralization.html#custom-pluralization\">offiziellen Leitfaden zur Pluralisierung<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"lokalisierte-zahlen-formatieren-%e2%80%93-so-gehts\"><\/span>Lokalisierte Zahlen formatieren \u2013 so geht&#8217;s<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>In verschiedenen Regionen werden unterschiedliche Zahlensysteme, Tausendertrennzeichen und Symbole verwendet, um Zahlen darzustellen. Das eingebaute <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/NumberFormat\">Intl.NumberFormat<\/a>-Objekt von JavaScript \u00fcbernimmt all das und wird im Hintergrund von Vue I18n verwendet. Es m\u00fcssen nur vorkonfigurierte Zahlenformate an Vue I18n \u00fcbergeben werden, die das Plugin wiederum an <code>Intl.NumberFormat<\/code> weiterleitet. Die registrierten Formate sind dann in den Komponenten verf\u00fcgbar.<\/p>\n<p>\ud83e\udd3f <em>Mehr erfahren \u00bb<\/em> Unser <a href=\"https:\/\/phrase.com\/blog\/posts\/number-localization\/\">Kompakter Leitfaden zur Zahlenslokalisierung<\/a> deckt Zahlensysteme, Trennzeichen und mehr ab.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"53dff551-57ad-43af-998b-95d5e8a2142f\" data-enlighter-title=\"src\/i18n\/numbers.js\">\/\/ Es werden die Formate angegeben, die die App nutzen wird\nexport const numberFormats = {\n  'en-US': {\n    \/\/ Ein benanntes Format\n    coords: {\n      \/\/ Diese Optionen werden an Intl.NumberFormat weitergegeben\n      style: 'decimal',\n      minimumSignificantDigits: 6,\n      maximumSignificantDigits: 6,\n    },\n  },\n  'ar-EG': {\n    coords: {\n      style: 'decimal',\n      minimumSignificantDigits: 6,\n      maximumSignificantDigits: 6,\n    },\n  },\n}\n\n<\/pre>\n<p>Unsere Zahlenformate m\u00fcssen w\u00e4hrend der Konstruktion beim Vue I18n-Objekt registriert werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-highlight=\"3,9\">import { createI18n } from 'vue-i18n'\nimport messages from '.\/messages'\nimport { numberFormats } from '.\/numbers'\nimport { arabicPluralRules } from '.\/plurals'\n\nconst i18n = createI18n({\n  locale: 'en-US',\n  messages,\n  numberFormats,\n  pluralizationRules: {\n    'ar-EG': arabicPluralRules,\n  },\n})\n\nexport default i18n\n\n<\/pre>\n<p>Jetzt k\u00f6nnen wir die eingef\u00fcgte <code>$n()<\/code> Funktion nutzen, um lokalisierte Zahlen in unseren Komponentenvorlagen zu formatieren.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">&lt;!-- Das benannte Format wird als zweiter Parameter angegeben --&gt;\n&lt;p&gt;{{ $n(49.5908, 'coords') }}&lt;\/p&gt;\n\n&lt;!-- =&gt; \"49.5908\" wenn das Gebietsschema en-US ist --&gt;\n&lt;!-- =&gt; \"\u0664\u0669\u060c\u0665\u0669\u0660\u0668\" wenn das Gebietsschema ar-EG ist --&gt;\n\n<\/pre>\n<p>\u270b <em>Achtung \u00bb<\/em> Vielleicht ist aufgefallen, dass <code>en<\/code> mit <code>en-US<\/code> und <code>ar<\/code> mit <code>ar-EG<\/code> in der obigen Konfiguration ausgetauscht wurden. Das liegt daran, dass <em>die Zahlenformatierung regionsspezifisch und nicht sprachspezifisch ist<\/em>. Durch das Hinzuf\u00fcgen von L\u00e4ndern oder Regionen zu unseren Locale-Tags kann die Ausgabe der lokalisierten Zahlenformatierung gesteuert werden. Andernfalls besteht die Gefahr, dass der Browser eine Standardregion verwendet. Nat\u00fcrlich m\u00fcssen unsere <code>Nachrichten<\/code> so aktualisiert werden, dass sie auch mit <code>en-US<\/code> und <code>ar-EG<\/code> verkn\u00fcpft sind.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"ff97c110-26bc-45ae-bc67-623aa2cf2e9e\" data-enlighter-title=\"src\/i18n\/messages.js\" data-enlighter-highlight=\"2,6\">export default {\n  'en-US': {\n    appTitle: 'Mushahed',\n    \/\/ ...\n  },\n  'ar-EG': {\n    appTitle: '\u0645\u0634\u0627\u0647\u062f',\n    \/\/ ...\n  },\n}\n\n<\/pre>\n<p>Die <code>&lt;Coords&gt;<\/code> Komponente wird so aktualisiert, dass die ISS-Koordinaten im Zahlenformat der aktiven Locale formatiert werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"acf8121b-a239-477c-89d7-b36b27cb9c89\" data-enlighter-title=\"src\/components\/Coords.vue\" data-enlighter-highlight=\"18,19\">&lt;script&gt;\nexport default {\n  data() {\n    return {\n      coords: null,\n      datetime: '',\n    }\n  },\n\n  created() {\n    \/\/ Die Koordinatendaten werden abgerufen und festgelegt\n    \/\/ this.coords und this.datetime hier ...\n  },\n\n  computed: {\n    issPosition() {\n      return this.$t('issPosition', {\n        latitude: this.$n(this.coords.latitude, 'coords'),\n        longitude: this.$n(this.coords.longitude, 'coords'),\n        datetime: this.datetime,\n      })\n    },\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;p&gt;{{ issPosition }}&lt;\/p&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<div>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35721\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/number-formatting.png\" alt=\"Darstellungen arabischer Pluralformen. Beachte, dass {n} durch unseren Z\u00e4hler ersetzt wird.\" width=\"1782\" height=\"303\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/number-formatting.png 1782w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/number-formatting-300x51.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/number-formatting-1024x174.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/number-formatting-768x131.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/number-formatting-1536x261.png 1536w\" sizes=\"auto, (max-width: 1782px) 100vw, 1782px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Darstellungen von arabischen Pluralformen. Beachte, dass {n} durch den Z\u00e4hler ersetzt wird.<\/span><\/p>\n<\/div>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Mehr Details gibt es im Abschnitt <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/number.html\">Nummernformatierung<\/a> der Vue I18n Dokumentation.<\/p>\n<p>Das Datum oben wirkt in der ansonsten arabischen Nachricht sehr englisch, oder? Kein Problem. Rat mal, was als N\u00e4chstes kommt?<\/p>\n<h2><span class=\"ez-toc-section\" id=\"formatierung-von-lokalisierten-datums-und-zeitangaben-%e2%80%93-so-gehts\"><\/span>Formatierung von lokalisierten Datums- und Zeitangaben \u2013 so geht\u2019s<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>\u00c4hnlich wie bei der Zahlenformatierung ist die Datumsformatierung <em>regionspezifisch<\/em>. Die USA und Kanada sprechen beide Englisch, aber der 9. September 2022 kann in den USA 9\/4\/2022 und in Kanada 2022-09-04 sein. Um korrekt mit lokalisierten Datumsangaben zu arbeiten, wird einem Rezept gefolgt, \u00e4hnlich wie bei Datumsangaben. Mit Vue I18n werden benannte Datumsformate bereitgestellt, die das Plugin als Optionen an das Standardformat <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/DateTimeFormat\">Intl.DateTimeFormat<\/a> weitergibt. Die registrierten Formate werden dann in den Komponenten verwendet.<\/p>\n<p>\ud83d\uddd2 <em>Hinweis \u00bb<\/em> Die Datums-Lokalisierung ist der Zahlen-Lokalisierung sehr \u00e4hnlich, daher baut dieser Abschnitt auf dem letzten auf.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"0034e3ba-e911-455e-aa9b-51aa1b594152\" data-enlighter-title=\"src\/i18n\/datetimes.js\">export const datetimeFormats = {\n  'en-US': {\n    full: {\n      \/\/ Diese Optionen werden an Intl.DateTimeFormat \u00fcbergeben\n      dateStyle: 'full',\n      timeStyle: 'full',\n    },\n    short: {\n      year: 'numeric',\n      month: 'short',\n      day: 'numeric',\n    },\n  },\n  'ar-EG': {\n    full: {\n      dateStyle: 'full',\n      timeStyle: 'full',\n    },\n    short: {\n      year: 'numeric',\n      month: 'long',\n      day: 'numeric',\n    },\n  },\n}\n\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"b47f9bc6-fff8-4e93-a1be-22141a8229c9\" data-enlighter-title=\"src\/i18n\/index.js\" data-enlighter-highlight=\"4,11\">import { createI18n } from 'vue-i18n'\nimport messages from '.\/messages'\nimport { numberFormats } from '.\/numbers'\nimport { datetimeFormats } from '.\/datetimes'\nimport { arabicPluralRules } from '.\/plurals'\n\nconst i18n = createI18n({\n  locale: 'en-US',\n  messages,\n  numberFormats,\n  datetimeFormats,\n  pluralizationRules: {\n    'ar-EG': arabicPluralRules,\n  },\n})\n\nexport default i18n\n\n<\/pre>\n<p>Mit den spezifizierten und registrierten Formaten kann die eingef\u00fcgte <code>$d()<\/code> Funktion genutzt werden, um lokalisierte Daten in den Komponenten darzustellen. Unsere <code>&lt;Coords&gt;<\/code> Komponente wird mit ordentlicher Datumsformatierung abgerundet.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"8015f894-4028-469e-b43e-a9a75ff6566c\" data-enlighter-title=\"src\/components\/Coords.vue\" data-enlighter-highlight=\"10\">&lt;script&gt;\nexport default {\n  \/\/ ...\n\n  computed: {\n    issPosition() {\n      return this.$t('issPosition', {\n        latitude: this.$n(this.coords.latitude, 'coords'),\n        longitude: this.$n(this.coords.longitude, 'coords'),\n        datetime: this.$d(this.datetime, 'full'),\n      })\n    },\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;p&gt;{{ issPosition }}&lt;\/p&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<div>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35726\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/full-date-format.png\" alt=\"Vollst\u00e4ndige Datumsformate im amerikanischen Englisch und \u00e4gyptischen Arabisch\" width=\"1792\" height=\"312\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/full-date-format.png 1792w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/full-date-format-300x52.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/full-date-format-1024x178.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/full-date-format-768x134.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/full-date-format-1536x267.png 1536w\" sizes=\"auto, (max-width: 1792px) 100vw, 1792px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Amerikanisches Englisch und \u00c4gyptisches Arabisch: vollst\u00e4ndige Datumsformate<\/span><\/p>\n<\/div>\n<p>W\u00e4hrend wir dabei sind, wird der <code>&lt;Astronauts&gt;<\/code> Header so formatiert, dass er lokalisierte kurze Daten in der &#8222;Aktualisiert&#8220;-Nachricht anzeigt.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"5ed38931-0864-4427-83d0-135ba26967c6\" data-enlighter-title=\"src\/i18n\/messages.js\" data-enlighter-highlight=\"4,8\">export default {\n  'en-US': {\n    \/\/ ...\n    updatedAt: 'Updated {date}',\n  },\n  'ar-EG': {\n    \/\/ ...\n    updatedAt: 'Letztes Update {date}',\n  },\n}\n\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"029a09ab-5a80-45f4-b8ef-56b4201254d5\" data-enlighter-title=\"src\/components\/Astronauts.vue\" data-enlighter-highlight=\"8\">&lt;script&gt;\n\/\/ ...\n&lt;\/script&gt;\n&lt;template&gt;\n  &lt;!-- ... --&gt;\n  &lt;h2&gt;\ud83e\uddd1\u200d\ud83d\ude80 {{ $tc('peopleInSpace', astros.length) }}&lt;\/h2&gt;\n  &lt;p&gt;\n    {{ $t('updatedAt', { date: $d(updated, 'short') }) }}\n  &lt;\/p&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<div>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35731\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/short-date-format.png\" alt=\"Amerikanische und \u00e4gyptische Kurzdatumsformate werden dargestellt\" width=\"1866\" height=\"938\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/short-date-format.png 1866w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/short-date-format-300x151.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/short-date-format-1024x515.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/short-date-format-768x386.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/short-date-format-1536x772.png 1536w\" sizes=\"auto, (max-width: 1866px) 100vw, 1866px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Amerikanische und \u00e4gyptische Kurzdatumsformate werden dargestellt<\/span><\/p>\n<\/div>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Im offiziellen Vue I18n Guide werden mehr <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/datetime.html\">Datumsformatierungsoptionen<\/a> behandelt.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"wie-kann-die-aktive-spracheinstellung-abgerufen-werden\"><\/span>Wie kann die aktive Spracheinstellung abgerufen werden?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Manchmal m\u00fcssen Entscheidungen basierend auf der Laufzeit-Locale der App getroffen werden. Mit Vue I18n l\u00e4sst sich die aktive Sprache einfach \u00fcber <code>i18n.locale<\/code> ermitteln.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-highlight=\"5,13\">&lt;script&gt;\nexport default {\n  methods: {\n    activeLocale() {\n      return this.$i18n.locale\n    }\n  }\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;!-- Angenommen, die aktive Locale ist en-US --&gt;\n  &lt;p&gt;{{ $i18n.locale}}&lt;\/p&gt; &lt;!-- =&gt; &lt;p&gt;en-US&lt;\/p&gt; --&gt;\n\n  &lt;p&gt;{{ activeLocale() }}&lt;\/p&gt; &lt;!-- =&gt; &lt;p&gt;en-US&lt;\/p&gt; --&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Ein neuer Wert kann auch f\u00fcr <code>$i18n.locale<\/code> festgelegt werden, um eine neue aktive Locale zu setzen. Gleich wird dies in Aktion gezeigt.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"umgestaltung-der-i18n-bibliothek\"><\/span>Umgestaltung der i18n Bibliothek<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>In den folgenden Abschnitten geht es um fortgeschrittene Themen wie lokalisierte Routen und das asynchrone Laden von \u00dcbersetzungsdateien. Das wird einfacher umzusetzen sein, wenn die kleine i18n-Bibliothek umgestaltet wird, damit gesteuert werden kann, wie Locales festgelegt und geladen werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-group=\"87591513-e0dd-402c-a0f8-0e616db8b311\" data-enlighter-title=\"src\/i18n\/index.js\" data-enlighter-linenumbers=\"false\">import { createI18n } from 'vue-i18n'\nimport { messages } from '.\/messages'\nimport { numberFormats } from '.\/numbers'\nimport { arabicPluralRules } from '.\/plurals'\nimport { datetimeFormats } from '.\/datetimes'\n\n\/\/ Die Standard-Locale festlegen und freigeben\nexport const defaultLocale = 'en-US'\n\n\/\/ Private Instanz des VueI18n-Objekts\nlet _i18n\n\n\/\/ Initializer\nfunction setup(options = { locale: defaultLocale }) {\n  _i18n = createI18n({\n    locale: options.locale,\n    fallbackLocale: defaultLocale,\n    messages,\n    numberFormats,\n    datetimeFormats,\n    pluralizationRules: {\n      'ar-EG': arabicPluralRules,\n    },\n  })\n\n  setLocale(options.locale)\n\n  return _i18n\n}\n\n\/\/ Legt die aktive Sprache fest. \nfunction setLocale(newLocale) {\n  _i18n.global.locale = newLocale\n}\n\n\/\/ \u00d6ffentliches Interface\nexport default {\n  \/\/ Die VueI18n-Instanz \u00fcber einen Getter bereitstellen\n  get vueI18n() {\n    return _i18n\n  },\n  setup,\n  setLocale,\n}\n\n<\/pre>\n<p>\ud83d\uddd2\ufe0f <em>Hinweis \u00bb<\/em> <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/scope.html#scope-and-locale-changing\">Vue I18n unterst\u00fctzt Scoping<\/a>, das verwendet werden kann, um die Locale f\u00fcr einen Teil der Komponenten-Hierarchie der App zu \u00e4ndern. Mithilfe von <code>i18n.global<\/code> wird auf den globalen, appweiten Scope von Vue I18n zugegriffen. Das ist der Standard-Scope von Vue I18n, und in diesem Artikel wird nur mit dem globalen Scope gearbeitet.<\/p>\n<p>Schauen wir mal, wie sich der Rest der App durch diese \u00dcberarbeitung ver\u00e4ndert.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"aafdef48-68be-49c2-bb02-1e217bf669dc\" data-enlighter-title=\"src\/main.js\" data-enlighter-highlight=\"10,12\">import { createApp } from 'vue'\nimport App from '.\/App.vue'\nimport router from '.\/router'\nimport i18n from '.\/i18n'\nimport '.\/assets\/main.css'\n\nconst app = createApp(App)\n\n\/\/ Die i18n-Bibliothek explizit initialisieren\ni18n.setup()\n\/\/ Die VueI18n-Instanz als Plugin an use() \u00fcbergeben\napp.use(i18n.vueI18n)\napp.use(router)\n\napp.mount('#app') \n\n<\/pre>\n<p>Es muss nichts weiter an der App ge\u00e4ndert werden, aber durch das Refactoring k\u00f6nnen komplexere Funktionen in den folgenden Abschnitten leichter entwickelt werden.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"wie-kann-die-lokalisierung-von-routen-erfolgen\"><\/span>Wie kann die Lokalisierung von Routen erfolgen?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Oft ist es sinnvoll, darauf zu achten, dass unsere URLs den dazugeh\u00f6rigen Inhalt widerspiegeln. Lokalisierte URLs k\u00f6nnen bedeuten, dass <code>\/en-US\/foo<\/code> und <code>\/ar-EG\/foo<\/code> auf die englische und arabische Version der <code>foo<\/code>-Seite verweisen. Lassen wir das in unserer Demo-App laufen.<\/p>\n<p>Zuerst wird geschaut, wie die Routen in der Demo konfiguriert wurden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"dd5a3338-1c85-439c-ae5f-f4d945a9c307\" data-enlighter-title=\"src\/router\/index.js\">import { createRouter, createWebHistory } from 'vue-router'\nimport HomeView from '..\/views\/HomeView.vue'\n\nconst router = createRouter({\n  history: createWebHistory(import.meta.env.BASE_URL),\n  routes: [\n    {\n      path: '\/',\n      name: 'home',\n      component: HomeView\n    },\n    {\n      path: '\/about',\n      name: 'about',\n      \/\/ Lazy-loaded via code-splitting\n      component: () =&gt; import('..\/views\/AboutView.vue')\n    }\n  ]\n})\n\nexport default router\n\n<\/pre>\n<p>Unsere relativ einfache Einrichtung l\u00e4dt die <code>\/<\/code> Route, die unsere <code>&lt;HomeView&gt;<\/code> l\u00e4dt, und <code>\/about<\/code> l\u00e4dt unsere <code>&lt;AboutView&gt;<\/code>. Die Komponenten werden in einem <code>&lt;router-view&gt;<\/code> in der Root-Komponente <code>&lt;App&gt;<\/code> geladen.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"0484ba96-b943-4761-b408-4602cd1f33c7\" data-enlighter-title=\"src\/App.vue\" data-enlighter-highlight=\"13\">&lt;script setup&gt;\nimport { RouterView } from 'vue-router'\nimport Nav from '.\/components\/Nav.vue'\nimport Footer from '.\/components\/Footer.vue'\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;div&gt;\n    &lt;header&gt;\n      &lt;Nav \/&gt;\n    &lt;\/header&gt;\n\n    &lt;RouterView \/&gt;\n\n    &lt;Footer \/&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Es wird daf\u00fcr gesorgt, dass <code>\/en-US\/about<\/code> die englische About-Seite anzeigt und <code>\/ar-EG\/about<\/code> die arabische About-Seite anzeigt.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"c13f5146-3908-423f-9c06-301a1c602eaf\" data-enlighter-title=\"src\/router\/index.js\" data-enlighter-highlight=\"2\">import { createRouter, createWebHistory } from 'vue-router'\nimport { defaultLocale } from '..\/i18n'\nimport HomeView from '..\/views\/HomeView.vue'\n\nconst router = createRouter({\n  history: createWebHistory(import.meta.env.BASE_URL),\n  routes: [\n    \/\/ Der Root Path leitet immer zu einer\n    \/\/ lokalisierten Route\n    {\n      path: '\/',\n      redirect: `\/${defaultLocale}`\n    },\n    \/\/ Alle Pfade unter dem Root-Verzeichnis sind lokalisiert\n    {\n      path: '\/:locale',\n      children: [\n        {\n          \/\/ Der leere Pfad gibt den Standard an\n          \/\/ Child-Routenkomponente\n          path: '',\n          component: HomeView,\n        },\n        {\n          \/\/ Das relative 'about' verwenden, nicht das absolute\n          \/\/ '\/about' erm\u00f6glicht es, das :locale einzubinden\n          \/\/ Parameter von der \u00fcbergeordneten Komponente.\n          path: 'about',\n          component: () =&gt; import('..\/views\/AboutView.vue'),\n        },\n      ],\n    },\n  ],\n})\n\nexport default router\n\n<\/pre>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Der <a href=\"https:\/\/router.vuejs.org\/guide\/\">offizielle Vue Router-Leitfaden<\/a> bietet einen tollen Einstieg, um die Grundlagen des Vue-Routings zu verstehen.<\/p>\n<p>Mit diesen \u00c4nderungen erfolgt eine Weiterleitung von <code>\/<\/code> zu <code>\/en-US<\/code>, wenn diese aufgerufen wird (angenommen, <code>en-US<\/code> ist das eingestellte Standard-Locale). <code>\/en-US<\/code> ist die lokalisierte Haupt-Route. Standardm\u00e4\u00dfig wird das <code>&lt;HomeView&gt;<\/code> in der <code>&lt;App&gt;<\/code>\u2019s <code>&lt;router-view&gt;<\/code> dargestellt. <code>\/en-US\/about<\/code> pr\u00e4sentiert das <code>&lt;AboutView&gt;<\/code>.<\/p>\n<p>Besucht man allerdings <code>\/ar-EG<\/code> oder eine andere arabische Route, werden englische \u00dcbersetzungen angezeigt. Das liegt daran, dass die aktive Locale nicht gewechselt wird, wenn sich der <code>:locale<\/code> <a href=\"https:\/\/router.vuejs.org\/guide\/essentials\/dynamic-matching.html\">Routenparameter<\/a> \u00e4ndert. Das l\u00e4sst sich mit einem <code>beforeEach<\/code> <a href=\"https:\/\/router.vuejs.org\/guide\/advanced\/navigation-guards.html\">Router-Navigationsw\u00e4chter<\/a> beheben.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-highlight=\"2,9,10,11,14,15,16,18,19\">import { createRouter, createWebHistory } from 'vue-router'\nimport i18n, { defaultLocale } from '..\/i18n'\n\/\/ ...\n\nconst router = createRouter({\n  \/\/ ...\n})\n\nrouter.beforeEach((to, from) =&gt; {\n  const newLocale = to.params.locale\n  const prevLocale = from.params.locale\n\n  \/\/ Wenn sich das Locale nicht ge\u00e4ndert hat, nichts unternehmen\n  if (newLocale === prevLocale) {\n    return\n  }\n\n  i18n.setLocale(newLocale)\n})\n\nexport default router\n\n<\/pre>\n<p>Der globale <a href=\"https:\/\/router.vuejs.org\/api\/interfaces\/router.html#beforeeach\">beforeEach()<\/a> Guard des Vue-Routers, der sehr praktisch ist, l\u00e4uft vor jeder Navigation, um sicherzustellen, dass wir es bemerken, wenn sich der Locale-Parameter in der URL \u00e4ndert. Ein anonymer Callback wird an den Guard \u00fcbergeben und die neue <code>setLocale()<\/code> Funktion wird verwendet, um die aktive Locale von Vue I18n zu aktualisieren, wenn der Locale-Parameter sich \u00e4ndert. Das bedeutet, dass bei Aufruf von <code>\/ar-EG\/about<\/code> die arabische Version der About-Seite angezeigt wird.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35736\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-localized-route.png\" alt=\"Arabische \u00dcbersetzungen werden jetzt auf unseren arabischen Routen angezeigt\" width=\"1558\" height=\"582\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-localized-route.png 1558w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-localized-route-300x112.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-localized-route-1024x383.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-localized-route-768x287.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-localized-route-1536x574.png 1536w\" sizes=\"auto, (max-width: 1558px) 100vw, 1558px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Auf unseren arabischen Routen werden jetzt \u00dcbersetzungen auf Arabisch angezeigt<\/span><\/p>\n<h3><span class=\"ez-toc-section\" id=\"so-erstellt-man-eine-wiederverwendbare-lokalisierte-link-komponente\"><\/span>So erstellt man eine wiederverwendbare lokalisierte Link-Komponente.<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Ein Problem bei unserer aktuellen lokalisierten Routing-L\u00f6sung ist, dass der <code>:locale<\/code> Routenparameter jedes Mal manuell eingef\u00fcgt werden muss, wenn ein Router-Link erstellt wird.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"844170d0-c44e-4374-8cd7-e2d54c4ab196\" data-enlighter-title=\"src\/components\/Nav.vue\" data-enlighter-highlight=\"8,12\">&lt;script setup&gt;\nimport { RouterLink } from 'vue-router'\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;nav&gt;\n    &lt;!-- ... --&gt;\n    &lt;router-link :to=\"`\/${$i18n.locale}`\"&gt;\n      {{ $t('home') }}\n    &lt;\/router-link&gt;\n\n    &lt;router-link :to=\"`\/${$i18n.locale}\/about`\"&gt;\n      {{ $t('about') }}\n    &lt;\/router-link&gt;\n    &lt;\/nav&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Das skaliert nicht besonders gut und ist fehleranf\u00e4llig. Um DRY (Don\u2019t Repeat Yourself) umzusetzen, wird Vues <code>&lt;router-link&gt;<\/code> in eine benutzerdefinierte Komponente eingebettet, die die Routenlokalisierung automatisch handhabt.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"03b5aa1d-9beb-47fb-9fb5-9bfcf22672e9\" data-enlighter-title=\"src\/components\/l10n\/LocalizedLink.vue\">&lt;script&gt;\nimport { RouterLink } from 'vue-router'\n\nexport default {\n  \/\/ Die to-Eigenschaft wird so eingestellt, dass sie relative Pfade akzeptiert,\n  \/\/ nicht-lokalisierte URIs\n  props: ['to'],\n\n  components: { RouterLink },\n\n  computed: {\n    localizedUrl() {\n      \/\/ Die Root-Route \/ ist besonders, da sie\n      \/\/ absolut\n      return this.to === '\/'\n        ? `\/${this.$i18n.locale}`\n        : `\/${this.$i18n.locale}\/${this.to}`\n    },\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;!-- Intern wird einfach Vue genutzt \n       altbew\u00e4hrter Router-Link --&gt;\n  &lt;router-link :to=\"localizedUrl\"&gt;\n    &lt;slot&gt;&lt;\/slot&gt;\n  &lt;\/router-link&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Der neue <code>&lt;LocalizedLink&gt;<\/code> ist fast ein direkter Ersatz f\u00fcr <code>&lt;router-link&gt;<\/code>s. Es muss nur darauf geachtet werden, <em>relative<\/em> URLs f\u00fcr alles au\u00dfer der Root-Route zu verwenden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-highlight=\"10,17\">&lt;script setup&gt;\nimport { RouterLink } from 'vue-router'\nimport LocalizedLink from '.\/l10n\/LocalizedLink.vue'\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;nav&gt;\n    &lt;!-- ... --&gt;\n\n    &lt;LocalizedLink to=\"\/\"&gt;\n      {{ $t('home') }}\n    &lt;\/LocalizedLink&gt;\n    &lt;!-- Wenn die aktive Sprache ar-EG ist, wird zu\n         \/ar-EG --&gt;\n\n    &lt;!-- Beachte, dass auf about und nicht auf \/about verwiesen wird --&gt;\n    &lt;LocalizedLink to=\"about\"&gt;\n      {{ $t('about') }}\n    &lt;\/LocalizedLink&gt;\n    &lt;!-- Wenn die aktive Sprache ar-EG ist, wird zu\n         \/ar-EG\/about --&gt;\n  &lt;\/nav&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"eine-sprachumschalter-oberflaeche-erstellen-%e2%80%93-so-gehts\"><\/span>Eine Sprachumschalter-Oberfl\u00e4che erstellen \u2013 so geht&#8217;s<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Um Seitenbesuchern die M\u00f6glichkeit zu geben, ihre Locales auszuw\u00e4hlen, wird eine Dropdown-Komponente f\u00fcr den Sprachwechsel erstellt, die unsere lokalisierten Routen verwendet. Zuerst werden die unterst\u00fctzten Locales unserer App in der i18n-Bibliothek konfiguriert und ver\u00f6ffentlicht.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"b6bea5d1-a8da-40fa-b9e5-03fbb26a663e\" data-enlighter-title=\"src\/i18n\/index.js\">\/\/ ...\n\n\/\/ Verwendung einer { localeCode: localeData } Struktur\n\/\/ erm\u00f6glicht es, Metadaten wie einen Namen zu jedem hinzuzuf\u00fcgen\n\/\/ zu jedem Locale hinzuf\u00fcgen, wenn die Bed\u00fcrfnisse wachsen.\nexport const supportedLocales = {\n  'en-US': { name: 'English' },\n  'ar-EG': { name: '\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabic)' },\n}\n\n\/\/ ...\n\n<\/pre>\n<p>Jetzt k\u00f6nnen unsere <code>supportedLocales<\/code> importiert und in einer neuen <code>&lt;LocaleSwitcher&gt;<\/code>-Komponente genutzt werden, die ein schlichtes <code>&lt;select&gt;<\/code> umfasst.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"fe340862-4870-4539-9da4-4b7771361dde\" data-enlighter-title=\"src\/components\/l10n\/LocaleSwitcher.vue\">&lt;script&gt;\nimport { supportedLocales } from '..\/..\/i18n'\n\nexport default {\n  methods: {\n    \/\/ Wird aufgerufen, wenn eine neue Spracheinstellung ausgew\u00e4hlt wird\n    \/\/ aus dem Dropdown-Men\u00fc\n    onLocaleChange(event_) {\n      const newLocale = event_.target.value\n\n      \/\/ Wenn die ausgew\u00e4hlte Sprache dieselbe ist wie die\n      \/\/ aktiv, dann nichts tun\n      if (newLocale === this.$i18n.locale) {\n        return\n      }\n\n      \/\/ Navigiere zur lokalisierten Stammroute f\u00fcr\n      \/\/ die gew\u00e4hlte Sprache\n      this.$router.push(`\/${newLocale}`)\n    },\n  },\n  computed: {\n    \/\/ Unser supportedLocales-Objekt wird umgewandelt in \n    \/\/ eine Liste von [{ code: 'en-US', name: 'English' }, ...]\n    locales() {\n      return Object.keys(supportedLocales).map((code) =&gt; ({\n        code,\n        name: supportedLocales[code].name,\n      }))\n    },\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;select\n    :value=\"$i18n.locale\"\n    @change=\"onLocaleChange($event)\"\n  &gt;\n    &lt;option \n      v-for=\"locale in locales\"\n      :key=\"locale.code\"\n      :value=\"locale.code\"\n    &gt;\n      {{ locale.name }}\n    &lt;\/option&gt;\n  &lt;\/select&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>\ud83e\udd3f <em>Mehr erfahren \u00bb<\/em> In unserem <code>&lt;LocaleSwitcher&gt;<\/code> wird <code>$router.push()<\/code> genutzt, um zur Startseite der gew\u00e4hlten Sprache zu navigieren. Im offiziellen Guide gibt es mehr Informationen zur <a href=\"https:\/\/router.vuejs.org\/guide\/essentials\/navigation.html\">programmgesteuerten Navigation von Vue Router<\/a>.<\/p>\n<p>Jetzt kann die neue Komponente in die Navigationsleiste der App integriert werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"a3985af2-8834-44fb-ab11-421f5414ca2a\" data-enlighter-title=\"src\/components\/Nav.vue\" data-enlighter-highlight=\"3,15\">&lt;script setup&gt;\nimport LocalizedLink from '.\/l10n\/LocalizedLink.vue'\nimport LocaleSwitcher from '.\/l10n\/LocaleSwitcher.vue'\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;div&gt;\n    &lt;nav&gt;\n      &lt;img :alt=\"$t('logo')\" src=\"@\/assets\/logo.svg\"\/&gt;\n      &lt;span class=\"font-bold text-purple-300\"&gt;{{ $t('appTitle') }}&lt;\/span&gt;\n      &lt;LocalizedLink to=\"\/\"&gt;{{ $t('home') }}&lt;\/LocalizedLink&gt;\n      &lt;LocalizedLink to=\"about\"&gt;{{ $t('about') }}&lt;\/LocalizedLink&gt;\n    &lt;\/nav&gt;\n\n    &lt;LocaleSwitcher \/&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<div><\/div>\n<div>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35743\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/locale-switcher.gif\" alt=\"Unser Sprachumschalter in Aktion\" width=\"600\" height=\"305\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Unser Sprachumschalter in Aktion<\/span><\/p>\n<\/div>\n<h3><span class=\"ez-toc-section\" id=\"direkt-an-i18nlocale-anbinden\"><\/span>Direkt an i18n.locale anbinden<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Wenn keine lokalisierten Routen verwendet werden, kann direkt an <code>$i18n.locale<\/code> wie folgt gebunden werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"41fe9fe0-4f94-49a8-9e52-33747e0a2516\" data-enlighter-title=\"src\/components\/l10n\/LocaleSwitcher.vue\" data-enlighter-highlight=\"15\">&lt;script&gt;\nimport { supportedLocales } from '..\/..\/i18n'\n\nexport default {\n  computed: {\n    locales() {\n      \/\/ ...\n    },\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;!-- Verwendung von Vues v-model f\u00fcr bidirektionale Bindung --&gt;\n  &lt;select v-model=\"$i18n.locale\"&gt;\n    &lt;option\n      v-for=\"locale in locales\"\n      :key=\"locale.code\"\n      :value=\"locale.code\"\n    &gt;\n      {{ locale.name }}\n    &lt;\/option&gt;\n  &lt;\/select&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>\ud83e\udd3f <em>Mehr erfahren \u00bb<\/em> In der Dokumentation von Vue I18n kann mehr \u00fcber <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/scope.html#locale-changing\">das \u00c4ndern der Locale<\/a> erfahren werden.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"uebersetzungsdateien-asynchron-laden-%e2%80%93-so-gehts\"><\/span>\u00dcbersetzungsdateien asynchron laden \u2013 so geht&#8217;s<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Mit dem Wachstum unserer Apps und der Hinzuf\u00fcgung weiterer unterst\u00fctzter Lokalisierungen besteht das Risiko, dass unser Hauptpaket mit all unseren \u00dcbersetzungsnachrichten aufgebl\u00e4ht wird. Realistisch gesehen werden nur Nachrichten f\u00fcr die gew\u00e4hlte Locale des aktuellen Besuchers ben\u00f6tigt. Das Hauptpaket kann schlanker gemacht werden, indem nur die Nachrichten der aktiven Locale bei Bedarf heruntergeladen werden.<\/p>\n<p>Das asynchrone Laden von \u00dcbersetzungen wird in die Demo-App eingef\u00fcgt. Zuerst wird die <code>messages.js<\/code>-Datei in JSON-Dateien pro Sprache aufgeteilt.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"6adcdd2b-5adc-4450-90e0-07d43db45935\" data-enlighter-title=\"src\/translations\/en-US.json\">{\n  \"appTitle\": \"Mushahed\",\n  \"home\": \"Home\",\n  \"about\": \"About\",\n  \/\/ ...\n}\n\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"b3ab72c4-53ed-4fd4-9993-b9088708df09\" data-enlighter-title=\"src\/translations\/ar-EG.json\">{\n  \"appTitle\": \"\u0645\u0634\u0627\u0647\u062f\",\n  \"home\": \"\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629\",\n  \"about\": \"\u00dcber uns\",\n  \/\/ ...\n}\n\n<\/pre>\n<p>N\u00e4chster Schritt ist das Hinzuf\u00fcgen einer Ladefunktion zur i18n-Bibliothek.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"43afe9a1-2199-4230-8952-9f2c9f3ee3d3\" data-enlighter-title=\"src\/i18n\/index.js\" data-enlighter-highlight=\"1,9,10,11,12,14,16,17,25\">import { nextTick } from 'vue'\n\n\/\/ ...\n\nlet _i18n\n\n\/\/ ...\n\nasync function loadMessagesFor(locale) {\n  const messages = await import(\n    \/* webpackChunkName: \"locale-[request]\" *\/ `..\/translations\/${locale}.json`\n  )\n\n  _i18n.global.setLocaleMessage(locale, messages.default)\n\n  return nextTick()\n}\n\nexport default {\n  get vueI18n() {\n    return _i18n\n  },\n  setup,\n  setLocale,\n  loadMessagesFor,\n}\n\n<\/pre>\n<p><code>loadMessagesFor()<\/code> nutzt die asynchrone <a href=\"https:\/\/v4.webpack.js.org\/guides\/code-splitting\/#dynamic-imports\">Code-Splitting und dynamische Importe<\/a> von Webpack, um die \u00dcbersetzungsdatei f\u00fcr die angegebene Locale asynchron zu laden. Sobald die \u00dcbersetzungsdatei geladen ist, werden die Nachrichten der Datei an Vue I18n \u00fcbergeben und mit der angegebenen Sprache verkn\u00fcpft. Um sicherzustellen, dass Vue das DOM bereits aktualisiert hat, bevor die Aufl\u00f6sung erfolgt, wird das Promise von <a href=\"https:\/\/vuejs.org\/api\/general.html#nexttick\">nextTick()<\/a> zur\u00fcckgegeben.<\/p>\n<p>Nun kann die <code>beforeEach()<\/code> Navigationsschutzfunktion im Router aktualisiert werden, um die Nachrichten der Spracheinstellung zu laden, bevor die zugeh\u00f6rige Komponente einer Route gerendert wird.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-highlight=\"11,21\">import { createRouter, createWebHistory } from 'vue-router'\nimport i18n, { defaultLocale } from '..\/i18n'\n\n\/\/ ...\n\nconst router = createRouter({\n  \/\/ ...\n})\n\n\/\/ Die Callback-Funktion wird asynchron gemacht...\nrouter.beforeEach(async (to, from) =&gt; {\n  const newLocale = to.params.locale\n  const prevLocale = from.params.locale\n\n  if (newLocale === prevLocale) {\n    return\n  }\n\n  \/\/ ...damit auf das Laden der Nachrichten gewartet werden kann,\n  \/\/ bevor es weitergeht\n  await i18n.loadMessagesFor(newLocale)\n\n  i18n.setLocale(newLocale)\n})\n\nexport default router\n\n<\/pre>\n<p>Wenn die App jetzt neu geladen wird, sollten keine gro\u00dfen \u00c4nderungen sichtbar sein. Wenn der Netzwerk-Tab in den Entwicklerwerkzeugen des Browsers ge\u00f6ffnet wird, sollte zu sehen sein, dass eine Nachrichten-JSON-Datei geladen wird, wenn die Spracheinstellungen ge\u00e4ndert werden.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35748\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/aync-loading-network-tab.png\" alt=\"Mit Webpacks Code-Splitting werden die \u00dcbersetzungen einer Spracheinstellung asynchron geladen\" width=\"1816\" height=\"138\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/aync-loading-network-tab.png 1816w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/aync-loading-network-tab-300x23.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/aync-loading-network-tab-1024x78.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/aync-loading-network-tab-768x58.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/aync-loading-network-tab-1536x117.png 1536w\" sizes=\"auto, (max-width: 1816px) 100vw, 1816px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Mit Webpacks Code-Splitting werden die \u00dcbersetzungen einer Spracheinstellung asynchron geladen<\/span><\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Mehr zum asynchronen\/faulen Laden in Vue I18ns <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/advanced\/lazy.html\">Leitfaden zum Lazy Loading<\/a> erfahren.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"arbeiten-mit-spracheinstellungs-fallbacks-%e2%80%93-so-gehts\"><\/span>Arbeiten mit Spracheinstellungs-Fallbacks \u2013 so geht&#8217;s<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>M\u00f6glicherweise wurden einige Konsolenwarnungen angezeigt, nachdem das asynchrone Laden von \u00dcbersetzungen oben implementiert wurde. Die Warnungen treten auf, wenn eine <code>en-US<\/code> Route zum ersten Mal aufgerufen wird.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35753\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/fallback-console-warnings.png\" alt=\"Vue I18n versucht, auf eine allgemeinere Locale zur\u00fcckzugreifen, wenn keine \u00dcbersetzungsnachricht gefunden wird\" width=\"1178\" height=\"158\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/fallback-console-warnings.png 1178w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/fallback-console-warnings-300x40.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/fallback-console-warnings-1024x137.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/fallback-console-warnings-768x103.png 768w\" sizes=\"auto, (max-width: 1178px) 100vw, 1178px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Vue I18n versucht, auf eine allgemeinere Locale zur\u00fcckzugreifen, wenn keine \u00dcbersetzungsnachricht gefunden wird<\/span><\/p>\n<p>Es passiert Folgendes: Vue I18n kann keine <code>en-US<\/code> Nachricht finden, wenn die App zum ersten Mal geladen wird. Die <code>en-US<\/code> Nachrichten werden in einer separaten HTTP-Anfrage vom Hauptbundle geladen, sodass sie m\u00f6glicherweise nicht verf\u00fcgbar sind, wenn die App zum ersten Mal geladen wird. Das wird in K\u00fcrze angesprochen.<\/p>\n<p>Es sollte jedoch beachtet werden, dass Vue I18n versucht, die <code>logo<\/code> Nachricht in einer allgemeinen <code>en<\/code> Spracheinstellung zu finden, wenn sie in der regionsspezifischen <code>en-US<\/code> Spracheinstellung nicht auffindbar ist. Das ist das Standard-Fallback-Verhalten der Bibliothek. Das kann sehr n\u00fctzlich sein, wenn \u00dcbersetzungen f\u00fcr eine unserer Sprachen fehlen.<\/p>\n<p>\ud83e\udd3f <em>Mehr erfahren \u00bb<\/em> Schau dir alle Optionen an, die Vue I18n f\u00fcr den Fallback im <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/fallback.html#fallbacking\">Fallback-Guide<\/a> bietet.<\/p>\n<p>Als allgemeine Auffangoption steht eine <code>fallbackLocale<\/code> Konfigurationsoption zur Verf\u00fcgung: Falls keine andere Nachricht gefunden wird, werden alle unter <code>fallbackLocale<\/code> aufgef\u00fchrten Locales zur Anzeige verwendet. Diese Option stellt sicher, dass in der App auf Englisch zur\u00fcckgegriffen wird.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"04d3ea19-3125-40a3-b6a1-5a021092308d\" data-enlighter-title=\"src\/i18n\/index.js\" data-enlighter-highlight=\"6,15,16\">\/\/ ...\nimport { createI18n } from 'vue-i18n'\nimport { numberFormats } from '.\/numbers'\nimport { arabicPluralRules } from '.\/plurals'\nimport { datetimeFormats } from '.\/datetimes'\nimport defaultMessages from '..\/translations\/en-US.json'\n\nexport const defaultLocale = 'en-US'\n\nlet _i18n\n\nfunction setup(options = { locale: defaultLocale }) {\n  _i18n = createI18n({\n    locale: options.locale,\n    fallbackLocale: defaultLocale,\n    messages: { [defaultLocale]: defaultMessages },\n    numberFormats,\n    datetimeFormats,\n    pluralizationRules: {\n      'ar-EG': arabicPluralRules,\n    },\n  })\n\n  setLocale(options.locale)\n\n  return _i18n\n}\n\n\/\/ ...\n\n<\/pre>\n<p>Unsere <code>en-US<\/code> \u00dcbersetzungsnachrichten werden <code>importiert<\/code> und in die <code>messages<\/code> Option von Vue I18n \u00fcbergeben. Dadurch werden unsere englischen Nachrichten im Hauptpaket enthalten sein, sodass die App nicht darauf warten muss, dass sie asynchron geladen werden. Wenn <code>en-US<\/code> als <code>fallbackLocale<\/code> eingestellt ist, wird die entsprechende englische Nachricht angezeigt, falls eine Nachricht in einer anderen Sprache fehlt.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35758\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-fallback-to-en.png\" alt=\"Die englische Nachricht f\u00fcr &quot;home&quot; wird anstelle der fehlenden arabischen Nachricht verwendet\" width=\"564\" height=\"138\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-fallback-to-en.png 564w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-fallback-to-en-300x73.png 300w\" sizes=\"auto, (max-width: 564px) 100vw, 564px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Die englische Nachricht f\u00fcr &#8222;home&#8220; wird anstelle der fehlenden arabischen Nachricht verwendet<\/span><\/p>\n<div><\/div>\n<div>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35763\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-fallback-console-warnings.png\" alt=\"Praktische Konsolenwarnungen offenbaren die Fallback-Kette von Vue I18n\" width=\"1194\" height=\"220\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-fallback-console-warnings.png 1194w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-fallback-console-warnings-300x55.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-fallback-console-warnings-1024x189.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/ar-fallback-console-warnings-768x142.png 768w\" sizes=\"auto, (max-width: 1194px) 100vw, 1194px\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Praktische Konsolenwarnungen offenbaren die Fallback-Kette von Vue I18n<\/span><\/p>\n<\/div>\n<p>Mit dieser Einstellung ist die kleine Demo-App internationalisiert.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35768\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/options-complete.gif\" alt=\"Die internationalisierte Demo-App\" width=\"600\" height=\"492\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Die internationalisierte Demo-App<\/span><\/p>\n<p>\ud83d\udd17 <em>Ressource<\/em> Der gesamte Code der internationalisierten Options-API-Demo-App, die oben erstellt wurde, kann von GitHub abgerufen werden. Der Democode enth\u00e4lt einige Funktionen, die in diesem Artikel keinen Platz gefunden haben, wie das Lauschen auf Locale-\u00c4nderungen, um Daten neu zu laden, und die Unterst\u00fctzung f\u00fcr Sprachen von rechts nach links.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"eine-vue-app-mit-der-composition-api-lokalisieren-%e2%80%93-so-gehts\"><\/span>Eine Vue-App mit der Composition API lokalisieren \u2013 so geht\u2019s<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Alles, was bisher in diesem Artikel behandelt wurde, bezieht sich auf Vues objektorientierte <a href=\"https:\/\/vuejs.org\/guide\/introduction.html#api-styles\">Options API<\/a>. Wenn die App die Composition API in Vue 3 verwendet, ist man in diesem Abschnitt gut aufgehoben.<\/p>\n<p>\u270b <em>Achtung \u00bb<\/em> Vue I18n ist darauf ausgelegt, mit <em>entweder<\/em> der Options-API oder der Composition-API zu arbeiten, <em>aber nicht mit beiden gleichzeitig<\/em>. Die Vue I18n Options API ist die Standard-API und wird <em>Legacy API<\/em> genannt. Der <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/migration\/vue3.html#migration-to-composition-api-from-legacy-api\">Leitfaden zur Migration von der Legacy API zur Composition API<\/a> enth\u00e4lt Informationen zu Einschr\u00e4nkungen und Vorbehalten.<\/p>\n<p>Bevor der I18n-Code umstrukturiert wird, wird kurz gezeigt, wie die Vue-Komponenten (ohne I18n) von der Options API zur Composition API umstrukturiert werden.<\/p>\n<p>\ud83d\uddd2\ufe0f <em>Hinweis \u00bb<\/em> Die folgenden Abschnitte bauen auf dem auf, was bereits in diesem Artikel behandelt wurde. Wer neu bei Vue I18n ist, sollte den Rest des Artikels lesen, bevor weitergemacht wird.<\/p>\n<p>In unserer Demo m\u00fcssen nur drei Dateien f\u00fcr die Composition umstrukturiert werden: <code>AstroCard.vue<\/code>, <code>Astronauts.vue<\/code> und <code>Coords.vue<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"4413e045-ce28-4dc3-95c1-1bbadddb9974\" data-enlighter-title=\"src\/components\/AstroCard.vue\">&lt;-- Syntaktischen Zucker f\u00fcr das Skript-Setup bei Einzeldateikomponenten verwenden --&gt;\n&lt;script setup&gt;\n\n\/\/ Funktionen aus Vue importieren\nimport { computed } from 'vue'\n\n\/\/ Mit einem Makro `props` definieren \nconst props = defineProps({\n  name: String,\n  photoUrl: String,\n  nationality: String,\n  craft: String,\n})\n\n\/\/ Berechnete Eigenschaft mit computed() erstellen\n\/\/ und die Prop-Referenz refaktorisieren, um `props.X` zu verwenden\nconst fullPhotoUrl = computed(() =&gt; `\/img\/astros\/${props.photoUrl}`)\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;!-- Im Template \u00e4ndert sich nichts --&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"cb1897b1-720e-4c01-a14e-4a4b0a02a239\" data-enlighter-title=\"src\/components\/Astronauts.vue\">&lt;script setup&gt;\nimport { ref } from 'vue'\nimport AstroCard from '.\/AstroCard.vue'\n\n\/\/ ref verwenden, um reaktive Daten zu definieren\nconst loading = ref(true)\nconst astros = ref([])\n\n\/\/ Logik, die in created() ausgef\u00fchrt wird\n\/\/ direkt auf der obersten Ebene geschrieben\nfetch('\/data\/astronauts.json')\n  .then((res) =&gt; res.json())\n  .then((data) =&gt; {\n    \/\/ Nicht vergessen, .value zu verwenden, wenn Werte abgerufen oder eingestellt werden\n    \/\/ Werte, die mit ref() festgelegt sind\n    astros.value = data\n    loading.value = false\n  })\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;!-- Im Template \u00e4ndert sich nichts --&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Hier machen wir eine Pause, um es kurz zu halten und zum i18n zu kommen. <a href=\"https:\/\/github.com\/PhraseApp-Blog\/vue3-i18n-2022\/compare\/start-options...start-composition\">Die Unterschiede beim Refactoring der Composition API<\/a> (vor i18n) sind in unserem GitHub-Repo einsehbar.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"refactoring-von-i18n-unter-verwendung-der-composition-api\"><\/span>Refactoring von i18n unter Verwendung der Composition API<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Es gibt nicht <em>allzu<\/em> viel zu \u00e4ndern, wenn die Composition API von Vue I18n verwendet werden soll. Folgendes wird behandelt:<\/p>\n<ul>\n<li>Beim Erstellen der VueI18n-Instanz <code>legacy: false<\/code> setzen.<\/li>\n<li>Refactoring von <code>vueI18n.global.locale<\/code> zu <code>vueI18n.global.locale.value<\/code>.<\/li>\n<li>Refactoring von <code>tc()<\/code> Aufrufen zu <code>t()<\/code> f\u00fcr Pluralformen.<\/li>\n<li>Refactoring aller <code>this.X<\/code>-Aufrufe zu ihren funktionalen Entsprechungen in unseren Komponenten-<code>&lt;script&gt;<\/code>s.<\/li>\n<\/ul>\n<p>Also, legen wir los.<\/p>\n<h4>Den Legacy-Modus ausschalten<\/h4>\n<p>Standardm\u00e4\u00dfig befindet sich Vue I18n im &#8222;Legacy&#8220;-Modus, wobei <code>createI18n()<\/code> eine <code>VueI8n<\/code>-Objektinstanz zur\u00fcckgibt. Es soll eine <a href=\"https:\/\/vue-i18n.intlify.dev\/api\/composition.html#composer\">Composer<\/a> Instanz erstellt werden, die Funktionen wie <code>t()<\/code> und <code>n()<\/code> f\u00fcr die Kompositionskomponente bereitstellt.<\/p>\n<p>Um dies zu erreichen, muss lediglich eine Option an <code>createI18n()<\/code> \u00fcbergeben und <code>legacy: false<\/code> eingestellt werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"2db6da9b-2001-4a30-a938-0151f3d81556\" data-enlighter-title=\"src\/i18n\/index.js\" data-enlighter-highlight=\"12\">\/\/ ...\nimport { createI18n } from 'vue-i18n'\n\nexport const defaultLocale = 'en-US'\n\n\/\/ ...\n\nlet _i18n\n\nfunction setup(options = { locale: defaultLocale }) {\n  _i18n = createI18n({\n    legacy: false,\n    \/\/ Sonst \u00e4ndert sich nichts an unseren Optionen\n  })\n\n  setLocale(options.locale)\n\n  return _i18n\n}\n\n\/\/ ... \n\n<\/pre>\n<h4>Reaktive Eigenschaften nutzen<\/h4>\n<p>Sobald mit <code>legacy: false<\/code> die Composition genutzt wird, ist eine Umstellung der Aufrufe an Vue I18n\u2019s <code>locale<\/code> notwendig, da <code>locale<\/code> nun wie ein <a href=\"https:\/\/vuejs.org\/guide\/essentials\/reactivity-fundamentals.html#reactive-variables-with-ref\">reaktives Ref<\/a> agiert.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"fb01cc97-f5b8-41fc-9ba5-908adb949795\" data-enlighter-title=\"src\/i18n\/index.js\" data-enlighter-highlight=\"6\">\/\/ ...\n\nfunction setLocale(newLocale) {\n  \/\/ Genau wie bei jedem reaktiven Vue-Ref, haben wir\n  \/\/ es mit .value zu setzen\/abzurufen\n  _i18n.global.locale.value = newLocale\n  setDocumentAttributesFor(newLocale)\n}\n\n\/\/ ...\n\n<\/pre>\n<p>Das ist alles, was wir in unserer i18n-Bibliothek \u00e4ndern m\u00fcssen. Die restlichen Updates werden in unseren Komponenten vorgenommen.<\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Im <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/advanced\/composition.html\">Composition API<\/a> Guide gibt es weitere Infos.<\/p>\n<h4>t() statt tc() f\u00fcr Nachrichten im Plural verwenden<\/h4>\n<p>Es gibt keine <code>tc()<\/code> Funktion in der Composer-Instanz von Vue I18n zum Ausgeben von Nachrichten im Plural; ein Wechsel zur Composition API f\u00fchrt dazu, dass Vue I18n einen Fehler ausgibt, sobald der Versuch unternommen wird, <code>tc()<\/code> zu nutzen. Als direkter Ersatz kann einfach die normale <code>t()<\/code> Funktion genutzt werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"bc6046ca-e556-46f8-8bc9-1d8aa5938011\" data-enlighter-title=\"src\/components\/Astronauts.vue\" data-enlighter-highlight=\"12\">&lt;script setup&gt;\n  \/\/ ...\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;!-- ... --&gt;\n\n  &lt;div&gt;\n    &lt;div&gt;\n      &lt;h2&gt;\n      &lt;!-- $t() anstelle von $tc() verwenden: funktioniert genau gleich --&gt;\n        \ud83e\uddd1\u200d\ud83d\ude80 {{ $t('peopleInSpace', astros.length) }}\n      &lt;\/h2&gt;\n\n      &lt;p&gt;\n        {{ $t('updatedAt', { date: $d(updated, 'short') }) }}\n      &lt;\/p&gt;\n    &lt;\/div&gt;\n\n    &lt;!-- ... --&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>\ud83d\uddd2\ufe0f <em>Hinweis \u00bb<\/em> <code>$t()<\/code>, <code>$d()<\/code> und <code>$n()<\/code> arbeiten in <code>&lt;template&gt;<\/code>-Komponenten sowohl im Legacy- als auch im Composition-Modus. Das liegt daran, dass Vue I18n sie standardm\u00e4\u00dfig <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/advanced\/composition.html#implicit-with-injected-properties-and-functions\">global<\/a> in beiden Modi einf\u00fcgt. Das ist <em>nicht<\/em> der Fall bei Komponenten <code>&lt;scripts&gt;<\/code>, wo <code>$t()<\/code>, <code>$d()<\/code> usw. im Composition-Modus nicht verf\u00fcgbar sind. Damit wird sich als N\u00e4chstes besch\u00e4ftigt.<\/p>\n<h4>Verwendung von Lokalisierungsfunktionen in Komponentenskripten<\/h4>\n<p>In der <code>Coords<\/code>-Komponente werden <code>this.$t()<\/code>, <code>this.$d()<\/code> und <code>this.$n()<\/code> zur Abfrage von \u00dcbersetzungsnachrichten sowie lokalisierten Daten und Zahlen verwendet.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"3993da28-5b01-454b-b85f-a5e87b806c3b\" data-enlighter-title=\"src\/components\/Coords.vue\" data-enlighter-highlight=\"19,20,21,22,23\">&lt;script&gt;\nexport default {\n  data() {\n    return {\n      loading: true,\n      coords: null,\n      datetime: '',\n    }\n  },\n\n  created() {\n    \/\/ Hier holen wir Koordinatendaten aus dem Netzwerk...\n  },\n\n  computed: {\n    issPosition() {\n      const { latitude, longitude } = this.coords\n     \n      return this.$t('issPosition', {\n        latitude: this.$n(latitude, 'coords'),\n        longitude: this.$n(longitude, 'coords'),\n        datetime: this.$d(this.datetime, 'full'),\n      })\n    },\n  },\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;!-- Koordinatendaten anzeigen --&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>Wenn zur Composition API gewechselt wird, zeigt <code>this<\/code> nicht mehr auf die Komponenteninstanz, daher k\u00f6nnen <code>this.$t()<\/code> und \u00e4hnliche Funktionen nicht mehr verwendet werden. Vue I18n stellt eine <code>useI18n()<\/code>-Funktion bereit, die die Composer-Instanz zur\u00fcckgibt, welche <code>t()<\/code> und Co. enth\u00e4lt. Schauen wir uns das mal in Aktion an.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"42e79f8b-0396-4169-bd30-0e50c67456da\" data-enlighter-title=\"src\/components\/Coords.vue\" data-enlighter-highlight=\"3,6,18,19,20,21,22\">&lt;script setup&gt;\nimport { ref, computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\n\/\/ Funktionen aus der zur\u00fcckgegebenen Composer-Instanz entnehmen\nconst { t, n, d } = useI18n()\n\nconst loading = ref(true)\nconst coords = ref(null)\nconst datetime = ref('')\n\n\/\/ Hier holen wir Koordinatendaten aus dem Netzwerk...\n\nconst issPosition = computed(() =&gt; {\n  const { latitude, longitude } = coords.value\n\n  \/\/ Funktionen ohne `this` verwenden\n  return t('issPosition', {\n    latitude: n(latitude, 'coords'),\n    longitude: n(longitude, 'coords'),\n    datetime: d(datetime.value, 'full'),\n  })\n})\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;!-- ... --&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>\u270b <em>Achtung \u00bb<\/em> Das $-Pr\u00e4fix sollte nicht mit den funktionalen Varianten in <code>&lt;script&gt;&lt;2&gt;-Komponenten verwendet werden.<\/code><\/p>\n<h4>Die reaktive <code>locale<\/code>-Eigenschaft wird verwendet<\/h4>\n<p>Genau wie <code>this.$t()<\/code> \u00fcberarbeitet werden musste, so muss auch <code>this.$i18n.locale<\/code> angepasst werden. Die aktive <code>Locale<\/code> kann durch Destrukturierung aus der Composer-Instanz in den Komponenten abgerufen und festgelegt werden. Die <code>LocaleSwitcher<\/code>&#8211; und <code>LocalizedLink<\/code>-Komponenten werden \u00fcberarbeitet, um die reaktive <code>locale<\/code>-Eigenschaft zu nutzen.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"75f0ca44-b299-4027-bdbc-2467415880dd\" data-enlighter-title=\"src\/components\/l10n\/LocaleSwitcher.vue\" data-enlighter-highlight=\"3,8,15,34\">&lt;script setup&gt;\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { useRouter } from 'vue-router'\nimport { supportedLocales } from '..\/..\/i18n'\n\nconst router = useRouter()\nconst { locale } = useI18n()\n\nfunction onLocaleChange(event_) {\n  const newLocale = event_.target.value\n\n  \/\/ Genau wie bei anderen reaktiven Referenzen, muss \n  \/\/ locale.value verwenden, um die aktive Sprache zu holen\/setzen\n  if (newLocale === locale.value) {\n    return\n  }\n\n  router.push(`\/${newLocale}`)\n}\n\nconst locales = computed(() =&gt;\n  Object.keys(supportedLocales).map((code) =&gt; ({\n    code,\n    name: supportedLocales[code].name,\n  }))\n)\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;!-- Beachten, dass $i18n.locale immer noch verf\u00fcgbar ist in\n       den Komponenten-Templates --&gt;\n  &lt;select\n    :value=\"$i18n.locale\"\n    @change=\"onLocaleChange($event)\"\n  &gt;\n    &lt;option v-for=\"locale in locales\" :key=\"locale.code\" :value=\"locale.code\"&gt;\n      {{ locale.name }}\n    &lt;\/option&gt;\n  &lt;\/select&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"7c490ef2-f10f-4ad4-bf85-10f7ef1e325a\" data-enlighter-title=\"src\/components\/l10n\/LocalizedLink.vue\" data-enlighter-highlight=\"4,8,11\">&lt;script setup&gt;\nimport { computed } from 'vue'\nimport { RouterLink } from 'vue-router'\nimport { useI18n } from 'vue-i18n'\n\nconst props = defineProps(['to'])\n\nconst { locale } = useI18n()\n\nconst localizedUrl = computed(() =&gt;\n  props.to === '\/' ? `\/${locale.value}` : `\/${locale.value}\/${props.to}`\n)\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;router-link :to=\"localizedUrl\"&gt;\n    &lt;slot&gt;&lt;\/slot&gt;\n  &lt;\/router-link&gt;\n&lt;\/template&gt;\n\n<\/pre>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Es werden weitere Eigenschaften von der Composer-Instanz angeboten: Eine vollst\u00e4ndige Liste ist in der <a href=\"https:\/\/vue-i18n.intlify.dev\/api\/composition.html\">API-Dokumentation<\/a> zu finden.<\/p>\n<p>Und damit ist unser Refactoring abgeschlossen.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35768\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/11\/options-complete.gif\" alt=\"Die internationalisierte Demo-App\" width=\"600\" height=\"492\" \/><\/p>\n<p><span style=\"text-align: center; font-style: italic; font-size: 80%;\">Unsere Demo funktioniert genauso wie im Legacy-Modus<\/span><\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Der <a href=\"https:\/\/github.com\/PhraseApp-Blog\/vue3-i18n-2022\/tree\/i18n-composition\">gesamte Code f\u00fcr die Composition i18n-Demo<\/a> ist auf GitHub erh\u00e4ltlich.<\/p>\n<p>\ud83d\udd17 <em>Ressource \u00bb<\/em> Bei Interesse an allgemeinem JavaScript i18n, einschlie\u00dflich anderer UI- und i18n-Bibliotheken, k\u00f6nnte unser <a href=\"https:\/\/phrase.com\/de\/blog\/posts\/step-step-guide-javascript-localization\/\">Ultimativer Leitfaden zur JavaScript-Lokalisierung<\/a> interessant sein.<\/p>\n<p>Damit ist unsere Vue 3 i18n-Demo abgeschlossen. Wir hoffen, dass es dir gefallen hat und du ein paar Dinge auf dem Weg gelernt hast. Wenn du i18n auf das n\u00e4chste Level heben willst, dann schau dich einmal bei <a href=\"https:\/\/phrase.com\/de\/\">Phrase<\/a> um. Phrase unterst\u00fctzt Vue I18n direkt mit dem In-Context Editor, wodurch \u00dcbersetzer:innen Nachrichten direkt in der App aktualisieren k\u00f6nnen. Die voll ausgestattete Phrase-Webkonsole mit maschinellem Lernen und intelligenten Vorschl\u00e4gen ist eine gro\u00dfe Hilfe f\u00fcr alle \u00dcbersertzenden. Sobald die \u00dcbersetzungen fertig sind, k\u00f6nnen sie automatisch mit dem Projekt synchronisiert werden \u2013 Phrase kommt mit einer CLI und synchronisiert mit Bitbucket, GitHub und GitLab. Einmal eingestellt, k\u00f6nnen \u00dcbersetzungen vergessen werden, sodass du dich ganz auf den geliebten Code konzentrieren kannst. Wirf einen Blick auf <a href=\"https:\/\/phrase.com\/de\/roles\/developers\/\">alle Funktionen<\/a>, die Phrase zu bieten hat, und <a href=\"https:\/\/eu.phrase.com\/idm-ui\/signup\">probiere es aus<\/a> \u2013 mit einer 14-t\u00e4gigen kostenlosen Testversion.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In diesem Leitfaden zur Vue-Lokalisierung erl\u00e4utern wir, wie die Vue I18n Bibliothek in die App integriert werden kann, um sie f\u00fcr Nutzer weltweit zug\u00e4nglich zu machen.<\/p>\n","protected":false},"author":41,"featured_media":35846,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"post-refresh-updated","format":"standard","meta":{"_acf_changed":false,"_stopmodifiedupdate":false,"_modified_date":"","_searchwp_excluded":"","episode_type":"","audio_file":"","podmotor_file_id":"","podmotor_episode_id":"","cover_image":"","cover_image_id":"","duration":"","filesize":"","filesize_raw":"","date_recorded":"","explicit":"","block":"","itunes_episode_number":"","itunes_title":"","itunes_season_number":"","itunes_episode_type":"","footnotes":""},"categories":[46],"class_list":["post-109686","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-lokalisierung"],"acf":[],"_links":{"self":[{"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/posts\/109686","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/comments?post=109686"}],"version-history":[{"count":7,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/posts\/109686\/revisions"}],"predecessor-version":[{"id":117643,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/posts\/109686\/revisions\/117643"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/media\/35846"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/media?parent=109686"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/de\/wp-json\/wp\/v2\/categories?post=109686"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}