{"id":84321,"date":"2024-06-10T10:59:00","date_gmt":"2024-06-10T08:59:00","guid":{"rendered":"https:\/\/phrase.com\/?p=84321"},"modified":"2024-06-07T16:00:29","modified_gmt":"2024-06-07T14:00:29","slug":"nestjs-localization","status":"publish","type":"post","link":"https:\/\/phrase.com\/blog\/posts\/nestjs-localization\/","title":{"rendered":"A Guide to NestJS Localization"},"content":{"rendered":"\n<div id=\"acf\/text-block_8ca1be44106b4c906f181b06c7cf7adf\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<\/h2>\n<p>A fast and loose framework like Express is great for small server-side apps, but as projects and teams scale, clean architecture and patterns are needed to keep things <a href=\"https:\/\/en.wikipedia.org\/wiki\/SOLID\">SOLID<\/a>, making it easier to find and change code. Enter the Angular-inspired, architecture-focused <a href=\"https:\/\/nestjs.com\/\">NestJS<\/a>, which sees downloads of <a href=\"https:\/\/www.npmjs.com\/package\/@nestjs\/common\">3 million per week<\/a>. The thoughtful structure of NestJS facilitates our projects\u2019 growth while minimizing technical debt.<\/p>\n<p>While NestJS is a comprehensive framework, it leaves internationalization (i18n) up to the developer. Fortunately, <a href=\"https:\/\/github.com\/toonvanstrijp\">Toon van Strijp<\/a> created <a href=\"https:\/\/nestjs-i18n.com\/\">nestjs-i18n<\/a>, a library that greatly simplifies NestJS localization. <code>nestjs-i18n<\/code> resolves the current request\u2019s language, manages translation files, and formats messages, all with built-in type safety. In this guide we\u2019ll take <code>nestjs-i18n<\/code> for a spin, building a REST API with NestJS and localizing it. Let\u2019s dive in.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Internationalization (i18n) and localization (l10n) allow us to make our apps available in different languages and regions, often for more profit. If you\u2019re new to i18n and l10n, check out our guide to <a href=\"https:\/\/phrase.com\/blog\/posts\/i18n-a-simple-definition\/\">internationalization<\/a>.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0If you\u2019re interested in general Node.js or Express i18n, check out our <a href=\"https:\/\/phrase.com\/blog\/posts\/node-js-i18n-guide\/\">Guide to Node.js Internationalization (I18n)<\/a>.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_69_1 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\/blog\/posts\/nestjs-localization\/#our-demo\" title=\"Our demo\">Our demo<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/phrase.com\/blog\/posts\/nestjs-localization\/#package-versions\" title=\"Package versions\">Package versions<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/phrase.com\/blog\/posts\/nestjs-localization\/#the-info-route\" title=\"The info route\">The info route<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/phrase.com\/blog\/posts\/nestjs-localization\/#posts-and-the-database\" title=\"Posts and the database\">Posts and the database<\/a><\/li><\/ul><\/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\/blog\/posts\/nestjs-localization\/#how-do-i-localize-my-nest-app\" title=\"How do I localize my Nest app?\">How do I localize my Nest app?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/phrase.com\/blog\/posts\/nestjs-localization\/#a-note-on-locales\" title=\"A note on locales\">A note on locales<\/a><\/li><\/ul><\/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\/blog\/posts\/nestjs-localization\/#how-do-i-set-up-nestjs-i18n\" title=\"How do I set up nestjs-i18n?\">How do I set up nestjs-i18n?<\/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\/blog\/posts\/nestjs-localization\/#how-do-i-localize-controllers-and-services\" title=\"How do I localize controllers and services?\">How do I localize controllers and services?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/phrase.com\/blog\/posts\/nestjs-localization\/#a-wrapper-i18n-service\" title=\"A wrapper i18n service\">A wrapper i18n service<\/a><\/li><\/ul><\/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\/blog\/posts\/nestjs-localization\/#how-do-i-load-translation-files\" title=\"How do I load translation files?\">How do I load translation files?<\/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\/blog\/posts\/nestjs-localization\/#how-do-i-resolve-the-active-locale\" title=\"How do I resolve the active locale?\">How do I resolve the active locale?<\/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\/blog\/posts\/nestjs-localization\/#how-do-i-redirect-while-keeping-the-active-locale\" title=\"How do I redirect while keeping the active locale?\">How do I redirect while keeping the active locale?<\/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\/blog\/posts\/nestjs-localization\/#how-do-i-make-my-message-keys-type-safe\" title=\"How do I make my message keys type-safe?\">How do I make my message keys type-safe?<\/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\/blog\/posts\/nestjs-localization\/#how-do-i-translate-basic-text\" title=\"How do I translate basic text?\">How do I translate basic text?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/phrase.com\/blog\/posts\/nestjs-localization\/#how-do-i-include-dynamic-values-in-translations\" title=\"How do I include dynamic values in translations?\">How do I include dynamic values in translations?<\/a><\/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\/blog\/posts\/nestjs-localization\/#how-do-i-work-with-localized-plurals\" title=\"How do I work with localized plurals?\">How do I work with localized plurals?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/phrase.com\/blog\/posts\/nestjs-localization\/#how-do-i-localize-the-database\" title=\"How do I localize the database?\">How do I localize the database?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/phrase.com\/blog\/posts\/nestjs-localization\/#localized-columns-on-the-main-model\" title=\"Localized columns on the main model\">Localized columns on the main model<\/a><\/li><\/ul><\/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\/blog\/posts\/nestjs-localization\/#how-do-i-localize-dto-validation-messages\" title=\"How do I localize DTO validation messages?\">How do I localize DTO validation messages?<\/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\/blog\/posts\/nestjs-localization\/#upgrade-your-nest-localization\" title=\"Upgrade your Nest localization\">Upgrade your Nest localization<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"our-demo\"><\/span>Our demo<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We\u2019ll build a fictitious jogging blog called <strong>Yogger Chicken<\/strong>, implementing it as a REST API with the following endpoints.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">GET    \/          # Root, redirects to \/info\nGET    \/info      # Lists available endpoints\nGET    \/today     # Shows a daily quote\nPOST   \/posts     # Creates a new blog post\nGET    \/posts     # Lists all blog posts\nGET    \/posts\/1   # Shows a blog post with ID 1\nPATCH  \/posts\/1   # Updates blog post with ID 1\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">plaintext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_50d58499032ba87d72dd509d7f781e7c\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<h3><span class=\"ez-toc-section\" id=\"package-versions\"><\/span>Package versions<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>We\u2019ll use the following NPM packages (versions in parentheses). Don\u2019t worry, we\u2019ll walk through installing them as we go.<\/p>\n<ul>\n<li><code>typescript<\/code> (5.4) \u2014\u00a0our programming language<\/li>\n<li><code>@nestjs\/cli<\/code> <strong><\/strong>(10.3) \u2014\u00a0Nest\u2019s command-line interface, used to spin up new projects and generate code<\/li>\n<li><code>@nestjs\/common<\/code> <strong><\/strong>(10.3) \u2014\u00a0Nest (NestJS) itself<\/li>\n<li><code>@nestjs\/mapped-types<\/code> <strong><\/strong>(2.0) \u2014\u00a0allows us to derive a DTO from another DTO<\/li>\n<li><code>sqlite3<\/code> <strong><\/strong>(5.1) \u2014 database driver for development<\/li>\n<li><code>typeorm<\/code> <strong><\/strong>(0.3) \u2014\u00a0our Object-Relational Mapper (ORM) for working with the database in code<\/li>\n<li><code>@nestjs\/typeorm<\/code> <strong><\/strong>(10.0) \u2014\u00a0first-party module that bridges TypeORM and Nest<\/li>\n<li><code>class-transformer<\/code>  <strong><\/strong>(0.5) \u2014\u00a0used along with class-validator for decorator-based DTO validation<\/li>\n<li><code>class-validator (0.14)<\/code> <strong><\/strong>\u2014\u00a0used for decorator-based DTO validation<\/li>\n<li><code>nestjs-i18n (10.4)<\/code> \u2014\u00a0our localization library<\/li>\n<\/ul>\n<p>Let\u2019s start by installing the Nest CLI from the command line and use it to spin up a new Nest project.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">$ npm install -g @nestjs\/cli\n$ nest new yogger-chicken\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_3a446a3388002538d430419b6fc5553b\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<h3><span class=\"ez-toc-section\" id=\"the-info-route\"><\/span>The info route<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Next, we\u2019ll add an <code>\/info<\/code> route that lists our API endpoints for consumers.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">$ nest generate module info\n$ nest generate controller info\n$ nest generate service info\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_d1858729f47ab2231b86810f608ee044\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Let\u2019s add the JSON that <code>\/info<\/code> will serve to our info service.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/info\/info.service.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Injectable } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/common'<\/span>;\n\n<span class=\"hljs-meta\">@Injectable<\/span>()\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> InfoService {\n  getInfo() {\n    <span class=\"hljs-keyword\">return<\/span> {\n      about: <span class=\"hljs-string\">'yogger chicken: a headless running blog'<\/span>,\n      lastUpdated: <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Date<\/span>().toISOString(),\n      routes: &#91;\n        {\n          verb: <span class=\"hljs-string\">'GET'<\/span>,\n          path: <span class=\"hljs-string\">'\/'<\/span>,\n          description: <span class=\"hljs-string\">'Redirects to \/info'<\/span>,\n        },\n        {\n          verb: <span class=\"hljs-string\">'GET'<\/span>,\n          path: <span class=\"hljs-string\">'\/info'<\/span>,\n          description: <span class=\"hljs-string\">'You are here'<\/span>,\n        },\n        {\n          verb: <span class=\"hljs-string\">'GET'<\/span>,\n          path: <span class=\"hljs-string\">'\/today'<\/span>,\n          description: <span class=\"hljs-string\">'Daily quote'<\/span>,\n        },\n        {\n          posts: &#91;\n            {\n              verb: <span class=\"hljs-string\">'GET'<\/span>,\n              path: <span class=\"hljs-string\">'\/posts'<\/span>,\n              description: <span class=\"hljs-string\">'Index of all posts'<\/span>,\n            },\n            {\n              verb: <span class=\"hljs-string\">'GET'<\/span>,\n              path: <span class=\"hljs-string\">'\/posts\/1'<\/span>,\n              description: <span class=\"hljs-string\">'Post with ID 1'<\/span>,\n            },\n            {\n              verb: <span class=\"hljs-string\">'POST'<\/span>,\n              path: <span class=\"hljs-string\">'\/posts'<\/span>,\n              description: <span class=\"hljs-string\">'Create a new post'<\/span>,\n            },\n            {\n              verb: <span class=\"hljs-string\">'PATCH'<\/span>,\n              path: <span class=\"hljs-string\">'\/posts\/1'<\/span>,\n              description: <span class=\"hljs-string\">'Update post with ID 1'<\/span>,\n            },\n          ],\n        },\n      ],\n    };\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_ca67ad754cd67cc4251b4a5d90e8ed6f\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The Nest CLI should have wired up the info module and controller when we created them. So if we run our app using <code>npm run start:dev<\/code> and hit the <code>\/info<\/code> route, we should JSON output like the following.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-84324 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/info-route.png\" alt=\"Our REST client making a GET request to the URL \/info with no query parameters. The response code is 200, and the response body contains { &quot;about&quot;: &quot;yogger chicken: a headless running blog&quot; }.\" width=\"1022\" height=\"888\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/info-route.png 1022w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/info-route-300x261.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/info-route-768x667.png 768w\" sizes=\"(max-width: 1022px) 100vw, 1022px\" \/><\/p>\n<p>Let\u2019s update the root route (<code>\/<\/code>) to redirect to <code>\/info<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/app.controller.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Controller, Get, Redirect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/common'<\/span>;\n\n<span class=\"hljs-meta\">@Controller<\/span>()\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> AppController {\n  <span class=\"hljs-meta\">@Get<\/span>()\n  <span class=\"hljs-meta\">@Redirect<\/span>(<span class=\"hljs-string\">'\/info'<\/span>, <span class=\"hljs-number\">301<\/span>)\n  getRoot() {}\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_aafb11e287f5b765ac9718344ec57203\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>If we hit <code>\/<\/code> now, we should see the same output as we did for <code>\/info<\/code>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"posts-and-the-database\"><\/span>Posts and the database<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>We want CRUD (Create, Read, Update, Delete) endpoints for our blog posts. To persist post data, let\u2019s use a SQLite development database. TypeORM works well with Nest, so we\u2019ll use it to interface with the database. Let\u2019s install the dependencies.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\"> $ npm install sqlite3 typeorm @nestjs\/typeorm\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_19f8e766c836550d41c698f9be622c10\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><code>class-validator<\/code> works out-of-the-box with Nest\u2019s <code>ValidationPipe<\/code>, so it\u2019s a good choice for validating our DTO (Data Transfer Objects). Let\u2019s install it.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">$ npm install class-validator class-transformer\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_8acd629d0241a5098118de6b81664591\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>And to make our lives a bit easier, let\u2019s install Nest\u2019s <code>mapped-types<\/code> package, which allows us to derive a DTO from another.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">$ npm install @nestjs\/mapped-types\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_a426cc89609236a498581d68fd20d5a2\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now we can configure TypeORM in the root <code>AppModule<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/app.module.ts\n\n  import { Module } from '@nestjs\/common';\n<span class=\"hljs-addition\">+ import { TypeOrmModule } from '@nestjs\/typeorm';<\/span>\n  import { AppController } from '.\/app.controller';\n  import { InfoModule } from '.\/info\/info.module';\n  import { TodayModule } from '.\/today\/today.module';\n\n  @Module({\n    imports: &#91;\n<span class=\"hljs-addition\">+     TypeOrmModule.forRoot({<\/span>\n<span class=\"hljs-addition\">+       type: 'sqlite',<\/span>\n<span class=\"hljs-addition\">+       database: 'db.sqlite',<\/span>\n<span class=\"hljs-addition\">+       entities: &#91;__dirname + '\/**\/*.entity.{ts,js}'],<\/span>\n<span class=\"hljs-addition\">+       synchronize: true, \/\/ DO NOT USE IN PRODUCTION<\/span>\n<span class=\"hljs-addition\">+       logging: true,<\/span>\n<span class=\"hljs-addition\">+     }),<\/span>\n      InfoModule,\n      TodayModule,\n    ],\n    controllers: &#91;AppController],\n    providers: &#91;],\n  })\n  export class AppModule {}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_17edc686bf99aa05dea17540f8a33d6d\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>If we run our app now, we should see an empty <code>db.sqlite<\/code> file created in the root directory of our project.<\/p>\n<p>OK, onto the posts themselves. Let\u2019s put the Nest CLI to work generating our posts boilerplate.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">$ nest generate module posts\n$ nest generate service posts\n$ nest generate controller posts\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_2ba0ad2b408310735480270465335fb5\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We\u2019ll manually add the <code>Post<\/code> entity in a new file, keeping it simple.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/posts\/entities\/post.entity.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> {\n  Column,\n  Entity,\n  PrimaryGeneratedColumn,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'typeorm'<\/span>;\n\n<span class=\"hljs-meta\">@Entity<\/span>()\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> Post {\n  <span class=\"hljs-meta\">@PrimaryGeneratedColumn<\/span>()\n  id: <span class=\"hljs-built_in\">number<\/span>;\n\n  <span class=\"hljs-meta\">@Column<\/span>()\n  title: <span class=\"hljs-built_in\">string<\/span>;\n\n  <span class=\"hljs-meta\">@Column<\/span>()\n  content: <span class=\"hljs-built_in\">string<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_2f84c353d9217ef526551fab1fc3ca17\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>This <code>Post<\/code> entity will be used in our <code>PostsService<\/code> via TypeORM\u2019s repository pattern, which means we should import <code>Post<\/code> into our <code>PostsModule<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/posts\/posts.module.ts\n\n  import { Module } from '@nestjs\/common';\n<span class=\"hljs-addition\">+ import { TypeOrmModule } from '@nestjs\/typeorm';<\/span>\n<span class=\"hljs-addition\">+ import { Post } from '.\/entities\/post.entity';<\/span>\n  import { PostsController } from '.\/posts.controller';\n  import { PostsService } from '.\/posts.service';\n\n  @Module({\n<span class=\"hljs-addition\">+   imports: &#91;TypeOrmModule.forFeature(&#91;Post])],<\/span>\n    providers: &#91;PostsService],\n    controllers: &#91;PostsController],\n  })\n  export class PostsModule {}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_e78481682e0b6caa4d31b82410e84cb8\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Let\u2019s also add our create and update the post DTO.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/posts\/dto\/create-post.dto.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> {\n  IsNotEmpty,\n  IsString,\n  Length,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'class-validator'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> CreatePostDto {\n  <span class=\"hljs-meta\">@IsString<\/span>()\n  <span class=\"hljs-meta\">@IsNotEmpty<\/span>()\n  <span class=\"hljs-meta\">@Length<\/span>(<span class=\"hljs-number\">1<\/span>, <span class=\"hljs-number\">255<\/span>)\n  title: <span class=\"hljs-built_in\">string<\/span>;\n\n  <span class=\"hljs-meta\">@IsString<\/span>()\n  <span class=\"hljs-meta\">@IsNotEmpty<\/span>()\n  content: <span class=\"hljs-built_in\">string<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_1e376b03700193261a22cfe94f88dc84\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/posts\/dto\/update-post.dto.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { PartialType } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/mapped-types'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { CreatePostDto } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/create-post.dto'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> UpdatePostDto <span class=\"hljs-keyword\">extends<\/span> PartialType(\n  CreatePostDto,\n) {}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_777a9995f5f731e649d8a528bc783596\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Weaving all these parts together is the all-important <code>PostsService<\/code>. <\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/posts\/posts.service.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Injectable } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/common'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { InjectRepository } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/typeorm'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Repository } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'typeorm'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { CreatePostDto } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/dto\/create-post.dto'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { UpdatePostDto } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/dto\/update-post.dto'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Post } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/entities\/post.entity'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">interface<\/span> PostSummary {\n  id: <span class=\"hljs-built_in\">number<\/span>;\n  title: <span class=\"hljs-built_in\">string<\/span>;\n}\n\n<span class=\"hljs-meta\">@Injectable<\/span>()\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> PostsService {\n  <span class=\"hljs-keyword\">constructor<\/span>(<span class=\"hljs-params\">\n    <span class=\"hljs-meta\">@InjectRepository<\/span>(Post)\n    <span class=\"hljs-keyword\">private<\/span> postRepo: Repository&lt;Post&gt;,\n  <\/span>) {}\n\n  <span class=\"hljs-keyword\">async<\/span> findAll(): <span class=\"hljs-built_in\">Promise<\/span>&lt;PostSummary&#91;]&gt; {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.find({\n      select: &#91;<span class=\"hljs-string\">'id'<\/span>, <span class=\"hljs-string\">'title'<\/span>],\n    });\n  }\n\n  <span class=\"hljs-keyword\">async<\/span> findOne(id: <span class=\"hljs-built_in\">number<\/span>): <span class=\"hljs-built_in\">Promise<\/span>&lt;Post | <span class=\"hljs-literal\">null<\/span>&gt; {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.findOneBy({ id });\n  }\n\n  <span class=\"hljs-keyword\">async<\/span> create(\n    createPostDto: CreatePostDto,\n  ): <span class=\"hljs-built_in\">Promise<\/span>&lt;Post&gt; {\n    <span class=\"hljs-keyword\">const<\/span> newPost = <span class=\"hljs-keyword\">this<\/span>.postRepo.create({\n      ...createPostDto,\n    });\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.save(newPost);\n  }\n\n  <span class=\"hljs-keyword\">async<\/span> update(\n    id: <span class=\"hljs-built_in\">number<\/span>,\n    updatePostDto: UpdatePostDto,\n  ): <span class=\"hljs-built_in\">Promise<\/span>&lt;Post | <span class=\"hljs-literal\">null<\/span>&gt; {\n    <span class=\"hljs-keyword\">const<\/span> post = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.findOne(id);\n\n    <span class=\"hljs-keyword\">if<\/span> (!post) {\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">null<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">if<\/span> (post) {\n      post.title = updatePostDto.title ?? post.title;\n      post.content = updatePostDto.content ?? post.content;\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.save(post);\n  }\n\n  <span class=\"hljs-keyword\">async<\/span> remove(id: <span class=\"hljs-built_in\">number<\/span>): <span class=\"hljs-built_in\">Promise<\/span>&lt;<span class=\"hljs-built_in\">boolean<\/span>&gt; {\n    <span class=\"hljs-keyword\">const<\/span> post = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.findOne(id);\n\n    <span class=\"hljs-keyword\">if<\/span> (!post) {\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">false<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.remove(post);\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">true<\/span>;\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_a0465af77a043aefc24d430dc05694f9\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0We have <code>strictNullChecks: true<\/code> in our <code>.\/tsconfig.json<\/code> file.<\/p>\n<p>Our controller exposes the posts service to the outside world.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/posts\/posts.controller.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> {\n  Body,\n  Controller,\n  Delete,\n  Get,\n  NotFoundException,\n  Param,\n  Patch,\n  Post,\n  UsePipes,\n  ValidationPipe,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/common'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { CreatePostDto } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/dto\/create-post.dto'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { UpdatePostDto } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/dto\/update-post.dto'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Post <span class=\"hljs-keyword\">as<\/span> PostEntity } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/entities\/post.entity'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { PostSummary, PostsService } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/posts.service'<\/span>;\n\n<span class=\"hljs-meta\">@Controller<\/span>(<span class=\"hljs-string\">'posts'<\/span>)\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> PostsController {\n  <span class=\"hljs-keyword\">constructor<\/span>(<span class=\"hljs-params\">\n    <span class=\"hljs-keyword\">private<\/span> readonly postsService: PostsService,\n  <\/span>) {}\n\n  <span class=\"hljs-meta\">@Get<\/span>()\n  <span class=\"hljs-keyword\">async<\/span> findAll(): <span class=\"hljs-built_in\">Promise<\/span>&lt;PostSummary&#91;]&gt; {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">this<\/span>.postsService.findAll();\n  }\n\n  <span class=\"hljs-meta\">@Get<\/span>(<span class=\"hljs-string\">':id'<\/span>)\n  <span class=\"hljs-keyword\">async<\/span> findOne(\n    <span class=\"hljs-meta\">@Param<\/span>(<span class=\"hljs-string\">'id'<\/span>) id: <span class=\"hljs-built_in\">string<\/span>,\n  ): <span class=\"hljs-built_in\">Promise<\/span>&lt;PostEntity&gt; {\n    <span class=\"hljs-keyword\">const<\/span> post = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postsService.findOne(+id);\n\n    <span class=\"hljs-keyword\">if<\/span> (!post) {\n      <span class=\"hljs-keyword\">this<\/span>.throwNotFound(id);\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> post;\n  }\n\n  <span class=\"hljs-meta\">@Post<\/span>()\n  <span class=\"hljs-meta\">@UsePipes<\/span>(<span class=\"hljs-keyword\">new<\/span> ValidationPipe({ whitelist: <span class=\"hljs-literal\">true<\/span> }))\n  <span class=\"hljs-keyword\">async<\/span> create(\n    <span class=\"hljs-meta\">@Body<\/span>() createPostDto: CreatePostDto,\n  ): <span class=\"hljs-built_in\">Promise<\/span>&lt;PostEntity&gt; {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">this<\/span>.postsService.create(createPostDto);\n  }\n\n  <span class=\"hljs-meta\">@Patch<\/span>(<span class=\"hljs-string\">':id'<\/span>)\n  <span class=\"hljs-meta\">@UsePipes<\/span>(<span class=\"hljs-keyword\">new<\/span> ValidationPipe({ whitelist: <span class=\"hljs-literal\">true<\/span> }))\n  <span class=\"hljs-keyword\">async<\/span> update(\n    <span class=\"hljs-meta\">@Param<\/span>(<span class=\"hljs-string\">'id'<\/span>) id: <span class=\"hljs-built_in\">string<\/span>,\n    <span class=\"hljs-meta\">@Body<\/span>() updatePostDto: UpdatePostDto,\n  ): <span class=\"hljs-built_in\">Promise<\/span>&lt;PostEntity&gt; {\n    <span class=\"hljs-keyword\">const<\/span> post = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postsService.update(\n      +id,\n      updatePostDto,\n    );\n\n    <span class=\"hljs-keyword\">if<\/span> (!post) {\n      <span class=\"hljs-keyword\">this<\/span>.throwNotFound(id);\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> post;\n  }\n\n  <span class=\"hljs-meta\">@Delete<\/span>(<span class=\"hljs-string\">':id'<\/span>)\n  <span class=\"hljs-keyword\">async<\/span> remove(\n    <span class=\"hljs-meta\">@Param<\/span>(<span class=\"hljs-string\">'id'<\/span>) id: <span class=\"hljs-built_in\">string<\/span>,\n  ): <span class=\"hljs-built_in\">Promise<\/span>&lt;{ message: <span class=\"hljs-built_in\">string<\/span> }&gt; {\n    <span class=\"hljs-keyword\">const<\/span> ok = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postsService.remove(+id);\n\n    <span class=\"hljs-keyword\">if<\/span> (!ok) {\n      <span class=\"hljs-keyword\">this<\/span>.throwNotFound(id);\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> { message: <span class=\"hljs-string\">'Post deleted'<\/span> };\n  }\n\n  <span class=\"hljs-keyword\">private<\/span> throwNotFound(id: <span class=\"hljs-built_in\">string<\/span>): never {\n    <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> NotFoundException(\n      <span class=\"hljs-string\">`Post with ID <span class=\"hljs-subst\">${id}<\/span> not found`<\/span>,\n    );\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_e1fd235ffc4d9c491668c1aa7c1d3fb0\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0We\u2019re skipping authentication and authorization in this tutorial for brevity.<\/p>\n<p>We can now create and update posts to our heart\u2019s content.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84348 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/post-post-1024x883.png\" alt=\"Our REST client making a POST request to the URL \/posts with form-encoded parameters title=&quot;Running in place&quot; and content=&quot;I love my treadmill, but I will admit...&quot;. The response code is 201 Created, and the response body contains { &quot;id&quot;: 5, &quot;title&quot;: &quot;Running in place&quot;, &quot;content&quot;: &quot;I love my treadmill, but I will admit...&quot; }.\" width=\"1024\" height=\"883\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/post-post-1024x883.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/post-post-300x259.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/post-post-768x663.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/post-post.png 1078w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84354 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/get-posts-1024x816.png\" alt=\"Our REST client making a GET request to the URL \/posts with no query parameters. The response code is 200, and the response body contains a JSON object with a list of posts, displaying their ids and titles.\" width=\"1024\" height=\"816\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/get-posts-1024x816.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/get-posts-300x239.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/get-posts-768x612.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/get-posts.png 1084w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0You can get all the starter demo code from our GitHub repo\u2019s <a href=\"https:\/\/github.com\/PhraseApp-Blog\/nestjs-i18n\/tree\/start\">start branch<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-my-nest-app\"><\/span>How do I localize my Nest app?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>OK, it\u2019s time for the main course. This is our recipe for localizing a Nest app with <code>nestjs-i18n<\/code>:<\/p>\n<p>1. Install and set up <code>nestjs-i18n<\/code>.<br \/>\n2. Move our hard-coded strings to translation files.<br \/>\n3. Load translation files using one of <code>nestjs-i18n<\/code>\u2019s loaders.<br \/>\n4. Inject the i18n context and service into controllers and services, respectively, and use them to fetch strings from translation files.<br \/>\n5. Determine the active locale using <code>nestjs-i18n<\/code>\u2019s resolvers.<br \/>\n6. Add type safety to our translation keys.<br \/>\n7. Handle dynamic values and localized plurals in our translations.<br \/>\n8. Localize the database data.<br \/>\n9. Localize validation messages.<\/p>\n<p>We\u2019ll go through these step-by-step.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"a-note-on-locales\"><\/span>A note on locales<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A locale defines a language, a region, and sometimes more. Locales typically use <a href=\"https:\/\/en.wikipedia.org\/wiki\/IETF_language_tag\">IETF BCP 47 language tags<\/a>, like <code>en<\/code> for English, <code>fr<\/code> for French, and <code>es<\/code> for Spanish. A region can be added with the ISO Alpha-2 code (e.g., <code>BH<\/code> for Bahrain, <code>CN<\/code> for China, <code>US<\/code> for the United States). So a locale with a region might look like <code>en-US<\/code> for English as used in the United States or <code>zh-CN<\/code> for Chinese as used in China.<\/p>\n<p>\ud83d\udd17 Explore more language tags on <a href=\"https:\/\/en.wikipedia.org\/wiki\/List_of_ISO_639-1_codes\">Wikipedia<\/a> and find country codes through the ISO&#8217;s <a href=\"https:\/\/www.iso.org\/obp\/ui\/#search\">search tool<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-set-up-nestjs-i18n\"><\/span>How do I set up nestjs-i18n?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>First things first, let\u2019s install the package.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">$ npm install nestjs-i18n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_da360ebc0813151fc08c1a2b0840ade3\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Next, let\u2019s create a <code>src\/locales<\/code> directory to house our translations. We\u2019ll work with English (<code>en<\/code>) and Arabic (<code>ar<\/code>) in this tutorial. Feel free to add any locales you want here. <\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ src\/locales\/en\/common.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"about\"<\/span>: <span class=\"hljs-string\">\"yogger chicken: a headless blog about\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_1e376b03700193261a22cfe94f88dc84\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ src\/locales\/ar\/common.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"about\"<\/span>: <span class=\"hljs-string\">\"\u062f\u062c\u0627\u062c \u064a\u0648\u062c\u0631: \u0645\u062f\u0648\u0646\u0629 \u062c\u0631\u064a \u0628\u0644\u0627 \u0631\u0623\u0633\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_0b62bcabb2756e51cb7e282b6f42b1db\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We need to add the global <code>I18nModule<\/code> from <code>nestjs-i18n<\/code> to our <code>AppModule<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/app.module.ts\n\n  import { Module } from '@nestjs\/common';\n  import { TypeOrmModule } from '@nestjs\/typeorm';\n<span class=\"hljs-addition\">+ import { I18nModule, QueryResolver } from 'nestjs-i18n';<\/span>\n<span class=\"hljs-addition\">+ import * as path from 'path';<\/span>\n  import { AppController } from '.\/app.controller';\n  import { InfoModule } from '.\/info\/info.module';\n  import { PostsModule } from '.\/posts\/posts.module';\n  import { TodayModule } from '.\/today\/today.module';\n\n  @Module({\n    imports: &#91;\n      TypeOrmModule.forRoot({\n        type: 'sqlite',\n        database: 'db.sqlite',\n        entities: &#91;__dirname + '\/**\/*.entity.{ts,js}'],\n        synchronize: true,\n        logging: true,\n      }),\n<span class=\"hljs-addition\">+     I18nModule.forRoot({<\/span>\n<span class=\"hljs-addition\">+       fallbackLanguage: 'en',<\/span>\n<span class=\"hljs-addition\">+       loaderOptions: {<\/span>\n<span class=\"hljs-addition\">+         path: path.join(__dirname, '\/locales\/'),<\/span>\n<span class=\"hljs-addition\">+         watch: true,<\/span>\n<span class=\"hljs-addition\">+       },<\/span>\n<span class=\"hljs-addition\">+       resolvers: &#91;new QueryResolver(&#91;'lang'])],<\/span>\n<span class=\"hljs-addition\">+     }),<\/span>\n      InfoModule,\n      PostsModule,\n      TodayModule,\n    ],\n    controllers: &#91;AppController],\n    providers: &#91;],\n  })\n  export class AppModule {}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_ec55f6f0aa5e8cbf4b604670eacaeb07\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><code>nestjs-i18n<\/code> uses a <strong>loader<\/strong> to read our translation message files, which we configure in <code>loaderOptions<\/code>. The <code>watch<\/code> option ensures that translations are reloaded into the app when translation files change.<\/p>\n<p>Our app needs to have a single active locale for a request. In our app, this will be English (<code>en<\/code>) or Arabic (<code>ar<\/code>). <code>nestjs-i18n<\/code> uses one or more <strong>resolvers<\/strong> to determine the current request\u2019s locale. We\u2019re using the <code>QueryResolver<\/code>, which will look at a query param called <code>lang<\/code> to determine the active locale. For example, if our URL is <code>http:\/\/localhost:3000?lang=ar<\/code>, the active locale will resolve to Arabic (<code>ar<\/code>).<\/p>\n<p>The <code>fallbackLanguage<\/code> is used when we can\u2019t otherwise resolve the active locale for a request. <\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0We\u2019ll look at resolvers and loaders more closely in later sections.<\/p>\n<p>The new <code>locales<\/code> directory housing our translation files won\u2019t be automatically copied to the <code>dist<\/code> directory during builds, which will cause errors. We can fix this by explicitly telling the Nest CLI to copy the directory in the <code>nest-cli.json<\/code> config file.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ nest-cli.json\n\n{\n  \"$schema\": \"https:\/\/json.schemastore.org\/nest-cli\",\n  \"collection\": \"@nestjs\/schematics\",\n  \"sourceRoot\": \"src\",\n  \"compilerOptions\": {\n    \"deleteOutDir\": true,\n<span class=\"hljs-addition\">+   \"assets\": &#91;<\/span>\n<span class=\"hljs-addition\">+     { \"include\": \"locales\/**\/*\", \"watchAssets\": true }<\/span>\n<span class=\"hljs-addition\">+   ]<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_c64cc63f6fde6903ebdae5e90547fbb3\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>OK, if we run our app now we should see server logs in our terminal indicating that <code>nestjs-i18n<\/code> is watching our <code>locales<\/code> directory for changes.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84360 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/i18n-server-logs-1024x53.png\" alt=\"NEST server log entries showing [I18nService] output. One entry reads &quot;Checking translation changes&quot; and the other reads &quot;No changes detected&quot;.\" width=\"1024\" height=\"53\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/i18n-server-logs-1024x53.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/i18n-server-logs-300x16.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/i18n-server-logs-768x40.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/i18n-server-logs.png 1354w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Let\u2019s use the library to localize something, shall we? We\u2019ll do so in the following section, where we\u2019ll look at injecting the <code>I18nContext<\/code> and <code>I18nService<\/code> into our controllers and services.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0If you want to use Nest\u2019s <a href=\"https:\/\/docs.nestjs.com\/techniques\/configuration\">configuration service<\/a> to set these config options using <code>.env<\/code> files, you can do so with <code>I18nModule.forRootAsync<\/code>. See the <code>nestjs-i18n<\/code> docs <a href=\"https:\/\/nestjs-i18n.com\/quick-start#module-setup\">Module setup<\/a> section for more.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Check out the <a href=\"https:\/\/nestjs-i18n.com\/quick-start#i18noptions\">I18nOptions<\/a> section of the docs for a complete listing of config options.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-controllers-and-services\"><\/span>How do I localize controllers and services?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Let\u2019s utilize the translations we added in the previous section to create a new route, <code>\/info\/about<\/code>, which will return the localized name of our app. We\u2019ll need to inject the <code>I18nContext<\/code> object into our route handler method and decorate it with <code>@I18n<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/info\/info.controller.ts\n\n  import { Controller, Get } from '@nestjs\/common';\n<span class=\"hljs-addition\">+ import { I18n, I18nContext } from 'nestjs-i18n';<\/span>\n  import { InfoService } from '.\/info.service';\n\n  @Controller('info')\n  export class InfoController {\n    constructor(private readonly infoService: InfoService) {}\n\n    @Get()\n    getInfo() {\n      return this.infoService.getInfo();\n    }\n\n<span class=\"hljs-addition\">+   @Get('about')<\/span>\n<span class=\"hljs-addition\">+   getAbout(@I18n() i18n: I18nContext) {<\/span>\n<span class=\"hljs-addition\">+     return i18n.t('common.about');<\/span>\n<span class=\"hljs-addition\">+   }<\/span>\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_7417357793fb0799f29e6b2662d521a5\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Notice the <code>t()<\/code> method exposed by <code>I18nContext<\/code>: It takes a translation key in the <code>file.key<\/code> format and returns the corresponding translation from the active locale&#8217;s file. <\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">.\n\u2514\u2500\u2500 src\n    \u2514\u2500\u2500 locales\n        \u251c\u2500\u2500 en\n        \u2502   \u2514\u2500\u2500 common&lt;strong&gt;.&lt;\/strong&gt;json\n        \u2502       \u2514\u2500\u2500 \"about\": \"yogger chicken: a headless running blog\"\n        \u2514\u2500\u2500 ar\n            \u2514\u2500\u2500 common.json\n                \u2514\u2500\u2500 \"about\": \"\u062f\u062c\u0627\u062c \u064a\u0648\u062c\u0631: \u0645\u062f\u0648\u0646\u0629 \u062c\u0631\u064a \u0628\u0644\u0627 \u0631\u0623\u0633\"\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">plaintext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_2d182f3baf0c4a58a96c2b7097ed7444\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Given the above translation structure,<code>i18n.t('common.about')<\/code> will map to the <code>common.json<\/code> file\u2019s <code>about<\/code> translation for the active locale. Let\u2019s see this in action by hitting the <code>\/info\/about<\/code> route.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-84367 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-info-about.png\" alt=\"Our REST client making a GET request to the URL \/info\/about with no query parameters. The response code is 200, and the response body contains &quot;yogger chicken: a headless running blog&quot;.\" width=\"966\" height=\"424\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-info-about.png 966w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-info-about-300x132.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-info-about-768x337.png 768w\" sizes=\"(max-width: 966px) 100vw, 966px\" \/><\/p>\n<p>Since we didn\u2019t specify a locale in our request, <code>nestjs-i18n<\/code> will use the configured <code>fallbackLanguage<\/code>, which is English (<code>en<\/code>).<\/p>\n<p>What if we want to set the locale in our request explicitly? Recall that we configured <code>nestjs-i18n<\/code> to use a <code>QueryResolver<\/code>, which looks for a <code>lang<\/code> query param to determine the active locale. All we need to do is provide the <code>\/?lang={locale}<\/code> param when we make a request.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-84373 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-locale-about.png\" alt=\"Our REST client making a GET request to the URL \/info\/about with a query parameter locale=ar. The response code is 200, and the response body contains &quot;\u062f\u062c\u0627\u062c \u064a\u0648\u062c\u0631: \u0645\u062f\u0648\u0646\u0629 \u062c\u0631\u064a \u0628\u0644\u0627 \u0631\u0623\u0633&quot;. \" width=\"964\" height=\"446\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-locale-about.png 964w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-locale-about-300x139.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-locale-about-768x355.png 768w\" sizes=\"(max-width: 964px) 100vw, 964px\" \/><\/p>\n<p>Hitting <code>\/info\/about?lang=ar<\/code> sets the active locale to Arabic (<code>ar<\/code>).<\/p>\n<p>That\u2019s the basic controller translation flow with <code>nestjs-i18n<\/code>. Of course, sometimes we need to translate strings inside our services. This can be accomplished by injecting the <code>I18nService<\/code> into our own.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/info\/info.service.ts\n\n  import { Injectable } from '@nestjs\/common';\n<span class=\"hljs-addition\">+ import { I18nContext, I18nService } from 'nestjs-i18n';<\/span>\n\n  @Injectable()\n  export class InfoService {\n<span class=\"hljs-addition\">+   constructor(private readonly i18n: I18nService) {}<\/span>\n\n    getInfo() {\n      return {\n<span class=\"hljs-deletion\">-       about: 'yogger chicken: a headless running blog',<\/span>\n<span class=\"hljs-addition\">+       about: this.i18n.translate('common.about', {<\/span>\n<span class=\"hljs-addition\">+         lang: I18nContext.current().lang,<\/span>\n<span class=\"hljs-addition\">+       }),<\/span>\n        lastUpdated: new Date().toISOString(),\n        routes: &#91;\n          \/\/ ...\n        ],\n      };\n    }\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_19286cf2d6c53d64fa085147f54aedb9\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Unlike the <code>I18nContext<\/code> object, the <code>I18nService<\/code> instance doesn\u2019t know about the active locale; we need to explicitly provide it with a <code>lang<\/code> option via <code>I18nContext.current()<\/code>.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0We can call <code>I18nContext.current().translate(key)<\/code> instead of <code>i18n.t(key)<\/code>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"a-wrapper-i18n-service\"><\/span>A wrapper i18n service<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>It\u2019s cumbersome to provide an <code>I18nContext<\/code> whenever we want to localize a controller or service. A simple wrapper service can reduce this friction. Let\u2019s create one quickly.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">$ nest generate module yc-i18n\n$ nest generate service yc-i18n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_c6d43593c9e910e33fee8e6a7bfed723\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We\u2019re using <code>Yc<\/code> as a namespace here. In the new service, we can wrap the <code>I18nService.translate()<\/code> function with our own <code>t()<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"> <span class=\"hljs-comment\">\/\/ src\/yc-i18n\/yc-i18n.service.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Injectable } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/common'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { I18nContext, I18nService } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'nestjs-i18n'<\/span>;\n\n<span class=\"hljs-meta\">@Injectable<\/span>()\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> YcI18nService {\n  <span class=\"hljs-keyword\">constructor<\/span>(<span class=\"hljs-params\"><span class=\"hljs-keyword\">private<\/span> readonly i18n: I18nService<\/span>) {}\n\n  t(key: <span class=\"hljs-built_in\">string<\/span>, options?: Record&lt;<span class=\"hljs-built_in\">string<\/span>, <span class=\"hljs-built_in\">any<\/span>&gt;) {\n    <span class=\"hljs-keyword\">const<\/span> lang = I18nContext.current().lang;\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">this<\/span>.i18n.translate(key, { lang, ...options });\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_f5a1572fd2f3003d5206ccf554ab8fa3\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Let\u2019s make sure the <code>YcI18nModule<\/code> exports its service and is made global.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/yc-i18n\/yc-i18n.module.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Global, Module } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/common'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { YcI18nService } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/yc-i18n.service'<\/span>;\n\n<span class=\"hljs-comment\">\/\/ Make the module global so that we don't have<\/span>\n<span class=\"hljs-comment\">\/\/ to import it into every other module that needs it.<\/span>\n<span class=\"hljs-meta\">@Global<\/span>()\n<span class=\"hljs-meta\">@Module<\/span>({\n  providers: &#91;YcI18nService],\n  exports: &#91;YcI18nService], <span class=\"hljs-comment\">\/\/ export the service<\/span>\n})\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> YcI18nModule {}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_a19e15240143d0037dfa6de5e79cab56\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now we can conveniently use constructor injection to access our <code>YcI18nService<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/info\/info.service.ts\n\n  import { Injectable } from '@nestjs\/common';\n<span class=\"hljs-deletion\">- import { I18nContext, I18nService } from 'nestjs-i18n';<\/span>\n<span class=\"hljs-addition\">+ import { YcI18nService } from 'src\/yc-i18n\/yc-i18n.service';<\/span>\n\n  @Injectable()\n  export class InfoService {\n<span class=\"hljs-deletion\">-   constructor(private readonly i18n: I18nService) {}<\/span>\n<span class=\"hljs-addition\">+   constructor(private readonly i18n: YcI18nService) {}<\/span>\n\n    getInfo() {\n      return {\n<span class=\"hljs-deletion\">-       about: this.i18n.translate('common.about', {<\/span>\n<span class=\"hljs-deletion\">-         lang: I18nContext.current().lang,<\/span>\n<span class=\"hljs-deletion\">-       }),<\/span>\n<span class=\"hljs-addition\">+       about: this.i18n.t('common.about'),<\/span>\n        lastUpdated: new Date().toISOString(),\n        routes: &#91;\n          \/\/ ...\n        ],\n      };\n    }\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_11c17ca32dae05a0ee1136563270fe2d\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We can do the same thing in our controllers, avoiding method injection and decoration.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/info\/info.controller.ts\n\n  import { Controller, Get } from '@nestjs\/common';\n<span class=\"hljs-deletion\">- import { I18n, I18nContext } from 'nestjs-i18n';<\/span>\n<span class=\"hljs-addition\">+ import { YcI18nService } from 'src\/yc-i18n\/yc-i18n.service';<\/span>\n  import { InfoService } from '.\/info.service';\n\n  @Controller('info')\n  export class InfoController {\n   constructor(\n     private readonly infoService: InfoService,\n<span class=\"hljs-addition\">+    private readonly i18n: YcI18nService,<\/span>\n   ) {}\n\n    @Get()\n    getInfo() {\n      return this.infoService.getInfo();\n    }\n\n    @Get('about')\n<span class=\"hljs-deletion\">-   getAbout(@I18n() i18n: I18nContext) {<\/span>\n<span class=\"hljs-addition\">+   getAbout() {<\/span>\n<span class=\"hljs-deletion\">-     return i18n.t('common.about');<\/span>\n<span class=\"hljs-addition\">+     return this.i18n.t('common.about');<\/span>\n    }\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_685572529967c7dd89231b8f5fd29242\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>This refactor reduces the surface area we need to touch when accessing the i18n service.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-load-translation-files\"><\/span>How do I load translation files?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><code>nestjs-i18n<\/code> comes with two built-in loaders, one for JSON files (the default) and one for YAML. We\u2019ll prefer the JSON loader in this article. Switching to the YAML loader is easy, however.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/app.module.ts\n\n\/\/ ...\nimport {\n  AcceptLanguageResolver,\n  HeaderResolver,\n  I18nModule,\n<span class=\"hljs-addition\">+ I18nYamlLoader,<\/span>\n  QueryResolver,\n} from 'nestjs-i18n';\nimport * as path from 'path';\n\/\/ ...\n\n@Module({\n  imports: &#91;\n    \/\/ ...\n    I18nModule.forRoot({\n      fallbackLanguage: 'en',\n<span class=\"hljs-addition\">+     loader: I18nYamlLoader,<\/span>\n      loaderOptions: {\n        path: path.join(__dirname, '\/locales\/'),\n        watch: true,\n      },\n      resolvers: &#91;new QueryResolver(&#91;'lang'])],\n    }),\n    \/\/ ...\n  ],\n  controllers: &#91;AppController],\n  providers: &#91;],\n})\nexport class AppModule {}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_eb60ccb9114f3361608648bf7e5fbe92\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Of course, this loader assumes our translation files are in YAML format.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"YAML\" data-shcb-language-slug=\"yaml\"><span><code class=\"hljs language-yaml\"><span class=\"hljs-comment\"># src\/locales\/en\/common.yml<\/span>\n\n<span class=\"hljs-attr\">about:<\/span> <span class=\"hljs-string\">'yogger chicken: a headless running blog'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">YAML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">yaml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_1e376b03700193261a22cfe94f88dc84\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"YAML\" data-shcb-language-slug=\"yaml\"><span><code class=\"hljs language-yaml\"><span class=\"hljs-comment\"># src\/locales\/ar\/common.yml<\/span>\n\n<span class=\"hljs-attr\">about:<\/span> <span class=\"hljs-string\">'\u062f\u062c\u0627\u062c \u064a\u0648\u062c\u0631: \u0645\u062f\u0648\u0646\u0629 \u062c\u0631\u064a \u0628\u0644\u0627 \u0631\u0623\u0633'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">YAML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">yaml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b770423fcd726962d55f2ab8246007d2\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Again, we\u2019ll stick to JSON in this article, but the choice is yours.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0Check out the <a href=\"https:\/\/nestjs-i18n.com\/concepts\/loader\">Loaders<\/a> docs page for more info, including <a href=\"https:\/\/nestjs-i18n.com\/concepts\/loader#subfolders\">subfolder loading<\/a>. <\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0It seems that you can create custom loaders for <code>nestjs-i18n<\/code>. This <a href=\"https:\/\/nestjs-i18n.com\/concepts\/loader#example-custom-loader-coming-soon\">wasn\u2019t documented<\/a> at the time of writing, however. So you might have to dig through the <a href=\"https:\/\/github.com\/toonvanstrijp\/nestjs-i18n\">nestjs-i18n source code<\/a> to figure out how to roll your own loaders.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-resolve-the-active-locale\"><\/span>How do I resolve the active locale?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>For a given request, we need to set <strong>one<\/strong> active locale. The process of determining this locale is called locale resolution, and there are multiple strategies we can use to resolve the active locale:<\/p>\n<ul>\n<li>Reading the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Accept-Language\">HTTP Accept-Language<\/a> header in the response is a common strategy when working with web browsers: The <code>Accept-Language<\/code> header often contains the locales preferred by the user and set in their browser settings.<\/li>\n<li>Reading a custom HTTP header, e.g. <code>x-lang<\/code>, can be handy since it can allow REST clients to configure the header once and reuse it.<\/li>\n<li>Using a query param is a flexible and transparent strategy. We\u2019ve already been doing this with our <code>?lang<\/code> param.<\/li>\n<\/ul>\n<p><code>nestjs-i18n<\/code> has several of these <strong>resolvers<\/strong> built-in. We\u2019ve already used the <code>QueryResovler<\/code>. Let\u2019s bring in another two and configure our resolvers into a little cascade.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/app.module.ts\n\nimport { Module } from '@nestjs\/common';\nimport { TypeOrmModule } from '@nestjs\/typeorm';\nimport {\n<span class=\"hljs-addition\">+ AcceptLanguageResolver,<\/span>\n<span class=\"hljs-addition\">+ HeaderResolver,<\/span>\n  I18nModule,\n  QueryResolver,\n} from 'nestjs-i18n';\nimport * as path from 'path';\n\/\/ ...\n\n@Module({\n  imports: &#91;\n    \/\/ ...\n    I18nModule.forRoot({\n      fallbackLanguage: 'en',\n      loaderOptions: {\n        path: path.join(__dirname, '\/locales\/'),\n        watch: true,\n      },\n      resolvers: &#91;\n        new QueryResolver(&#91;'lang']),\n<span class=\"hljs-addition\">+       AcceptLanguageResolver,<\/span>\n<span class=\"hljs-addition\">+       new HeaderResolver(&#91;'x-lang']),<\/span>\n      ],\n    }),\n    \/\/ ...\n  ],\n  controllers: &#91;AppController],\n  providers: &#91;],\n})\nexport class AppModule {}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_2da8e9b33ca33c0907e2b7cb1729b39b\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>With this configuration, <code>nestjs-i18n<\/code> will go through each resolver in order, stopping if it resolves a locale. Let\u2019s test this by hitting <code>http:\/\/localhost:3000?lang=ar<\/code><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84379 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/query-resolver-1024x658.png\" alt=\"Our REST client making a GET request to the URL \/query with query parameter lang=ar. The response code is 200, and the response body contains { &quot;about&quot;: &quot;\u062f\u062c\u0627\u062c \u064a\u0648\u062c\u0631: \u0645\u062f\u0648\u0646\u0629 \u062c\u0631\u064a \u0628\u0644\u0627 \u0631\u0623\u0633&quot;}.\" width=\"1024\" height=\"658\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/query-resolver-1024x658.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/query-resolver-300x193.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/query-resolver-768x494.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/query-resolver.png 1030w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Since we provided a query param of <code>lang=ar<\/code>, the <code>QueryResolver<\/code> will catch the param and resolve the active locale to <code>ar<\/code>. No other resolver will run.<\/p>\n<p>Next test: don\u2019t provide a query param; provide an <code>Accept-Language<\/code> HTTP header instead.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84385 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/accept-lang-resolver-1024x836.png\" alt=\"Our REST client making a GET request to the URL \/language with no query parameters. The request headers include an Accept-Language header set to &quot;ar;q=1.0,en;q=0.9&quot;. The response code is 200, and the response body contains { &quot;about&quot;: &quot;\u062f\u062c\u0627\u062c \u064a\u0648\u062c\u0631: \u0645\u062f\u0648\u0646\u0629 \u062c\u0631\u064a \u0628\u0644\u0627 \u0631\u0623\u0633&quot; }.\" width=\"1024\" height=\"836\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/accept-lang-resolver-1024x836.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/accept-lang-resolver-300x245.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/accept-lang-resolver-768x627.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/accept-lang-resolver.png 1048w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>In this case, the <code>QueryResolver<\/code> doesn\u2019t find a <code>lang<\/code> param, so we cascade down to the <code>AcceptLanguageResolver<\/code>, which does find our header value of <code>ar;q=1.0,en;q=0.9<\/code>. This value indicates that <code>ar<\/code> is preferred over <code>en<\/code>, so the <code>AcceptLanguageResolver<\/code> resolves to <code>ar<\/code>.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0We go into locale detection and the <code>Accept-Language<\/code> header in our guide, <a href=\"https:\/\/phrase.com\/blog\/posts\/detecting-a-users-locale\/\">Detecting a User\u2019s Locale in a Web App<\/a>.<\/p>\n<p>As you can imagine, if we don\u2019t supply a <code>lang<\/code> query param or an <code>Accept-Language<\/code> header but do set a custom <code>x-lang<\/code> HTTP header, our <code>HeaderResolver<\/code> will kick in.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84391 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/header-resolver-1024x834.png\" alt=\"&quot;Our REST client making a GET request to the URL \/ with no query parameters. The request headers include a custom header x-lang set to &quot;ar&quot;. The response code is 200, and the response body contains { &quot;about&quot;: &quot;\u062f\u062c\u0627\u062c \u064a\u0648\u062c\u0631: \u0645\u062f\u0648\u0646\u0629 \u062c\u0631\u064a \u0628\u0644\u0627 \u0631\u0623\u0633&quot; }.\" width=\"1024\" height=\"834\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/header-resolver-1024x834.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/header-resolver-300x244.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/header-resolver-768x626.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/header-resolver.png 1048w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>If our cascade falls through entirely, <code>nestjs-i18n<\/code> will use the <code>fallbackLanguage<\/code> we set in the config (<code>en<\/code> in our case).<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0<code>nestjs-i18n<\/code> resolvers use a best-fit algorithm, such that <code>en<\/code>, <code>en-US<\/code>, <code>en_GB<\/code>, etc. will all resolve to <code>en<\/code>.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0If you want granular control over fallback behavior, check out the <a href=\"https:\/\/nestjs-i18n.com\/guides\/fallback-languages\">Fallback languages<\/a> page of the docs.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Check out the <a href=\"https:\/\/nestjs-i18n.com\/concepts\/resolver\">Resolvers<\/a> page of the <code>nestjs-i18n<\/code> docs for more info, including coverage of the <code>CookieResolver<\/code>, <code>GraphQLWebsocketResolver<\/code>, and <code>GrpcMetadataResolver<\/code>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-redirect-while-keeping-the-active-locale\"><\/span>How do I redirect while keeping the active locale?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We currently have our <code>\/<\/code> route redirecting to <code>\/info<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/app.controller.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Controller, Get, Redirect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/common'<\/span>;\n\n<span class=\"hljs-meta\">@Controller<\/span>()\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> AppController {\n  <span class=\"hljs-meta\">@Get<\/span>()\n  <span class=\"hljs-meta\">@Redirect<\/span>(<span class=\"hljs-string\">'\/info'<\/span>, <span class=\"hljs-number\">301<\/span>)\n  getRoot() {}\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_e26516a8dce6fad0ceb4f3eee2c02233\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>While this will keep the original HTTP headers when redirecting, it won\u2019t pass along any query parameters. This means the <code>QueryResolver<\/code> we configured in the previous section won\u2019t get its <code>lang<\/code> param when redirecting from <code>\/<\/code> to <code>\/info<\/code>.<\/p>\n<p>We can fix this by copying over the query params manually when redirecting.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/app.controller.ts\n\n  import {\n    Controller,\n    Get,\n<span class=\"hljs-addition\">+   Query,<\/span>\n<span class=\"hljs-addition\">+   Res,<\/span>\n  } from '@nestjs\/common';\n<span class=\"hljs-addition\">+ import { Response } from 'express';<\/span>\n\n  @Controller()\n  export class AppController {\n    @Get()\n    getRoot(\n<span class=\"hljs-addition\">+     @Query() query: Record&lt;string, any&gt;,<\/span>\n<span class=\"hljs-addition\">+     @Res() res: Response,<\/span>\n    ) {\n<span class=\"hljs-addition\">+     \/\/ copy original query params<\/span>\n<span class=\"hljs-addition\">+     const queryParams = new URLSearchParams(<\/span>\n<span class=\"hljs-addition\">+       query,<\/span>\n<span class=\"hljs-addition\">+     ).toString();<\/span>\n<span class=\"hljs-addition\">+     return res.redirect(302, `\/info?${queryParams}`);<\/span>\n    }\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_6a5aba64b4f370d7ea31823af849f0a1\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now if we visit <code>http:\/\/localhost:300?lang=ar<\/code> we\u2019ll correctly see the Arabic output from <code>\/info<\/code>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84398 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-redirect-1024x633.png\" alt=\"Our REST client making a GET request to the URL \/ with a query parameter locale=ar. The response code is 200, and the response body contains { &quot;about&quot;: &quot;\u062f\u062c\u0627\u062c \u064a\u0648\u062c\u0631: \u0645\u062f\u0648\u0646\u0629 \u062c\u0631\u064a \u0628\u0644\u0627 \u0631\u0623\u0633&quot; }.\" width=\"1024\" height=\"633\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-redirect-1024x633.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-redirect-300x185.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-redirect-768x474.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-redirect.png 1104w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Grab all the demo code from our <a href=\"https:\/\/github.com\/PhraseApp-Blog\/nestjs-i18n\">GitHub repo<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-make-my-message-keys-type-safe\"><\/span>How do I make my message keys type-safe?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><code>nestjs-i18n<\/code> is TypeScript-ready by default, but the keys passed into <code>t()<\/code> or <code>translate()<\/code> are not. For bullet-proof (or at least type-safe) translation keys, we must opt into the feature.<\/p>\n<p><code>nestjs-i18n<\/code> supports type-safe keys by generating the types on-the-fly when we run our app. Let\u2019s configure this.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/app.module.ts\n\n\/\/ ...\n\n@Module({\n  imports: &#91;\n    \/\/ ...\n    I18nModule.forRoot({\n      \/\/...\n      resolvers: &#91;\n        new QueryResolver(&#91;'lang']),\n        AcceptLanguageResolver,\n        new HeaderResolver(&#91;'x-lang']),\n      ],\n<span class=\"hljs-addition\">+     typesOutputPath: path.join(<\/span>\n<span class=\"hljs-addition\">+       __dirname,<\/span>\n<span class=\"hljs-addition\">+       '..\/src\/generated\/i18n.generated.ts',<\/span>\n<span class=\"hljs-addition\">+     ),<\/span>\n    }),\n    \/\/ ...\n  ],\n  controllers: &#91;AppController],\n  providers: &#91;],\n})\nexport class AppModule {}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_e3efb8257df248123c00bf53b0098cad\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>When we add the <code>typesOutputPath<\/code> to <code>nestjs-i18n<\/code>\u2019s config options, the library will generate a type file at the given path when we run the app.<\/p>\n<p>Let\u2019s add the generated file to eslint ignores to prevent linting errors.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ .eslintrc.js\n\nmodule.exports = {\n  parser: '@typescript-eslint\/parser',\n  \/\/...\n  env: {\n    node: true,\n    jest: true,\n  },\n<span class=\"hljs-addition\">+ ignores: &#91;'src\/generated\/i18n.generated.ts'],<\/span>\n  ignorePatterns: &#91;'.eslintrc.js'],\n  rules: {\n    \/\/ ...\n  },\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b172b4b9fac3a83be203eef762a94b5d\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>If we run our app now, we should see a file appear inside <code>src\/generated<\/code><\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/generated\/i18n.generated.ts<\/span>\n\n<span class=\"hljs-comment\">\/* DO NOT EDIT, file generated by nestjs-i18n *\/<\/span>\n\n<span class=\"hljs-comment\">\/* eslint-disable *\/<\/span>\n<span class=\"hljs-comment\">\/* prettier-ignore *\/<\/span>\n<span class=\"hljs-keyword\">import<\/span> { Path } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"nestjs-i18n\"<\/span>;\n<span class=\"hljs-comment\">\/* prettier-ignore *\/<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">type<\/span> I18nTranslations = {\n    <span class=\"hljs-string\">\"common\"<\/span>: {\n        <span class=\"hljs-string\">\"about\"<\/span>: <span class=\"hljs-built_in\">string<\/span>;\n    };\n};\n<span class=\"hljs-comment\">\/* prettier-ignore *\/<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">type<\/span> I18nPath = Path&lt;I18nTranslations&gt;;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_21408dfe488b216cf4122712fa39b83c\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>This file will aggregate all of our translation keys across message files, generating counterpart keys under the <code>I18nTranslations<\/code> type. We can use <code>I18nTranslations<\/code> along with <code>I18nPath<\/code> to add stronger typing to our <code>t()<\/code> method.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-39\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/yc-i18n\/yc-i18n.service.ts\n\n  import { Injectable } from '@nestjs\/common';\n  import { I18nContext, I18nService } from 'nestjs-i18n';\n<span class=\"hljs-addition\">+ import {<\/span>\n<span class=\"hljs-addition\">+   I18nPath,<\/span>\n<span class=\"hljs-addition\">+   I18nTranslations,<\/span>\n<span class=\"hljs-addition\">+ } from 'src\/generated\/i18n.generated';<\/span>\n\n  @Injectable()\n  export class YcI18nService {\n    constructor(\n<span class=\"hljs-deletion\">-     private readonly i18n: I18nService,<\/span>\n<span class=\"hljs-addition\">+     private readonly i18n: I18nService&lt;I18nTranslations&gt;,<\/span>\n    ) {}\n\n<span class=\"hljs-deletion\">-   t(key: string, options?: Record&lt;string, any&gt;) {<\/span>\n<span class=\"hljs-addition\">+   t(key: I18nPath, options?: Record&lt;string, any&gt;) {<\/span>\n      const lang = I18nContext.current().lang;\n      return this.i18n.translate(key, { lang, ...options });\n    }\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-39\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_c17bda81d20de8c4318f6a97004d1293\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>With that, when we attempt to call <code>t()<\/code> with a translation key that doesn\u2019t exist in our message files, we\u2019ll get a TypeScript error in our code editor.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84405 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/typescript-error-1024x377.png\" alt=\"A screenshot of a TypeScript error in an IDE. The error message states: 'Argument of type '&quot;foo.bar&quot;' is not assignable to parameter of type 'PathImpl2&lt;I18nTranslations&gt;'. ts(2345)'. The cursor is on the line containing return this.i18n.t('foo.bar'); with a red squiggly underline under 'foo.bar'.\" width=\"1024\" height=\"377\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/typescript-error-1024x377.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/typescript-error-300x110.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/typescript-error-768x283.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/typescript-error.png 1272w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0Shut down your app before adding a new translation file and run it afterwards. Otherwise, translations in your new file won\u2019t be added to the generated type file.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Read the <a href=\"https:\/\/nestjs-i18n.com\/guides\/type-safety\">Type Safety<\/a> guide in the official docs.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-translate-basic-text\"><\/span>How do I translate basic text?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Let\u2019s quickly repeat the basic translation steps to refresh our memories before tackling advanced translation. First, we add the translations to existing or new translation files.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-40\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ src\/locales\/en\/routes.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"posts\"<\/span>: {\n    <span class=\"hljs-attr\">\"index\"<\/span>: <span class=\"hljs-string\">\"Index of all posts\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-40\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_1e376b03700193261a22cfe94f88dc84\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-41\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ src\/locales\/ar\/routes.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"posts\"<\/span>: {\n    <span class=\"hljs-attr\">\"index\"<\/span>: <span class=\"hljs-string\">\"\u0641\u0647\u0631\u0633 \u062c\u0645\u064a\u0639 \u0627\u0644\u0645\u0634\u0627\u0631\u0643\u0627\u062a\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-41\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_2b62d0c0b098f98f28d1626631786ee5\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now we can reference the message using the <code>file.key[.subkey...]<\/code> format when using the <code>t()<\/code> function.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-42\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\">i18n.t(<span class=\"hljs-string\">'routes.posts.index'<\/span>);\n\n<span class=\"hljs-comment\">\/\/ When active locale is `en`<\/span>\n<span class=\"hljs-comment\">\/\/ =&gt; 'Index of all posts'<\/span>\n\n<span class=\"hljs-comment\">\/\/ When active loclae is `ar`<\/span>\n<span class=\"hljs-comment\">\/\/ =&gt; '\u0641\u0647\u0631\u0633 \u062c\u0645\u064a\u0639 \u0627\u0644\u0645\u0634\u0627\u0631\u0643\u0627\u062a'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-42\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_e50e5b9588dd2a6269b471c6a866ff7a\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<h2><span class=\"ez-toc-section\" id=\"how-do-i-include-dynamic-values-in-translations\"><\/span>How do I include dynamic values in translations?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We often need to inject values determined at runtime into translation strings. For example, to show the logged-in user&#8217;s name in a translated message. We can use the <code>{variable}<\/code> syntax in translation messages to achieve this.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-43\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ src\/locales\/en\/common.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"greeting\"<\/span>: <span class=\"hljs-string\">\"Hello, {username} \ud83d\udc4b\"<\/span>,\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-43\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_1e376b03700193261a22cfe94f88dc84\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-44\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ src\/locales\/ar\/common.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"greeting\"<\/span>: <span class=\"hljs-string\">\"\u0645\u0631\u062d\u0628\u0627 {username} \ud83d\udc4b\"<\/span>,\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-44\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_fe2e331bf507e38152f741e19bf19cfb\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>In our code, we pass in an object as the second argument to <code>t()<\/code>. The object should house an <code>args<\/code> object with key\/value pairs corresponding to the <code>{variable}<\/code>s we set in our message. <\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-45\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\">\ni18n.t(<span class=\"hljs-string\">\"common.greeting\"<\/span>, { args: { username: <span class=\"hljs-string\">\"Noor\"<\/span> } });\n\n<span class=\"hljs-comment\">\/\/ When active locale is `en`<\/span>\n<span class=\"hljs-comment\">\/\/ =&gt; \"Hello, Noor \ud83d\udc4b\"<\/span>\n<span class=\"hljs-comment\">\/\/ When active locale is `ar`<\/span>\n<span class=\"hljs-comment\">\/\/ =&gt; \"\u0645\u0631\u062d\u0628\u0627 Noor \ud83d\udc4b\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-45\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_8043e95307cf0df6f82b930659bd2d03\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0See the <code>nestjs-i18n<\/code> docs on <a href=\"https:\/\/nestjs-i18n.com\/guides\/formatting\">Formatting<\/a> for more.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-work-with-localized-plurals\"><\/span>How do I work with localized plurals?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Working with plurals is more than \u201csingular and plural\u201d. Different languages have a varying number of plural forms. English has two official plural forms, \u201ca tree\u201d and \u201cmany trees\u201d \u2014\u00a0called <code>one<\/code> and <code>other<\/code>, respectively. Some languages, like Chinese, have one plural form. Arabic and Russian each have six.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0The Unicode CLDR <a href=\"https:\/\/www.unicode.org\/cldr\/charts\/42\/supplemental\/language_plural_rules.html\">Language Plural Rules<\/a> listing is a canonical source for languages\u2019 plural forms.<\/p>\n<p><code>nestjs-i18n<\/code> provides good plural support. Let\u2019s localize our app\u2019s <code>\/today<\/code> route to demonstrate.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0Reminder that you can get the starter demo from the <a href=\"https:\/\/github.com\/PhraseApp-Blog\/nestjs-i18n\/tree\/start\">start branch of our GitHub repo<\/a>.<\/p>\n<p>First, let\u2019s add our translation messages.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-46\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ src\/locales\/en\/today.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"plannedRun\"<\/span>: {\n    <span class=\"hljs-attr\">\"one\"<\/span>: <span class=\"hljs-string\">\"Running a kilometer today\"<\/span>,\n    <span class=\"hljs-attr\">\"other\"<\/span>: <span class=\"hljs-string\">\"Running {count} kilometers today\"<\/span>,\n    <span class=\"hljs-attr\">\"zero\"<\/span>: <span class=\"hljs-string\">\"Resting today\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-46\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_98133a2aee688bd1d8b9e33199c58ccb\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We add the <code>one<\/code> and <code>other<\/code> plural forms for English. <code>nestjs-i18n<\/code> allows an additional <code>zero<\/code> form. Note the interpolated <code>{count}<\/code> variable in the <code>other<\/code> form: It will be replaced with an integer counter at runtime. <\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0See the CLDR <a href=\"https:\/\/www.unicode.org\/cldr\/charts\/42\/supplemental\/language_plural_rules.html\">Language Plural Rules<\/a> for your language\u2019s plural forms e.g. <code>one<\/code>, <code>other<\/code>, etc. The listing places forms under the <strong>Category<\/strong> header.<\/p>\n<p>Now for the Arabic message, which has six forms.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-47\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ src\/locales\/ar\/today.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"plannedRun\"<\/span>: {\n    <span class=\"hljs-attr\">\"zero\"<\/span>: <span class=\"hljs-string\">\"\u0645\u0633\u062a\u0631\u064a\u062d \u0627\u0644\u064a\u0648\u0645\"<\/span>,\n    <span class=\"hljs-attr\">\"one\"<\/span>: <span class=\"hljs-string\">\"\u0633\u0623\u062c\u0631\u064a \u0643\u064a\u0644\u0648\u0645\u062a\u0631 \u0648\u0627\u062d\u062f \u0627\u0644\u064a\u0648\u0645\"<\/span>,\n    <span class=\"hljs-attr\">\"two\"<\/span>: <span class=\"hljs-string\">\"\u0633\u0623\u062c\u0631\u064a \u0643\u064a\u0644\u0648\u0645\u062a\u0631\u064a\u0646 \u0627\u0644\u064a\u0648\u0645\"<\/span>,\n    <span class=\"hljs-attr\">\"few\"<\/span>: <span class=\"hljs-string\">\"\u0633\u0623\u062c\u0631\u064a {count} \u0643\u064a\u0644\u0648\u0645\u062a\u0631\u0627\u062a \u0627\u0644\u064a\u0648\u0645\"<\/span>,\n    <span class=\"hljs-attr\">\"many\"<\/span>: <span class=\"hljs-string\">\"\u0633\u0623\u062c\u0631\u064a {count} \u0643\u064a\u0644\u0648\u0645\u062a\u0631\u0627\u064b \u0627\u0644\u064a\u0648\u0645\"<\/span>,\n    <span class=\"hljs-attr\">\"other\"<\/span>: <span class=\"hljs-string\">\"\u0633\u0623\u062c\u0631\u064a {count} \u0643\u064a\u0644\u0648\u0645\u062a\u0631 \u0627\u0644\u064a\u0648\u0645\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-47\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_950760a9937eca2446c9880c5fbd4a90\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now we can call <code>i18n.t()<\/code> with the parent key and a special <code>count<\/code> integer argument.  <\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-48\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/today\/today.controller.ts\n\n  import { Controller, Get } from '@nestjs\/common';\n<span class=\"hljs-addition\">+ import { YcI18nService } from 'src\/yc-i18n\/yc-i18n.service';<\/span>\n\n  @Controller('today')\n  export class TodayController {\n<span class=\"hljs-addition\">+   constructor(private readonly i18n: YcI18nService) {}<\/span>\n\n    @Get()\n    getTodayInfo() {\n      return {\n<span class=\"hljs-deletion\">-       plannedRun: 'Running 4 kilometers today',<\/span>\n<span class=\"hljs-addition\">+       plannedRun: this.i18n.t('today.plannedRun', {<\/span>\n<span class=\"hljs-addition\">+         args: { count: 2 },<\/span>\n<span class=\"hljs-addition\">+       }),<\/span>\n      };\n    }\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-48\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_32e20186b500f31da39b98197db7ec8f\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>When <code>nestjs-i18n<\/code> sees the <code>count<\/code> arg, it determines that the message is a plural, and uses the value of <code>count<\/code> to determine the correct form for the active locale.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0The counter arg must be called <code>count<\/code>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84411 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-plural-1024x499.png\" alt=\"Our REST client making a GET request to the URL \/today with a query parameter locale=en. The response code is 200, and the response body contains { &quot;plannedRun&quot;: &quot;Running 2 kilometers today&quot; }.\" width=\"1024\" height=\"499\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-plural-1024x499.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-plural-300x146.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-plural-768x375.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-plural.png 1062w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84417 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-plural-1024x494.png\" alt=\"Our REST client making a GET request to the URL \/today with a query parameter locale=ar. The response code is 200, and the response body contains { &quot;plannedRun&quot;: &quot;\u0633\u0623\u062c\u0631\u064a \u0643\u064a\u0644\u0648\u0645\u062a\u0631\u064a\u0646 \u0627\u0644\u064a\u0648\u0645&quot; }.\" width=\"1024\" height=\"494\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-plural-1024x494.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-plural-300x145.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-plural-768x371.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-plural.png 1048w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0If <code>nestjs-i18n<\/code> doesn\u2019t find an expected plural form in your translation message, a server-side error will be thrown.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Check out the <a href=\"https:\/\/nestjs-i18n.com\/guides\/plurals\">Plurals<\/a> <code>nestjs-i18n<\/code> documentation for more info.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Our quick <a href=\"https:\/\/phrase.com\/blog\/posts\/pluralization\/\">Guide to Localizing Plurals<\/a> covers ordinal plurals and other interesting plural-related tidbits.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-the-database\"><\/span>How do I localize the database?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Our demo app has a blog posts resource with full CRUD and database persistence. However, posts currently presume a single locale. What if we wanted a post to be translated into different languages? There are two main strategies for localizing a database entity:<\/p>\n<ul>\n<li>Translation columns on the main table rows e.g. instead of a <code>title<\/code> column, we have <code><code>title_en<\/code><\/code>, <code>title_ar<\/code>, etc.<\/li>\n<li>Translations in a separate table: we move all translations into a separate translation table, creating a 1-n relationship between the main entity and its translations.<\/li>\n<\/ul>\n<p>We\u2019ll explore the simpler translations-columns-on-main-table strategy here. Check out the following resources if you\u2019re interested in the 1-n solution.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0<a href=\"https:\/\/phrase.com\/blog\/posts\/best-database-structure-for-keeping-multilingual-data\/\">What\u2019s the Best Database Structure to Keep Multilingual Data?<\/a> covers both strategies in detail.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0If you want an example of the n-1 strategy implemented in Nest, we have one in our GitHub repo. Check out the <a href=\"https:\/\/github.com\/PhraseApp-Blog\/nestjs-i18n\/tree\/main\/src\/tags\">tags module<\/a> for the example.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"localized-columns-on-the-main-model\"><\/span>Localized columns on the main model<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>For cases when we have a few columns and a few supported languages, it might make sense to simply put our translated fields directly into the main table. Let\u2019s walk through how to do this for our blog posts.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-49\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/posts\/entities\/post.entity.ts\n\nimport {\n  Column,\n  Entity,\n  PrimaryGeneratedColumn,\n} from 'typeorm';\n\n@Entity()\nexport class Post {\n  @PrimaryGeneratedColumn()\n  id: number;\n\n  @Column()\n<span class=\"hljs-deletion\">- title: string;<\/span>\n<span class=\"hljs-addition\">+ title_en: string;<\/span>\n\n<span class=\"hljs-addition\">+ @Column({ nullable: true })<\/span>\n<span class=\"hljs-addition\">+ title_ar: string;<\/span>\n\n  @Column()\n<span class=\"hljs-deletion\">- content: string;<\/span>\n<span class=\"hljs-addition\">+ content_en: string;<\/span>\n\n<span class=\"hljs-addition\">+ @Column({ nullable: true })<\/span>\n<span class=\"hljs-addition\">+ content_ar: string;<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-49\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_af5f40b4d694f0fee70098f8e447a66e\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We assume that English (<code>en<\/code>) is our source\/default locale, and update our entity\u2019s translatable fields with trailing locale suffixes (<code>_en<\/code>). We also add counterpart fields for each supported locale: <code>title_ar<\/code> and <code>content_ar<\/code> for our Arabic post title and body, respectively.<\/p>\n<p>Since English is our source, we\u2019ll always have an English version of a post, with an optional Arabic translation. So we\u2019ve made our Arabic fields nullable. Let\u2019s update our DTO validation to reflect this.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-50\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/posts\/dto\/create-post.dto.ts\n\n  import {\n    IsNotEmpty,\n<span class=\"hljs-addition\">+   IsOptional,<\/span>\n    IsString,\n    Length,\n  } from 'class-validator';\n\n  export class CreatePostDto {\n    @IsString()\n    @IsNotEmpty()\n    @Length(1, 255)\n<span class=\"hljs-deletion\">-   title: string;<\/span>\n<span class=\"hljs-addition\">+   title_en: string;<\/span>\n\n<span class=\"hljs-addition\">+   @IsOptional()<\/span>\n<span class=\"hljs-addition\">+   @IsString()<\/span>\n<span class=\"hljs-addition\">+   @Length(1, 255)<\/span>\n<span class=\"hljs-addition\">+   title_ar: string;<\/span>\n\n    @IsString()\n    @IsNotEmpty()\n<span class=\"hljs-deletion\">-   content: string;<\/span>\n<span class=\"hljs-addition\">+   content_en: string;<\/span>\n\n<span class=\"hljs-addition\">+   @IsOptional()<\/span>\n<span class=\"hljs-addition\">+   @IsString()<\/span>\n<span class=\"hljs-addition\">+   @Length(1, 255)<\/span>\n<span class=\"hljs-addition\">+   content_ar: string;<\/span>\n  }\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-50\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_3563b119a99d06b9d08643f6f8eff710\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>It would be nice to retrieve post translations corresponding to the active locale. For example, a <code>GET \/posts?lang=ar<\/code> request would return our posts with their Arabic titles, omitting the English ones. To achieve this, we need to update our <code>PostsService<\/code>.<\/p>\n<p>First, let\u2019s add a handy <code>lang()<\/code> method to our i18n wrapper service that returns the active locale. <\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-51\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/yc-i18n\/yc-i18n.service.ts\n\n  import { Injectable } from '@nestjs\/common';\n  import { I18nContext, I18nService } from 'nestjs-i18n';\n  import {\n    I18nPath,\n    I18nTranslations,\n  } from 'src\/generated\/i18n.generated';\n\n<span class=\"hljs-addition\">+ export type SupportedLang = 'en' | 'ar';<\/span>\n<span class=\"hljs-addition\">+ export const defaultLang: SupportedLang = 'en';<\/span>\n\n  @Injectable()\n  export class YcI18nService {\n    constructor(\n      private readonly i18n: I18nService&lt;I18nTranslations&gt;,\n    ) {}\n\n    t(key: I18nPath, options?: Record&lt;string, any&gt;) {\n      return this.i18n.translate(key, {\n        lang: this.lang(),\n        ...options,\n      });\n    }\n\n<span class=\"hljs-addition\">+   lang(): SupportedLang {<\/span>\n<span class=\"hljs-addition\">+     return (I18nContext.current()?.lang ||<\/span>\n<span class=\"hljs-addition\">+       defaultLang) as SupportedLang;<\/span>\n<span class=\"hljs-addition\">+   }<\/span>\n  }\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-51\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_1b16f585c4c2cccd8a81703bec34e0d7\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We can now use <code>lang()<\/code> to determine the active locale and return corresponding post titles and content from our <code>PostsService<\/code> read methods.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-52\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ src\/posts\/posts.service.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Injectable } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/common'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { InjectRepository } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@nestjs\/typeorm'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { YcI18nService } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'src\/yc-i18n\/yc-i18n.service'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Repository } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'typeorm'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { CreatePostDto } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/dto\/create-post.dto'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { UpdatePostDto } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/dto\/update-post.dto'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Post } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/entities\/post.entity'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">type<\/span> TranslatedPostSummary = {\n  id: <span class=\"hljs-built_in\">number<\/span>;\n  title: <span class=\"hljs-built_in\">string<\/span>;\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">type<\/span> TranslatedPost = {\n  id: <span class=\"hljs-built_in\">number<\/span>;\n  title: <span class=\"hljs-built_in\">string<\/span>;\n  content: <span class=\"hljs-built_in\">string<\/span>;\n};\n\n<span class=\"hljs-meta\">@Injectable<\/span>()\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">class<\/span> PostsService {\n  <span class=\"hljs-keyword\">constructor<\/span>(<span class=\"hljs-params\">\n    <span class=\"hljs-meta\">@InjectRepository<\/span>(Post)\n    <span class=\"hljs-keyword\">private<\/span> postRepo: Repository&lt;Post&gt;,\n    <span class=\"hljs-keyword\">private<\/span> readonly i18n: YcI18nService,\n  <\/span>) {}\n\n  <span class=\"hljs-keyword\">async<\/span> findAll(): <span class=\"hljs-built_in\">Promise<\/span>&lt;TranslatedPostSummary&#91;]&gt; {\n    <span class=\"hljs-keyword\">const<\/span> lang = <span class=\"hljs-keyword\">this<\/span>.i18n.lang();\n\n    <span class=\"hljs-keyword\">const<\/span> post = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.find({\n      select: &#91;<span class=\"hljs-string\">'id'<\/span>, <span class=\"hljs-string\">`title_<span class=\"hljs-subst\">${lang}<\/span>`<\/span>],\n    });\n\n    <span class=\"hljs-keyword\">return<\/span> post.map(<span class=\"hljs-function\">(<span class=\"hljs-params\"><span class=\"hljs-params\">p<\/span><\/span>) =&gt;<\/span> ({\n      id: p.id,\n      title: p&#91;<span class=\"hljs-string\">`title_<span class=\"hljs-subst\">${lang}<\/span>`<\/span>],\n    }));\n  }\n\n  <span class=\"hljs-keyword\">async<\/span> findOne(\n    id: <span class=\"hljs-built_in\">number<\/span>,\n  ): <span class=\"hljs-built_in\">Promise<\/span>&lt;TranslatedPost | <span class=\"hljs-literal\">null<\/span>&gt; {\n    <span class=\"hljs-keyword\">const<\/span> lang = <span class=\"hljs-keyword\">this<\/span>.i18n.lang();\n\n    <span class=\"hljs-keyword\">const<\/span> post = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.findOne({\n      select: &#91;<span class=\"hljs-string\">'id'<\/span>, <span class=\"hljs-string\">`title_<span class=\"hljs-subst\">${lang}<\/span>`<\/span>, <span class=\"hljs-string\">`content_<span class=\"hljs-subst\">${lang}<\/span>`<\/span>],\n      where: { id },\n    });\n\n    <span class=\"hljs-keyword\">if<\/span> (!post) {\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">null<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> {\n      id: post.id,\n      title: post&#91;<span class=\"hljs-string\">`title_<span class=\"hljs-subst\">${lang}<\/span>`<\/span>],\n      content: post&#91;<span class=\"hljs-string\">`content_<span class=\"hljs-subst\">${lang}<\/span>`<\/span>],\n    };\n  }\n\n  <span class=\"hljs-comment\">\/\/ Our create\/update\/delete methods are<\/span>\n  <span class=\"hljs-comment\">\/\/ unchanged...<\/span>\n\n  create(createPostDto: CreatePostDto): <span class=\"hljs-built_in\">Promise<\/span>&lt;Post&gt; {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.save({ ...createPostDto });\n  }\n\n  <span class=\"hljs-keyword\">async<\/span> update(\n    id: <span class=\"hljs-built_in\">number<\/span>,\n    updatePostDto: UpdatePostDto,\n  ): <span class=\"hljs-built_in\">Promise<\/span>&lt;Post | <span class=\"hljs-literal\">null<\/span>&gt; {\n    <span class=\"hljs-keyword\">const<\/span> post = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.findOneBy({ id });\n\n    <span class=\"hljs-keyword\">if<\/span> (post) {\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.save({\n        ...post,\n        ...updatePostDto,\n      });\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">null<\/span>;\n  }\n\n  <span class=\"hljs-keyword\">async<\/span> remove(id: <span class=\"hljs-built_in\">number<\/span>): <span class=\"hljs-built_in\">Promise<\/span>&lt;<span class=\"hljs-built_in\">boolean<\/span>&gt; {\n    <span class=\"hljs-keyword\">const<\/span> post = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.findOneBy({ id });\n\n    <span class=\"hljs-keyword\">if<\/span> (!post) {\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">false<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.postRepo.remove(post);\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">true<\/span>;\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-52\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_aeeec688e12db8baa3208c8b0155fec7\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We can create posts by providing <code><code>title_en<\/code><\/code> and <code>content_en<\/code> at a minimum.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-84423 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-post-post.png\" alt=\"Our REST client making a POST request to the URL \/posts with form-encoded parameters title_en=&quot;Why I run&quot; and content_en=&quot;There are many reasons...&quot;. The response code is 201 Created, and the response body contains { &quot;id&quot;: 3, &quot;title_en&quot;: &quot;Why I run&quot;, &quot;content&quot;: &quot;There are many reasons...&quot;, title_ar: null, content_ar: null }.\" width=\"932\" height=\"1022\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-post-post.png 932w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-post-post-274x300.png 274w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-post-post-768x842.png 768w\" sizes=\"(max-width: 932px) 100vw, 932px\" \/><\/p>\n<p>We can <code>PATCH<\/code> our Arabic translations for a post at any time.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-84429 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-patch-post.png\" alt=\"Our REST client making a PATCH request to the URL \/posts\/3 with form-encoded parameters title_ar=&quot;\u0644\u0645\u0627\u0630\u0627 \u0623\u0631\u0643\u062f&quot; and content_ar=&quot;\u0647\u0646\u0627\u0643 \u0627\u0644\u0639\u062f\u064a\u062f \u0645\u0646 \u0627\u0644\u0623\u0633\u0628\u0627\u0628&quot;. The response code is 200, and the response body contains { &quot;id&quot;: 3, &quot;title_en&quot;: &quot;Why I run&quot;, &quot;content_en&quot;: &quot;There are many reasons...&quot;, &quot;title_ar&quot;: &quot;\u0644\u0645\u0627\u0630\u0627 \u0623\u0631\u0643\u062f&quot;, &quot;content_ar&quot;: &quot;\u0647\u0646\u0627\u0643 \u0627\u0644\u0639\u062f\u064a\u062f \u0645\u0646 \u0627\u0644\u0623\u0633\u0628\u0627\u0628&quot; }.\" width=\"944\" height=\"978\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-patch-post.png 944w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-patch-post-290x300.png 290w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-patch-post-768x796.png 768w\" sizes=\"(max-width: 944px) 100vw, 944px\" \/><\/p>\n<p>When we <code>GET<\/code> a post, we only see its text for the active locale.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-84435\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-get-post.png\" alt=\"Our REST client making a GET request to the URL \/posts\/3 with the query parameter lang=en. The response code is 200, and the response body contains { &quot;id&quot;: 3, &quot;title&quot;: &quot;Why I run&quot;, &quot;content&quot;: &quot;There are many reasons...&quot;}.\" width=\"938\" height=\"744\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-get-post.png 938w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-get-post-300x238.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-get-post-768x609.png 768w\" sizes=\"(max-width: 938px) 100vw, 938px\" \/><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-84441\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-get-post.png\" alt=\"Our REST client making a GET request to the URL \/posts\/3 with the query parameter lang=ar. The response code is 200, and the response body contains { &quot;id&quot;: 3, &quot;title&quot;: &quot;\u0644\u0645\u0627\u0630\u0627 \u0623\u0631\u0643\u062f&quot;, &quot;content&quot;: &quot;\u0647\u0646\u0627\u0643 \u0627\u0644\u0639\u062f\u064a\u062f \u0645\u0646 \u0627\u0644\u0623\u0633\u0628\u0627\u0628...&quot;}.\" width=\"932\" height=\"706\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-get-post.png 932w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-get-post-300x227.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-get-post-768x582.png 768w\" sizes=\"(max-width: 932px) 100vw, 932px\" \/><\/p>\n<p>This data translation strategy is relatively simple and works well for small models with a few supported locales.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0If you\u2019re working off the <code>start<\/code> branch in our Git repo, delete your <code>db.sqlite<\/code> file after making the above changes. In production, we&#8217;d use <a href=\"https:\/\/typeorm.io\/migrations\">TypeORM migrations<\/a> for a smooth data update, but for this demo we&#8217;re brute-forcing the update, making the new model incompatible with previous data. Deleting the <code>db.sqlite<\/code> file should cause the app to recreate it with our new schema.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-dto-validation-messages\"><\/span>How do I localize DTO validation messages?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><code>nestjs-i18n<\/code> has built-in support for DTO validation when using the <code>class-validator<\/code> package. Let\u2019s utilize this to localize our posts\u2019 DTO validation messages.<\/p>\n<p>First, <code>nestjs-i18n<\/code> gives us a global pipe and filter that we should register in our <code>main.ts<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-53\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/main.ts\n\n  import { NestFactory } from '@nestjs\/core';\n<span class=\"hljs-addition\">+ import {<\/span>\n<span class=\"hljs-addition\">+   I18nValidationExceptionFilter,<\/span>\n<span class=\"hljs-addition\">+   I18nValidationPipe,<\/span>\n<span class=\"hljs-addition\">+ } from 'nestjs-i18n';<\/span>\n  import { AppModule } from '.\/app.module';\n\n  async function bootstrap() {\n    const app = await NestFactory.create(AppModule);\n<span class=\"hljs-addition\">+   app.useGlobalPipes(new I18nValidationPipe());<\/span>\n<span class=\"hljs-addition\">+   app.useGlobalFilters(<\/span>\n<span class=\"hljs-addition\">+     new I18nValidationExceptionFilter({<\/span>\n<span class=\"hljs-addition\">+       detailedErrors: true,<\/span>\n<span class=\"hljs-addition\">+     }),<\/span>\n<span class=\"hljs-addition\">+   );<\/span>\n    await app.listen(3000);\n  }\n  bootstrap();\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-53\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b9a5f21057a61c4f86f7d6b25f4a5f8d\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0See all available options for the <code>I18nValidationExceptionFilter<\/code> in the <a href=\"https:\/\/nestjs-i18n.com\/guides\/dto_validation\/global-validation#i18nvalidationexceptionfilteroptions\">I18nValidationExceptionFilterOptions<\/a> section of the docs.<\/p>\n<p>Now let\u2019s add our validation messages:<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-54\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ src\/locales\/en\/validation.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"required\"<\/span>: <span class=\"hljs-string\">\"Please provide a value for `{property}`\"<\/span>,\n  <span class=\"hljs-attr\">\"length\"<\/span>: <span class=\"hljs-string\">\"`{property}` must be between {constraints.0} and {constraints.1} characters\"<\/span>,\n  <span class=\"hljs-attr\">\"string\"<\/span>: <span class=\"hljs-string\">\"`{property}` must be a string; you provided {value}\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-54\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_1e376b03700193261a22cfe94f88dc84\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-55\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ src\/locales\/ar\/validation.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"required\"<\/span>: <span class=\"hljs-string\">\"\u064a\u0631\u062c\u0649 \u062a\u0642\u062f\u064a\u0645 \u0642\u064a\u0645\u0629 \u0644\u0640 `{property}`\"<\/span>,\n  <span class=\"hljs-attr\">\"length\"<\/span>: <span class=\"hljs-string\">\"\u064a\u062c\u0628 \u0623\u0646 \u064a\u0643\u0648\u0646 \u0639\u062f\u062f \u0623\u062d\u0631\u0641 `{property}` \u0628\u064a\u0646 {constraints.0} \u0648 {constraints.1}.\"<\/span>,\n  <span class=\"hljs-attr\">\"string\"<\/span>: <span class=\"hljs-string\">\"`{property}` \u064a\u062c\u0628 \u0623\u0646 \u062a\u0643\u0648\u0646 \u0633\u0644\u0633\u0644\u0629 (string) \u061b \u0644\u0642\u062f \u0642\u062f\u0645\u062a {value}\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-55\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_361ac2d46660b3aa3dfe7420b6c984ba\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><code>{property}<\/code>, <code>value<\/code>, and <code>{constraints.index}<\/code> will be replaced by the validated property name, given value, and a validation\u2019s constraint values, respectively. We\u2019ll see working in a moment.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0<code>{constraint.0}<\/code> and <code>{constraint.1}<\/code> above are array arguments. Read more about those in the <a href=\"https:\/\/nestjs-i18n.com\/guides\/formatting#array-arguments\">Formatting<\/a> documentation.<\/p>\n<p>We can now use our validation messages in DTO in two ways:<\/p>\n<ul>\n<li>Passing the message key to the validation decorator directly.<\/li>\n<li>Calling the <code>i18nValidationMessage<\/code> function provided by <code>nestjs-i18n<\/code>; We need to use this version when our messages access validation <code>constraints<\/code>.<\/li>\n<\/ul>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-56\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/posts\/dto\/create-post.dto.ts\n\n  import {\n    IsNotEmpty,\n    IsOptional,\n    IsString,\n    Length,\n  } from 'class-validator';\n<span class=\"hljs-addition\">+ import { i18nValidationMessage } from 'nestjs-i18n';<\/span>\n\n  export class CreatePostDto {\n<span class=\"hljs-deletion\">-   @IsString()<\/span>\n<span class=\"hljs-addition\">+   @IsString({ message: 'validation.string' })<\/span>\n<span class=\"hljs-deletion\">-   @Length(1, 255)<\/span>\n<span class=\"hljs-addition\">+   \/\/ 1 and 255 are constraints we want passed to<\/span>\n<span class=\"hljs-addition\">+   \/\/ the validation message, so we use<\/span>\n<span class=\"hljs-addition\">+   \/\/ `i18nValidationMessage` here.<\/span>\n<span class=\"hljs-addition\">+   @Length(1, 255, {<\/span>\n<span class=\"hljs-addition\">+     message: i18nValidationMessage('validation.length'),<\/span>\n<span class=\"hljs-addition\">+   })<\/span>\n    title_en: string;\n\n    @IsOptional()\n<span class=\"hljs-deletion\">-   @IsString()<\/span>\n<span class=\"hljs-addition\">+   @IsString({ message: 'validation.string' })<\/span>\n<span class=\"hljs-deletion\">-   @Length(1, 255)<\/span>\n<span class=\"hljs-addition\">+   @Length(1, 255, {<\/span>\n<span class=\"hljs-addition\">+     message: i18nValidationMessage('validation.length'),<\/span>\n<span class=\"hljs-addition\">+   })<\/span>\n    title_ar: string;\n\n<span class=\"hljs-deletion\">-   @IsString()<\/span>\n<span class=\"hljs-addition\">+   @IsString({ message: 'validation.string' })<\/span>\n<span class=\"hljs-deletion\">-   @IsNotEmpty()<\/span>\n<span class=\"hljs-addition\">+   @IsNotEmpty({ message: 'validation.required' })<\/span>\n    content_en: string;\n\n    @IsOptional()\n<span class=\"hljs-deletion\">-   @IsString()<\/span>\n<span class=\"hljs-addition\">+   @IsString({ message: 'validation.string' })<\/span>\n<span class=\"hljs-deletion\">-   @Length(1, 255)<\/span>\n<span class=\"hljs-addition\">+   @Length(1, 255, {<\/span>\n<span class=\"hljs-addition\">+     message: i18nValidationMessage('validation.length'),<\/span>\n<span class=\"hljs-addition\">+   })<\/span>\n    content_ar: string;\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-56\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_aa7cb3b2a723424c3f01b83f856faf89\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now our validation messages will be translated for the active locale in the request.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84448 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-validation-747x1024.png\" alt=\"Our REST client making a POST request to the URL \/posts. The request results in a 400 Bad Request status. The response body is a JSON object detailing validation errors. It includes a &#96;statusCode&#96; of 400, a &#96;message&#96; stating 'Bad Request', and an &#96;errors&#96; array with two objects. The first object has a &#96;property&#96; of &#96;title_en&#96;, an empty &#96;target&#96; object, no &#96;children&#96;, and &#96;constraints&#96; including 'isLength': '&#96;title_en&#96; must be between 1 and 255 characters' and 'isString': '&#96;title_en&#96; must be a string; you provided undefined'. The second object has a &#96;property&#96; of &#96;content_en&#96;, an empty &#96;target&#96; object, no &#96;children&#96;, and &#96;constraints&#96; including 'isNotEmpty': 'Please provide a value for &#96;content_en&#96;' and 'isString': '&#96;content_en&#96; must be a string; you provided undefined'.\" width=\"747\" height=\"1024\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-validation-747x1024.png 747w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-validation-219x300.png 219w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-validation-768x1053.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/en-validation.png 1024w\" sizes=\"(max-width: 747px) 100vw, 747px\" \/><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-84460 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-validation-1-745x1024.png\" alt=\"Our REST client making a POST request to the URL \/posts?lang=ar. The request results in a 400 Bad Request status. The response body is a JSON object detailing validation errors in Arabic. It includes a &#96;statusCode&#96; of 400, a &#96;message&#96; stating 'Bad Request', and an &#96;errors&#96; array with two objects. The first object has a &#96;property&#96; of &#96;title_en&#96;, an empty &#96;target&#96; object, no &#96;children&#96;, and &#96;constraints&#96; including 'isLength': '\u064a\u062c\u0628 \u0623\u0646 \u064a\u0643\u0648\u0646 \u0639\u062f\u062f \u0623\u062d\u0631\u0641 &#96;title_en&#96; \u0628\u064a\u0646 1 \u0648 255.', and 'isString': '\u064a\u062c\u0628 \u0623\u0646 \u062a\u0643\u0648\u0646 \u0633\u0644\u0633\u0644\u0629 &#96;title_en&#96; (string); \u0644\u0642\u062f \u0642\u062f\u0645\u062a undefined'. The second object has a &#96;property&#96; of &#96;content_en&#96;, an empty &#96;target&#96; object, no &#96;children&#96;, and &#96;constraints&#96; including 'isNotEmpty': '\u064a\u0631\u062c\u0649 \u062a\u0642\u062f\u064a\u0645 \u0642\u064a\u0645\u0629 \u0644&#96;content_en&#96;', and 'isString': '\u064a\u062c\u0628 \u0623\u0646 \u062a\u0643\u0648\u0646 \u0633\u0644\u0633\u0644\u0629 &#96;content_en&#96; (string); \u0644\u0642\u062f \u0642\u062f\u0645\u062a undefined'.\" width=\"745\" height=\"1024\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-validation-1-745x1024.png 745w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-validation-1-218x300.png 218w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-validation-1-768x1055.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/05\/ar-validation-1.png 984w\" sizes=\"(max-width: 745px) 100vw, 745px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0If you want to validate your DTO manually, check out the <a href=\"https:\/\/nestjs-i18n.com\/guides\/dto_validation\/manual-validation\">Manual validation<\/a> section of the <code>nestjs-i18n<\/code> docs.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0<a href=\"https:\/\/github.com\/PhraseApp-Blog\/nestjs-i18n\">Check out all the code for the demo on GitHub<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"upgrade-your-nest-localization\"><\/span>Upgrade your Nest localization<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We hope you\u2019ve found this guide to localizing NestJS with <code>nestjs-i18n<\/code> library helpful.<\/p>\n<p>When you\u2019re all set to start the translation process, rely on the Phrase Localization Platform to handle the heavy lifting. A specialized software localization platform, the Phrase Localization Platform comes with dozens of tools designed to automate your translation process and native integrations with platforms like GitHub, GitLab, and Bitbucket.<\/p>\n<p>With its user-friendly strings editor, translators can effortlessly access your content and transfer it into your target languages. Once your translations are set, easily integrate them back into your project with a single command or automated sync.<\/p>\n<p>This way, you can stay focused on what you love\u2014your code. <a href=\"https:\/\/eu.phrase.com\/idm-ui\/signup?uiLang=en-US\">Sign up for a free trial<\/a> and see for yourself why developers appreciate using The Phrase Localization Platform for software localization.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>We localize a Nest app with nestjs-i18n, working through controller, service, and database localization.<\/p>\n","protected":false},"author":41,"featured_media":85868,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_stopmodifiedupdate":false,"_modified_date":"","_searchwp_excluded":"","footnotes":""},"categories":[40],"class_list":["post-84321","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-localization"],"acf":[],"_links":{"self":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/84321"}],"collection":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/comments?post=84321"}],"version-history":[{"count":12,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/84321\/revisions"}],"predecessor-version":[{"id":84469,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/84321\/revisions\/84469"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media\/85868"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media?parent=84321"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/categories?post=84321"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}