<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: vaclavhodek</title>
    <description>The latest articles on DEV Community by vaclavhodek (@vaclavhodek).</description>
    <link>https://dev.to/vaclavhodek</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F373685%2F0699bf1f-81d7-4063-a3d6-abf3f5f0dca5.jpeg</url>
      <title>DEV Community: vaclavhodek</title>
      <link>https://dev.to/vaclavhodek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vaclavhodek"/>
    <language>en</language>
    <item>
      <title>Automated Localization: Localazy ❤Bitrise.io</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Sat, 13 Mar 2021 20:35:16 +0000</pubDate>
      <link>https://dev.to/localazy/automated-localization-localazy-bitrise-io-3j1l</link>
      <guid>https://dev.to/localazy/automated-localization-localazy-bitrise-io-3j1l</guid>
      <description>&lt;h2&gt;
  
  
  Automated Localization
&lt;/h2&gt;

&lt;p&gt;You have probably already heard of continuous localization. It's important as software development is a never-ending process, and with new features, you usually need to add new strings.&lt;/p&gt;

&lt;p&gt;Fully automated localization is a level above it. As a developer, you only set up it once, and then, you can forget about it completely. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is Bitrise?
&lt;/h2&gt;

&lt;p&gt;If you haven't heard about &lt;a href="http://bitrise.io"&gt;Bitrise.io&lt;/a&gt; yet, it's CD/CI made for mobile apps developers. It has an awesome UI with a visual workflow editor. You just build the automation out of boxes (called steps) and there are many of them made for us, mobile app developers. &lt;/p&gt;

&lt;p&gt;Of course, if you prefer code, you can build your workflow in YAML in a similar way you would do with Github Actions, and there is also a powerful CLI tool available!&lt;/p&gt;

&lt;p&gt;You can start in minutes with Bitrise and save hours with automated build and deployment. &lt;/p&gt;

&lt;h2&gt;
  
  
  Localazy Gradle plugin
&lt;/h2&gt;

&lt;p&gt;It's simple to run Gradle build tasks on Bitrise, and so if you use our Localazy Gradle plugin for Android, it's supported out of the box. No action is needed. &lt;/p&gt;

&lt;h2&gt;
  
  
  Let's try it out!
&lt;/h2&gt;

&lt;p&gt;Let's suppose that your mobile app is ready for localization, and strings in the source language are stored in Android XML, iOS' strings, Flutter's ARB, JSON, or some other common format. &lt;/p&gt;

&lt;p&gt;You can configure Bitrise manually for anything you want - it may not even be a mobile app - and there are predefined configurations for iOS, Android, Xamarin, macOS, Cordova, ionic, React Native and Flutter. Localazy supports usual localizable files for all of these platforms. A perfect match!&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Localazy
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://localazy.com"&gt;Sign up with Localazy&lt;/a&gt;, create a new project, &lt;a href="https://localazy.com/docs/cli/installation"&gt;install the CLI tool&lt;/a&gt;, and then create and test your &lt;code&gt;localazy.json&lt;/code&gt; configuration. You should be able to upload the source language files and download localized ones. &lt;/p&gt;

&lt;p&gt;My configuration is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"readKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"writeKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"upload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"locales/en.json"&lt;/span&gt;&lt;span class="w"&gt;         
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"download"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"locales/${lang}.json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this moment on, Localazy is the place to manage your translations, translators and contributors. And as a bonus, &lt;strong&gt;it translates your app to up to 80 languages for free&lt;/strong&gt; by sharing translations with another app. &lt;/p&gt;

&lt;p&gt;Creating and testing &lt;code&gt;localazy.json&lt;/code&gt; is essential as it's used by the Bitrise step described below. &lt;/p&gt;

&lt;p&gt;We are going to automate downloading translations from Localazy when the app is built on Bitrise. It's also possible to automatically upload strings to Localazy, e.g., &lt;a href="https://localazy.com/blog/automated-localization-github-actions-localazy"&gt;when you push your app to Github&lt;/a&gt; or with &lt;a href="https://localazy.com/blog/automated-localization-gitlab-cicd-localazy"&gt;Gitlab CI/CD&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To use Bitrise, push your app to Github, Gitlab or Bitbucket.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Ignore Localized Files
&lt;/h2&gt;

&lt;p&gt;The source language file, in my case &lt;code&gt;en.json&lt;/code&gt;, is the source of truth and for localized files, it's the latest version on Localazy. I don't need those files to be committed to my repository. &lt;/p&gt;

&lt;p&gt;With a simple &lt;code&gt;.gitignore&lt;/code&gt; file placed in &lt;code&gt;locales&lt;/code&gt; folder, I can filter out all localized files except for the source language one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*.json
!en.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Your App on Bitrise
&lt;/h2&gt;

&lt;p&gt;Head to &lt;a href="https://bitrise.io"&gt;bitrise.io&lt;/a&gt; and set up a new account if you haven't one yet. Once you are done, &lt;strong&gt;Add New App&lt;/strong&gt; and choose to &lt;strong&gt;Add New App on web UI&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Follow the visual guide to get everything configured for your app. It's straight forward. You add a repository and select branch, and Bitrise autodetects your app and preconfigures it. Neat!&lt;/p&gt;

&lt;p&gt;Once configured, Bitrise automatically starts the first build and you should see something like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---lR8rWNs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---lR8rWNs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/1.png" alt="https://content.localazy.com/bitrise/1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations! Your app is just being built with Bitrise!&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Localazy step
&lt;/h2&gt;

&lt;p&gt;Wait for the build to finish and then Open Workflow Editor (the button is just above the console output):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0Yrll0oa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0Yrll0oa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/2.png" alt="https://content.localazy.com/bitrise/2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've created a manual project to focus on continuous localization, so my workflow is simple and looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BfgUUJrs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BfgUUJrs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/3.png" alt="https://content.localazy.com/bitrise/3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the &lt;strong&gt;plus button&lt;/strong&gt; where it makes sense to download updated translations and search for &lt;strong&gt;Localazy CLI&lt;/strong&gt; in the list of available steps. You may need to switch to show ALL of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MgNTbyNG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MgNTbyNG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/4.png" alt="https://content.localazy.com/bitrise/4.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And my new workflow with the Localazy step is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lnInYBAY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lnInYBAY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/5.png" alt="https://content.localazy.com/bitrise/5.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Access Keys
&lt;/h2&gt;

&lt;p&gt;I've decided not to commit my read and write keys for Localazy to the public repository (probably a good idea) and so I need to configure them on Bitrise. &lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;Localazy CLI step&lt;/strong&gt; to see available configuration options. There is a lot of them, so you can configure Localazy for your need. &lt;/p&gt;

&lt;p&gt;But now, we only need to setup &lt;strong&gt;Read Key&lt;/strong&gt; and &lt;strong&gt;Write Key&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fISRpJ19--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fISRpJ19--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/6.png" alt="https://content.localazy.com/bitrise/6.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They are both secret variables, so don't worry about them being exposed even if you have your app public on Bitrise. &lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Localization... Done.
&lt;/h2&gt;

&lt;p&gt;Leave the Workflow Editor and re-run the build. As you can see in the screenshot below, Localazy CLI is invoked and updated translations are downloaded. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pYla66h5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pYla66h5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/bitrise/7.png" alt="https://content.localazy.com/bitrise/7.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Words
&lt;/h2&gt;

&lt;p&gt;It's simple enough to completely remove the localization burden from your shoulders with &lt;a href="https://localazy.com"&gt;Localazy&lt;/a&gt; and &lt;a href="https://bitrise.io"&gt;Bitrise&lt;/a&gt;. No more file handling. All the translations and build steps of your lovely app can be managed in a single place with a beautiful UI designed for developers.&lt;/p&gt;

</description>
      <category>bitrise</category>
      <category>localization</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Localazy is on ProductHunt. Can you help?</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Thu, 03 Dec 2020 15:44:46 +0000</pubDate>
      <link>https://dev.to/vaclavhodek/localazy-is-on-producthunt-can-you-help-df4</link>
      <guid>https://dev.to/vaclavhodek/localazy-is-on-producthunt-can-you-help-df4</guid>
      <description>&lt;p&gt;Hello, everybody,&lt;/p&gt;

&lt;p&gt;we've just launched &lt;a href="https://www.producthunt.com/posts/localazy"&gt;Localazy&lt;/a&gt; on Product Hunt. Would be so kind and upvote it?&lt;/p&gt;

&lt;p&gt;There's 75% discount for all hunters! ❤️&lt;/p&gt;

&lt;p&gt;The link: &lt;a href="https://www.producthunt.com/posts/localazy"&gt;https://www.producthunt.com/posts/localazy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks in advance and thanks for this amazing community!&lt;/p&gt;

</description>
      <category>localazy</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to localise NodeJS with Polyglot.js and Localazy</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Wed, 25 Nov 2020 13:53:36 +0000</pubDate>
      <link>https://dev.to/localazy/how-to-localise-nodejs-with-polyglot-js-and-localazy-2j7c</link>
      <guid>https://dev.to/localazy/how-to-localise-nodejs-with-polyglot-js-and-localazy-2j7c</guid>
      <description>&lt;p&gt;Do you use Polyglot.js to localize your NodeJS app? Or maybe you're looking for a tool to use? That's great, but tell me, how easily can you manage the translation files? Not really, huh? In this article, I'll give you a short introduction to translation management with Polyglot.js and Localazy.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is Localazy?
&lt;/h1&gt;

&lt;p&gt;Localazy is a translation management platform and it's a great option for developers because of several key features. My favorite ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It comes with &lt;a href="https://localazy.com/docs/general/what-is-localazy-sharetm"&gt;ShareTM&lt;/a&gt; allowing you to automatically translate your app into 80+ languages for free by sharing translations with other developers.&lt;/li&gt;
&lt;li&gt;With its &lt;a href="https://localazy.com/docs/cli/the-basics"&gt;CLI&lt;/a&gt;, it can be easily integrated into any workflow. And we are going to use the CLI today. &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Getting started with Localazy
&lt;/h1&gt;

&lt;p&gt;Create a &lt;a href="https://localazy.com"&gt;Localazy&lt;/a&gt; account and create a new app. I will use English as the source language, but you can choose any other. Then on the integration screen, select JSON. We will upload the source strings in a bit.&lt;/p&gt;

&lt;p&gt;Afterward, you can &lt;a href="https://localazy.com/docs/cli/installation"&gt;install&lt;/a&gt; Localazy's CLI for Linux, macOS, or Windows.&lt;/p&gt;

&lt;p&gt;Come back to your project. In the root folder, create a file called &lt;code&gt;localazy.json&lt;/code&gt; and paste the following. Make sure to fill in your &lt;em&gt;writeKey&lt;/em&gt; and &lt;em&gt;readKey&lt;/em&gt; which you can retrieve from your app either under the settings tab or in the first step of the JSON CLI guide on the select integration screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"writeKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-apps-write-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"readKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-apps-read-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"upload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"locales/en.json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"download"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"locales/${lang}.json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can upload the source strings. Create &lt;code&gt;en.json&lt;/code&gt; in &lt;em&gt;locales&lt;/em&gt; folder and edit as needed. If you are using another language as your source, replace &lt;code&gt;en&lt;/code&gt; with the correct locale.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"appName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Cool App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"An error has occurred."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello %{name}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run &lt;code&gt;localazy upload&lt;/code&gt; and you should see your source strings in your app in Localazy. At this point, you may add new languages, for which you can use automatic or manual translations. &lt;strong&gt;Automatic translations&lt;/strong&gt; use highly accurate community translations, so they are generally very precise. However, they support translations only from English at the moment, so you need to have the English language added. It does not have to be your source language though.&lt;/p&gt;

&lt;p&gt;Before downloading, you need to review automatically translated strings. Otherwise, they have only a candidate status and won't be published. In case you, as an owner, translate anything, the strings are automatically accepted without the review process. Try to add German language and review the suggested phrases or translate them manually (it does not need to be proper German ツ).&lt;/p&gt;

&lt;p&gt;Once you have approved the translations, you can run &lt;code&gt;localazy download&lt;/code&gt; to gather edited files.&lt;/p&gt;

&lt;h1&gt;
  
  
  Development
&lt;/h1&gt;

&lt;p&gt;Install &lt;a href="https://npm.im/node-polyglot"&gt;node-polyglot&lt;/a&gt; and fs via npm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;node-polyglot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your code add the following to your &lt;code&gt;index.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Polyglot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-polyglot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;polyglots&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// here we will store all our different locales&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translationFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./locales&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// gather our locales&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;translationFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./locales/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Polyglot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// load all translations into it&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;polyglots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localeName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// sets the locale name and the Polyglot instance&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;translate&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;polyglots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd like, you can name the file something like &lt;code&gt;translate.js&lt;/code&gt; to turn it into a module. You'd also add &lt;code&gt;module.exports = translate&lt;/code&gt; to the end of the file. Then you can &lt;code&gt;const translate = require('./translate.js')&lt;/code&gt; to get the function. &lt;/p&gt;

&lt;p&gt;Now, to translate anything, use the &lt;code&gt;translate()&lt;/code&gt; function, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Daniel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run &lt;code&gt;node index.js&lt;/code&gt;, you should see a console log of the phrase &lt;em&gt;hello&lt;/em&gt; translated into German.&lt;/p&gt;

&lt;p&gt;Implement this to fit your code, and you are all set! You may check out the final repo &lt;a href="https://github.com/danielnewell/polyglotjs-sample"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you run into issues, please leave a message and I will fix it as soon as possible.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;This article was originally written by &lt;a href="https://dev.to/danielnewell"&gt;Daniel Newell&lt;/a&gt; and reposted with permission.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>node</category>
      <category>i18n</category>
    </item>
    <item>
      <title>How to: Localise your React App with LinguiJS and Localazy</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Fri, 20 Nov 2020 11:25:51 +0000</pubDate>
      <link>https://dev.to/localazy/how-to-localise-your-react-app-with-linguijs-and-localazy-489d</link>
      <guid>https://dev.to/localazy/how-to-localise-your-react-app-with-linguijs-and-localazy-489d</guid>
      <description>&lt;p&gt;Do you have an app that you use and you want to localise it? Localising is very important to ensure that all people can use your app with ease. With &lt;a href="https://localazy.com"&gt;Localazy&lt;/a&gt; and &lt;a href="https://lingui.js.org/"&gt;LinguiJS&lt;/a&gt; you can achieve it easily (and for free)!&lt;/p&gt;

&lt;p&gt;Localazy is a free &lt;strong&gt;translation management&lt;/strong&gt; system to help developers and translators focus on their jobs and make the translation process seamless. It provides a pro-active translation memory called &lt;a href="https://localazy.com/docs/general/what-is-localazy-sharetm"&gt;ShareTM&lt;/a&gt; which provides highly accurate automatic translations for even easier localization.&lt;/p&gt;

&lt;h2&gt;
  
  
  App setup
&lt;/h2&gt;

&lt;p&gt;Let's start with the React app. Create a new project with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;npx&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="nx"&gt;localazy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;
&lt;span class="nx"&gt;cd&lt;/span&gt; &lt;span class="nx"&gt;localazy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the newly created project and install &lt;strong&gt;LinguiJS&lt;/strong&gt; for in-app translation management.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="k"&gt;npm&lt;/span&gt; install --save-dev @lingui/cli @babel/core babel-core@bridge
&lt;span class="k"&gt;npm&lt;/span&gt; install --save-dev @lingui/macro babel-plugin-macros  &lt;span class="c1"&gt;# required for macros&lt;/span&gt;
&lt;span class="k"&gt;npm&lt;/span&gt; install --save @lingui/react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Localazy setup and integration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://localazy.com/register"&gt;Sign up on Localazy&lt;/a&gt; and create a new app. I will use English as a source language, but you can choose any other. Then on the integration screen, select JSON. We will upload the source strings in a bit.&lt;/p&gt;

&lt;p&gt;Afterwards, you can &lt;a href="https://localazy.com/docs/cli/installation"&gt;install&lt;/a&gt; Localazy's CLI for Linux, MacOS or Windows.&lt;/p&gt;

&lt;p&gt;Come back to your project. In the root folder, create a file called &lt;code&gt;localazy.json&lt;/code&gt; and paste the following. Make sure to fill in your &lt;em&gt;writeKey&lt;/em&gt; and &lt;em&gt;readKey&lt;/em&gt; which you can retrieve from your app either under the settings tab or in the first step of the JSON CLI guide on the select integration screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"writeKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-write-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"readKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-read-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"upload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/locales/en.json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"download"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/locales/${lang}.json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we need to create a file called &lt;code&gt;.linguirc&lt;/code&gt; in our root directory. Fill it in as so, make sure to include whatever locales you use. In this article we'll use &lt;em&gt;en&lt;/em&gt; and &lt;em&gt;es&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"catalogs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/locales/{locale}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"sourceLocale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"locales"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"minimal"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterwards, we need to do a little modification to your &lt;code&gt;package.json&lt;/code&gt; file. Add the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="nl"&gt;"localise"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localazy download &amp;amp;&amp;amp; lingui extract &amp;amp;&amp;amp; lingui compile"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can create a &lt;code&gt;./src/locales&lt;/code&gt; folder. This is the folder in which we will store all of our locale files. Create a file called &lt;code&gt;en.json&lt;/code&gt; in the &lt;code&gt;./src/locales&lt;/code&gt; folder and paste the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome, {name}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"today"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Today is {date}."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"thatsAll"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"That's all for today!"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can change these as you please. That's just an example of the kinds of things you can do. We are almost ready. Let's upload the source English phrases to Localazy. Run &lt;code&gt;localazy upload&lt;/code&gt; and you should see your strings on the project screen (you need to refresh the page). ✨ Magical! ✨&lt;/p&gt;

&lt;p&gt;Go to Settings and scroll down. You should see three options. Make sure &lt;code&gt;Use community translations (ShareTM)&lt;/code&gt; is switched on.&lt;/p&gt;

&lt;p&gt;Learn more about &lt;a href="https://dev.to/docs/general/what-is-localazy-sharetm"&gt;how ShareTM works&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M7zphgZk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hmvi4dhdk991m57y06n8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M7zphgZk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hmvi4dhdk991m57y06n8.png" alt="Options for ShareTM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, you may add new languages, for which you can use automatic or manual translations. Automatic translations use highly accurate community translations, so they are generally very precise. However, they support translations only from English at the moment, so you need to have the English language added. It does not have to be your source language though.&lt;/p&gt;

&lt;p&gt;Before downloading, you need to review automatically translated strings. Otherwise, they have only a candidate status and won't be published. In case you, as an owner, translate anything, the strings are automatically accepted without the review process. Try to add Spanish language and review the suggested phrases or translate them manually (it does not need to be proper Spanish).&lt;/p&gt;

&lt;p&gt;Now, run &lt;code&gt;npm run localise&lt;/code&gt; to download these new files and set them up with LinguiJS. And just like that, your app is almost done! It's time to get into the nitty-gritty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Localazy translations with LinguiJS
&lt;/h2&gt;

&lt;p&gt;Here, we are going to give a brief example. LinguiJS already has an amazing guide &lt;a href="https://lingui.js.org/tutorials/react.html"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;index.js&lt;/code&gt; file, add these lines:&lt;/p&gt;

&lt;p&gt;Change these imports to match your project. We are just creating a basic welcome page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Welcome&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./welcome.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@lingui/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;I18nProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@lingui/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// or whatever you need it to be&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;catalog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./locales/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.js`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;I18nProvider&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Welcome&lt;/span&gt;  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Joe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/I18nProvider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in your &lt;code&gt;welcome.js&lt;/code&gt; file, paste this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Trans&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@lingui/macro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Welcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Date will not be translated, you can use more advanced techniques to do so&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Trans&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;welcome&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Trans&amp;gt; {name}&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Trans&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Trans&amp;gt; {date}&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;footer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Trans&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;thatsAll&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Trans&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;footer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;   &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Welcome&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, to translate with Lingui, we have surrounded the keys with the &lt;code&gt;&amp;lt;Trans&amp;gt;&amp;lt;/Trans&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;You may check out the final repo &lt;a href="https://github.com/danielnewell/linguijs-sample"&gt;here&lt;/a&gt;. Remember, this is just a sample, you will have to tweak this to fit into your application, however, this is a great first step to ensure that your project is ready to be localized and allow everyone to use your app, regardless of what languages they speak.&lt;/p&gt;

&lt;p&gt;As always, please post your questions, comments, and concerns below. &lt;/p&gt;




&lt;p&gt;This article was originally written by &lt;a href="https://dev.to/danielnewell"&gt;Daniel Newell&lt;/a&gt; and reposted with permission.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>react</category>
      <category>localization</category>
    </item>
    <item>
      <title>How I localized my media player app into 50 languages for free</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Thu, 19 Nov 2020 10:28:38 +0000</pubDate>
      <link>https://dev.to/localazy/how-i-localized-my-media-player-app-into-50-languages-for-free-cp6</link>
      <guid>https://dev.to/localazy/how-i-localized-my-media-player-app-into-50-languages-for-free-cp6</guid>
      <description>&lt;p&gt;Recently, I worked on a small media player app for Android to play a list of tracks in a given folder on the SD card. One of the requirements was to get the app translated to more languages.&lt;/p&gt;

&lt;p&gt;There were only a few strings at the time, but the app was just a proof of concept, and it was supposed to be improved in the future.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;resources&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button_play"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Play&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button_pause"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Pause&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button_stop"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Stop&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button_next"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Next&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button_previous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Previous&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"menu_tracks"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tracks&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"info_playing"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Now playing: %1$s&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/resources&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was looking for a simple solution to manage translations and found &lt;a href="https://localazy.com" rel="noopener noreferrer"&gt;Localazy&lt;/a&gt;. It's free, and it promised to translate my app into 80 languages for free by sharing translations with other apps, so I decided to give it a try. &lt;/p&gt;

&lt;p&gt;I signed up, followed the instructions, and integrated Localazy with my Gradle script. It was simple, and I didn't need to touch the source code or handle XML files. Neat!&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;uploadStrings&lt;/code&gt; Gradle command added by Localazy, I uploaded strings. In a few seconds, new languages are available for my app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2F80_langs%2Favailable_langs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2F80_langs%2Favailable_langs.png" alt="Available languages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I added about 50 of then to my app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2F80_langs%2Fadded_langs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2F80_langs%2Fadded_langs.png" alt="News languages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then reviewed them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2F80_langs%2Freview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2F80_langs%2Freview.png" alt="Review languages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The review is awesome as it allowed me to check that placeholders are correct, etc. &lt;/p&gt;

&lt;p&gt;Honestly, the offered translations are extremely accurate. I tried on another of my apps too, and it's way better than Google Translate. If the string is not available through the shared translations, there are machine translations from Amazon and Google. &lt;/p&gt;

&lt;p&gt;Adding a new language to my app took me just a few minutes. And what I totally love about &lt;a href="https://localazy.com" rel="noopener noreferrer"&gt;Localazy&lt;/a&gt; is that I only cleaned my project and rebuilt it to have all the languages included in the app! Nothing else needed.&lt;/p&gt;

&lt;p&gt;It's not that shocking that my simple app was 100% translated easily, as my strings are common. However, the whole process is enjoyable and seamless!&lt;/p&gt;




&lt;p&gt;This post is translated and republished with the consent of the original author.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>localization</category>
      <category>localazy</category>
    </item>
    <item>
      <title>Automated Localization: Github Actions ❤ Localazy</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Fri, 13 Nov 2020 10:54:13 +0000</pubDate>
      <link>https://dev.to/localazy/automated-localization-github-actions-localazy-33do</link>
      <guid>https://dev.to/localazy/automated-localization-github-actions-localazy-33do</guid>
      <description>&lt;h2&gt;
  
  
  Automated Localization
&lt;/h2&gt;

&lt;p&gt;You have probably already heard of continuous localization. It's important as software development is a never-ending process, and with new features, you usually need to add new strings.&lt;/p&gt;

&lt;p&gt;Fully automated localization is a level above it. As a developer, you only set up it once, and then, you can forget about it completely. &lt;/p&gt;

&lt;p&gt;And as we are going to use &lt;strong&gt;&lt;a href="https://github.com/features/actions"&gt;Github Actions&lt;/a&gt;&lt;/strong&gt;, it's gonna be fun!&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Localization Platform
&lt;/h2&gt;

&lt;p&gt;Let's suppose that your mobile, desktop or web app is ready for localization, and strings in the source language are stored in JSON, YAML, iOS' strings, Flutter's ARB, or some other common format. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://localazy.com"&gt;Sign up with Localazy&lt;/a&gt;, create a new project, &lt;a href="https://localazy.com/docs/cli/installation"&gt;install the CLI tool&lt;/a&gt;, and then create and test your &lt;code&gt;localazy.json&lt;/code&gt; configuration. You should be able to upload the source language files and download localized ones. &lt;/p&gt;

&lt;p&gt;My configuration is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s"&gt;"readKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"read-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"writeKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"write-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="s"&gt;"upload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"locales/en.json"&lt;/span&gt;         
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="s"&gt;"download"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"locales/${lang}.json"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this moment on, Localazy is the place to manage your translations, translators, and contributors. And as a bonus, &lt;strong&gt;it translates your app to up to 80 languages for free&lt;/strong&gt; by sharing translations with other apps. &lt;/p&gt;

&lt;p&gt;Creating and testing &lt;code&gt;localazy.json&lt;/code&gt; is essential as it's used by the Github actions described below. &lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Secrets
&lt;/h2&gt;

&lt;p&gt;Private data should not be committed, and so, before the commit, let's remove the read and write keys from the configuration file above and instead place them to Github Secrets. &lt;/p&gt;

&lt;p&gt;Create a repository on Github if you haven't yet, go to &lt;strong&gt;Settings &amp;gt; Secrets&lt;/strong&gt;, and add keys here. My configuration looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_E3ETZmx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/github_actions/secrets.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_E3ETZmx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/github_actions/secrets.png" alt="https://content.localazy.com/github_actions/secrets.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ignore Localized Files
&lt;/h2&gt;

&lt;p&gt;The source language file - in my case &lt;code&gt;en.json&lt;/code&gt; - is the source of truth, and for localized files, it's the latest version on Localazy. I don't need those files to be committed to my repository. &lt;/p&gt;

&lt;p&gt;With a simple &lt;code&gt;.gitignore&lt;/code&gt; file placed in the &lt;code&gt;locales&lt;/code&gt; folder, I can filter out all localized files except for the source language one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!en.json
*.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automate Uploading
&lt;/h2&gt;

&lt;p&gt;If you are not familiar with &lt;a href="https://github.com/features/actions"&gt;Github Actions&lt;/a&gt;, I recommend reading more about it. It may take some time to get used to it, but it saves you a lot of time. &lt;a href="https://localazy.com/blog/localazy-cli-sofware-localization-tool-kotlin-github"&gt;We use it extensively for building releases of our Localazy CLI&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The configuration below is all I need for uploading the source language file &lt;code&gt;en.json&lt;/code&gt; to Localazy with each commit that changes it. I add a new feature, add new strings to &lt;code&gt;en.json&lt;/code&gt;, commit it, and push it. Done. &lt;/p&gt;

&lt;p&gt;And all you need to do now is to create &lt;code&gt;.github/workflows/upload.yml&lt;/code&gt; file with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to Localazy&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;locales/en.json'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;localazy-upload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload strings to Localazy&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localazy/upload@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;read_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LOCALAZY_READ_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;write_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LOCALAZY_WRITE_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to fine-tune it, there is &lt;a href="https://github.com/marketplace/actions/localazy-upload"&gt;the documentation for the upload action on Github Marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's also simple to configure the &lt;code&gt;on&lt;/code&gt; clause to upload strings to Localazy only for releases, certain tags or branches. &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#on"&gt;See its configuration on Github docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On each push touching &lt;code&gt;en.json&lt;/code&gt;, I get this beauty:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fz6N4JIm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/github_actions/upload.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fz6N4JIm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/github_actions/upload.png" alt="https://content.localazy.com/github_actions/upload.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate Downloading
&lt;/h2&gt;

&lt;p&gt;Normally, you would integrate downloading localizable files from Localazy into your build chain, so they are added before building the release.&lt;/p&gt;

&lt;p&gt;For clarity, let's test just the downloading and omit other steps.&lt;/p&gt;

&lt;p&gt;Create file &lt;code&gt;.github/workflows/download.yml&lt;/code&gt; for downloading the latest version of localizable files whenever the tag &lt;code&gt;v*&lt;/code&gt; is pushed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download from Localazy&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v*'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;localazy-download-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download strings from Localazy&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localazy/download@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;read_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LOCALAZY_READ_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;write_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LOCALAZY_WRITE_KEY }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# Test that localized files were downloaded by listing &lt;/span&gt;
          &lt;span class="s"&gt;# the content of locales folder.&lt;/span&gt;
          &lt;span class="s"&gt;ls locales/*.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, this example effectively does nothing as files are only downloaded to the action, and if we don't use them to produce the release build, they are lost once the action is finished and cleared.&lt;/p&gt;

&lt;p&gt;The documentation for the &lt;code&gt;localazy/download&lt;/code&gt; action is available on &lt;a href="https://github.com/marketplace/actions/localazy-download"&gt;Github Marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pushing &lt;code&gt;v2&lt;/code&gt; tag to my test repository (notice that there is &lt;code&gt;cs.json&lt;/code&gt; file):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QnoH8vqy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/github_actions/download.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QnoH8vqy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://content.localazy.com/github_actions/download.png" alt="https://content.localazy.com/github_actions/download.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;The source code of a &lt;a href="https://github.com/localazy/github-actions-sample"&gt;demonstrational project for this post is available on Github&lt;/a&gt;. Do not forget to explore the content of the &lt;code&gt;.github/workflows&lt;/code&gt; folder!&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Words
&lt;/h2&gt;

&lt;p&gt;As you can see, it's simple enough to completely remove the localization burden from your shoulders with &lt;a href="https://localazy.com"&gt;Localazy&lt;/a&gt;. No more file handling. All the translations of your lovely app can be managed in a single place with beautiful UI designed for developers and those that want them to bring their apps into the multilingual realm.&lt;/p&gt;

</description>
      <category>github</category>
      <category>localization</category>
      <category>tutorial</category>
      <category>automation</category>
    </item>
    <item>
      <title>From Android's floating windows to Floating Apps: final tech pieces</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Thu, 12 Nov 2020 12:43:21 +0000</pubDate>
      <link>https://dev.to/localazy/from-android-s-floating-windows-to-floating-apps-final-tech-pieces-4377</link>
      <guid>https://dev.to/localazy/from-android-s-floating-windows-to-floating-apps-final-tech-pieces-4377</guid>
      <description>&lt;p&gt;Have you ever wondered how to make those floating windows used by Facebook Heads and other apps? Have you ever wanted to use the same technology in your app? It’s easy, and I will guide you through the whole process.&lt;/p&gt;

&lt;p&gt;I'm the author of &lt;a href="https://floatingapps.net"&gt;Floating Apps&lt;/a&gt;; the first app of its kind on Google Play and the most popular one with over 8 million downloads. After 6 years of the development of the app, I know a bit about it. It’s sometimes tricky, and I spent months reading documentation and Android source code and experimenting. I received feedback from tens of thousands of users and see various issues on different phones with different Android versions.&lt;/p&gt;

&lt;p&gt;Here's what I learned along the way. &lt;/p&gt;

&lt;p&gt;Before reading this article, it's recommended to go through &lt;a href="https://localazy.com/blog/floating-windows-on-android-9-shortcomings"&gt;Floating Windows on Android 9: Shortcomings&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this article, I show you some tips &amp;amp; tricks I used in Floating Apps.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimize
&lt;/h2&gt;

&lt;p&gt;In Floating Apps, it's possible to minimize windows. How do I achieve this effect? &lt;/p&gt;

&lt;p&gt;Well, I move the window outside of the visible area of the screen. This is the safest thing you can do. Changing the window’s size, removing the view from &lt;code&gt;WindowManager&lt;/code&gt;, or any other similar action could break your app. For example, components rendering content directly to the video memory such as &lt;code&gt;SurfaceView&lt;/code&gt;, &lt;code&gt;VideoView&lt;/code&gt;, etc. don’t like it.&lt;/p&gt;

&lt;p&gt;When the window is moved outside of the screen area, I inject a new view - the bubble. It’s all done together with smooth animations, so it actually looks like the window is minimized to the bubble. But it's all just a smart effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maximize
&lt;/h2&gt;

&lt;p&gt;In Floating Apps, there is heavy math behind the window's size as there are different modes (with the title bar, without it, with minimal bar, etc.), half-screen size, etc. &lt;/p&gt;

&lt;p&gt;But maximizing the window is not the case :-). You can use &lt;code&gt;MATCH_PARENT&lt;/code&gt; for &lt;code&gt;LayoutParams&lt;/code&gt;, and it automatically sets the maximal available size. Simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gravity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TOP&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="nc"&gt;Gravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LEFT&lt;/span&gt;  
&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ViewGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LayoutParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MATCH_PARENT&lt;/span&gt;  
&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ViewGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LayoutParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MATCH_PARENT&lt;/span&gt;  
&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  
&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to remember the original position and size, and disable moving the maximized window. &lt;/p&gt;

&lt;p&gt;Also, be sure that your layout is flexible enough to work correctly with the maximized window as well as with smaller ones. &lt;/p&gt;

&lt;h2&gt;
  
  
  Resizing Windows
&lt;/h2&gt;

&lt;p&gt;This one is tricky. In Floating Apps, some windows can be resized, and such windows have a small handle in the right bottom corner. &lt;/p&gt;

&lt;p&gt;Resizing the window is very similar to moving it, and you can use the same &lt;code&gt;DraggableTouchListener&lt;/code&gt; as we introduced in &lt;a href="https://localazy.com/blog/floating-windows-on-android-5-moving-window"&gt;the &lt;strong&gt;Moving Window&lt;/strong&gt; article&lt;/a&gt;. Just change &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; for &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;I experimented with changing the window size directly, but for windows with a complex layout, it's slow. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So my final version is:&lt;/strong&gt; When the resize handle is touched, a new semi-transparent floating view is injected above the original window with the same size and position and it's resized instead of it. When resizing is finished, the new size is applied to the original window. &lt;/p&gt;

&lt;h2&gt;
  
  
  Transparency
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;WindowManager.LayoutParams&lt;/code&gt;  comes with &lt;code&gt;alpha&lt;/code&gt;, so this one is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Make the window transparent with 50% opacity.&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't allow the user to make the window completely invisible as it could have undesired effects. I bet you can imagine it. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FUN TIP:&lt;/strong&gt; Completely invisible floating window is a nice prank! ;-).&lt;/p&gt;

&lt;h2&gt;
  
  
  Screen Rotation
&lt;/h2&gt;

&lt;p&gt;In the foreground service, register the broadcast receiver to listen to &lt;code&gt;Intent.ACTION_CONFIGURATION_CHANGED&lt;/code&gt;, and you get notified when the screen is rotated.&lt;/p&gt;

&lt;p&gt;In Floating Apps, when the screen orientation is changed, I keep the same size of the window and recalculate its position using the percentual calculation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;newX&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oldX&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;oldScreenWidth&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;newScreenWidth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The window seems to be still in the same position relatively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bubble Physics
&lt;/h2&gt;

&lt;p&gt;In Floating Apps, the bubbles for minimized windows are just specifically layouted views and nothing more. There is almost no difference from floating windows, as we discussed them in this series. &lt;/p&gt;

&lt;p&gt;However, the bubbles have nice psychics when they're moved around the screen. All the magic is created with the &lt;a href="https://github.com/facebookarchive/rebound"&gt;Facebook Rebound&lt;/a&gt; library - a java library that models spring dynamics. &lt;/p&gt;

&lt;p&gt;Ooh, I just noticed that the library is archived. Pity. It's very nice.&lt;/p&gt;

&lt;h2&gt;
  
  
  And We Are Done!
&lt;/h2&gt;

&lt;p&gt;This is the last article in the series about the floating technology. I taught you almost everything I learned during the last few years. &lt;/p&gt;

&lt;p&gt;Enjoy it and feel free to share your apps with me.&lt;/p&gt;

&lt;p&gt;Also, I will be glad if you decide to localize your apps with &lt;a href="https://localazy.com"&gt;Localazy&lt;/a&gt;. I put the very same love into it as I did with Floating Apps. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thanks for reading!  Follow me (&lt;a href="https://twitter.com/vaclavhodek"&gt;@vaclavhodek&lt;/a&gt;) and Localazy (&lt;a href="https://twitter.com/localazy"&gt;@localazy&lt;/a&gt;) on Twitter, or like &lt;a href="https://www.facebook.com/localazy"&gt;Localazy on Facebook&lt;/a&gt; for more interesting information about Android development.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;Floating Windows on Android&lt;/strong&gt; series. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-1-jetpack-compose-and-room"&gt;Floating Windows on Android 1: Jetpack Compose &amp;amp; Room&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-2-foreground-service"&gt;Floating Windows on Android 2: Foreground Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-3-permissions"&gt;Floating Windows on Android 3: Permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-4-floating-window"&gt;Floating Windows on Android 4: Floating Window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-5-moving-window"&gt;Floating Windows on Android 5: Moving Window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-6-keyboard-input"&gt;Floating Windows on Android 6: Keyboard Input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-7-boot-receiver"&gt;Floating Windows on Android 7: Boot Receiver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-8-the-final-app"&gt;Floating Windows on Android 8: The Final App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-9-shortcomings"&gt;Floating Windows on Android 9: Shortcomings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-10-tips-and-tricks"&gt;Floating Windows on Android 10: Tips &amp;amp; Tricks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>Switching locales with Jetpack Compose</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Thu, 12 Nov 2020 12:15:34 +0000</pubDate>
      <link>https://dev.to/localazy/switching-locales-with-jetpack-compose-1ohe</link>
      <guid>https://dev.to/localazy/switching-locales-with-jetpack-compose-1ohe</guid>
      <description>&lt;p&gt;I've already published an article on &lt;a href="https://localazy.com/blog/why-allow-users-to-switch-languages" rel="noopener noreferrer"&gt;how it's important to allow users to switch languages&lt;/a&gt;. There's plenty of reasons to do so. &lt;/p&gt;

&lt;p&gt;Now, let's see how simple it can be with Jetpack Compose to implement custom language switching. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare your app
&lt;/h2&gt;

&lt;p&gt;Be sure that you have all strings in &lt;code&gt;res/values/strings.xml&lt;/code&gt; and whenever you need them, get them using Android's standard mechanism &lt;code&gt;getString()&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;It's best to use English as the base language as it works best with translation tools, and it's usually easier to find collaborators and volunteers. &lt;/p&gt;

&lt;p&gt;And that's all. You don't need to care about XML files in other languages. We use a localization platform for that. &lt;/p&gt;

&lt;h2&gt;
  
  
  Manage translations
&lt;/h2&gt;

&lt;p&gt;Once you have your &lt;code&gt;strings.xml&lt;/code&gt; file ready, &lt;a href="https://localazy.com" rel="noopener noreferrer"&gt;sign up for Localazy&lt;/a&gt; and follow Android integration instruction. It's simple as it only means a few lines to be added to your root's and app's&lt;code&gt;build.gradle&lt;/code&gt; — no need to change source code or resources. &lt;/p&gt;

&lt;p&gt;All changes you will need to do will be as simple as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;add Localazy to the root's build.gradle file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;buildscript&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="n"&gt;maven&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="s2"&gt;"https://maven.localazy.com/repository/release/"&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="n"&gt;classpath&lt;/span&gt; &lt;span class="s2"&gt;"com.localazy:gradle:1.5.2"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Add Localazy to the app's build.gradle file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="nl"&gt;plugin:&lt;/span&gt; &lt;span class="s1"&gt;'com.localazy.gradle'&lt;/span&gt;

&lt;span class="n"&gt;localazy&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;readKey&lt;/span&gt; &lt;span class="s2"&gt;"your-read-key"&lt;/span&gt;
    &lt;span class="n"&gt;writeKey&lt;/span&gt; &lt;span class="s2"&gt;"your-write-key"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! Now, you can upload strings using the &lt;code&gt;uploadStrings&lt;/code&gt; task using Gradle. It's available on the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gradlew uploadStrings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And also in the &lt;strong&gt;Gradle&lt;/strong&gt; view in Android Studio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fjetpack_locale_switching%2Fandroid-studio.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fjetpack_locale_switching%2Fandroid-studio.png" alt="|Gradle View in Android Studio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From that moment on, you can manage translations easily using Localazy, and there are also shared translations to translate a huge portion of your app to up to 80 languages for free. &lt;/p&gt;

&lt;h2&gt;
  
  
  Jetpack: Hello World
&lt;/h2&gt;

&lt;p&gt;To test how our locale switching works, let's create a simple Hello World app.&lt;/p&gt;

&lt;p&gt;Here goes a code for a composable, a text, rendered in the center of the screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;WelcomeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxHeight&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;verticalArrangement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Arrangement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gravity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterHorizontally&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To allow users to switch their language, we can use the floating action button. We can add it to our activity and give it an action - to open &lt;code&gt;SwitchActivity&lt;/code&gt; that we will discuss later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;floatingActionButton&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;FloatingActionButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="nf"&gt;startActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="nd"&gt;@MainActivity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SwitchActivity&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="n"&gt;elevation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nc"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Icons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Translate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;bodyContent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;WelcomeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;welcome_message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting screen is expected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fjetpack_locale_switching%2Fmainactivity.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fjetpack_locale_switching%2Fmainactivity.png" alt="MainActivity screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's implement &lt;code&gt;SwitchActivity&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  SwitchActivity
&lt;/h2&gt;

&lt;p&gt;Localazy Android library has been automatically integrated with our app by the few lines in the build script mentioned above, so it's available. The whole &lt;a href="https://localazy.com/docs/android/localazy-android-library" rel="noopener noreferrer"&gt;documentation for the Localazy Android library is available on the website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, we wrap a simple &lt;code&gt;ViewModel&lt;/code&gt; around it to make their data easily accessible to our newly created activity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocaleViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;localazyListener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalazyWrappedListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;locales&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocalazyLocale&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Localazy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;localazyListener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;locales&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Localazy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLocales&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We wrapped &lt;code&gt;LocalazyListener&lt;/code&gt;, so we don't need to implement all the overrides to listen for a single event. Here goes the implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * A simple class to wrap LocalazyListener, so we don't need to implement
 * all functions, and can use a lambda to monitor changes.
 */&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocalazyWrappedListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LocalazyListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;missingTextFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LocalazyId&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;missingKeyFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;stringsUpdateStarted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * This function is called when updated data is downloaded.
     */&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;stringsUpdateFinished&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;stringsUpdateFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;stringsUpdateNotNecessary&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * This function is called when the strings are loaded.
     */&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;stringsLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromUpdate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! That was pretty simple. Now, let's render the language selector using Jetpack's composables. Not only that we show the available languages, but we can also indicate that the given language is not yet fully translated (which is extremely handy as it can attract more contributors and volunteers to help with translating) and, of course, we also need to point users to our project on Localazy, so they can actually help us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;LocaleSwitcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocalazyLocale&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
    &lt;span class="n"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LocalazyLocale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;onHelp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;LazyColumnFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;TextButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                    &lt;span class="s"&gt;"${it.localizedName}${if (!it.isFullyTranslated) "&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;incomplete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s"&gt;" else ""}"&lt;/span&gt;
                &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;TextButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;onHelp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Help us translate the app!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have everything ready for &lt;code&gt;SwitchActivity&lt;/code&gt;. Let's make the drum rolls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SwitchActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;localeViewModel&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModels&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocaleViewModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;LocaleSwitcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;localeViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;onChange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

                    &lt;span class="c1"&gt;// Change the locale and persist the new choice.&lt;/span&gt;
                    &lt;span class="nc"&gt;Localazy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forceLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="c1"&gt;// Reopen MainActivity with clearing top.&lt;/span&gt;
                    &lt;span class="nf"&gt;startActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="nd"&gt;@SwitchActivity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="nc"&gt;MainActivity&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;
                        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FLAG_ACTIVITY_CLEAR_TOP&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;onHelp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// Open the project on Localazy to allow contributors to help us with translating.&lt;/span&gt;
                    &lt;span class="nf"&gt;startActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACTION_VIEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Localazy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProjectUri&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FLAG_ACTIVITY_NEW_TASK&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fjetpack_locale_switching%2Fswitchactivity.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fjetpack_locale_switching%2Fswitchactivity.png" alt="SwitchActivity screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which exactly matches my app on Localazy at the given time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fjetpack_locale_switching%2Flocalazy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fjetpack_locale_switching%2Flocalazy.png" alt="Localazy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To translate my app to more languages, I don't need to touch XML files or source code. Everything can be managed through Localazy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Updated translations and new languages are delivered online to existing users without the need to re-submit the app to Play Store ;-). Awesome, well?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;You can find &lt;a href="https://github.com/vaclavhodek/JetpackLocaleSwitching" rel="noopener noreferrer"&gt;the whole source code on Github&lt;/a&gt;. &lt;/p&gt;




&lt;p&gt;This post was originally published on &lt;a href="https://localazy.com/blog/switching-locales-with-jetpack-compose" rel="noopener noreferrer"&gt;Localazy&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>android</category>
      <category>localization</category>
      <category>localazy</category>
    </item>
    <item>
      <title>Android's strings.xml: Deprecated!</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Sun, 08 Nov 2020 12:04:14 +0000</pubDate>
      <link>https://dev.to/localazy/android-s-strings-xml-deprecated-1pi0</link>
      <guid>https://dev.to/localazy/android-s-strings-xml-deprecated-1pi0</guid>
      <description>&lt;p&gt;Have you decided to make your Android app multilingual? I guess that even if you haven't yet, you know about &lt;code&gt;strings.xml&lt;/code&gt; and &lt;code&gt;values-XX&lt;/code&gt; folders. Localizing your app should be easier than handling XML files. And it can be! &lt;/p&gt;

&lt;p&gt;Here goes a simple guide on avoiding handling translated XML files at all in a few simple steps. And as a bonus, you get &lt;strong&gt;your app translated to up to 80 languages for free&lt;/strong&gt; by sharing translations with other apps. &lt;/p&gt;

&lt;h2&gt;
  
  
  Not thinking about localization yet?
&lt;/h2&gt;

&lt;p&gt;You should keep localization in mind early. It's better to have your app prepared for it from the beginning. Believe me. Several times, I was depressed from searching for all the hardcoded strings having a hard time refactoring my code to be prepared for proper localization. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A few minutes you spend with this article will save you hours or maybe days in the future.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The source language
&lt;/h2&gt;

&lt;p&gt;Okay, there's already a localization solution baked into Android, so there's no reason to go against it. Use &lt;code&gt;strings.xml&lt;/code&gt; in the &lt;code&gt;values&lt;/code&gt; folder for the source language. Btw, I strongly recommend using English.&lt;/p&gt;

&lt;p&gt;Just keep doing what's standard and what's recommended by Android Studio, and for what Android Studio can give us insights and useful warnings. &lt;/p&gt;

&lt;p&gt;However, completely forget about those &lt;code&gt;values-de-rDE&lt;/code&gt; or &lt;code&gt;values-b+zh+Hans+TW&lt;/code&gt; folders. You don't need them at all!&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Sign up for Localazy - It's free
&lt;/h2&gt;

&lt;p&gt;Navigate to &lt;a href="https://localazy.com/android" rel="noopener noreferrer"&gt;Localazy&lt;/a&gt;, sign up, and create a new app after the registration. &lt;/p&gt;

&lt;p&gt;Select the Android integration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fstrings_xml_deprecated%2F1_select_integration.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fstrings_xml_deprecated%2F1_select_integration.png" alt="Select the Android integration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Follow the integration guide
&lt;/h2&gt;

&lt;p&gt;It's as simple as adding a few lines to your &lt;strong&gt;root's&lt;/strong&gt; &lt;code&gt;build.gradle&lt;/code&gt; (adding a repository and a build plugin):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;buildscript&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;maven&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="s2"&gt;"https://maven.localazy.com/repository/release/"&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="n"&gt;classpath&lt;/span&gt; &lt;span class="s2"&gt;"com.localazy:gradle:1.5.2"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a few lines at the end of your &lt;strong&gt;app's&lt;/strong&gt; &lt;code&gt;build.gradle&lt;/code&gt; (configuring and applying a build plugin):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="nl"&gt;plugin:&lt;/span&gt; &lt;span class="s1"&gt;'com.localazy.gradle'&lt;/span&gt;

&lt;span class="n"&gt;localazy&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;readKey&lt;/span&gt; &lt;span class="s2"&gt;"secret-read-key"&lt;/span&gt;
    &lt;span class="n"&gt;writeKey&lt;/span&gt; &lt;span class="s2"&gt;"secret-write-key"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a matter of about 30 seconds if you know what you do :-). And btw, the &lt;a href="https://localazy.com/docs/android/localazy-gradle-plugin" rel="noopener noreferrer"&gt;Localazy Gradle plugin is highly configurable&lt;/a&gt; ;-).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Upload strings
&lt;/h2&gt;

&lt;p&gt;Once Localazy is integrated with your app, all you need to do is to upload strings. You can do so from the command-line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gradlew uploadStrings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or from Android Studio using the &lt;strong&gt;Gradle&lt;/strong&gt; view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fstrings_xml_deprecated%2F2_android_studio.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fstrings_xml_deprecated%2F2_android_studio.png" alt="Gradle View in Android Studio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upload your strings whenever you add new ones so that translators can get their hands on them early. &lt;/p&gt;

&lt;h2&gt;
  
  
  4. Get your app translate
&lt;/h2&gt;

&lt;p&gt;Shortly after you upload your strings, you are offered about 80 languages into which Localazy can automatically translate your app. All you need to do is just to review them so that they can go live. &lt;/p&gt;

&lt;p&gt;You can go as far as translating languages you don't know using machine translations. They are completely free with Localazy. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fstrings_xml_deprecated%2F3_langs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fstrings_xml_deprecated%2F3_langs.png" alt="The simplest localization management"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, I strongly recommend doing so, &lt;strong&gt;you can invite your loyal users to Localazy to help you with translating your app&lt;/strong&gt; to their native languages. With Localazy, you can manage them easily, and the review is here to ensure high-quality translation. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fstrings_xml_deprecated%2F4_contribs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fstrings_xml_deprecated%2F4_contribs.png" alt="The simplest contributor management"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  And that's it!
&lt;/h2&gt;

&lt;p&gt;Really! Do you expect another step? Downloading translated files and placing them in the correct values folder? Nothing like this is necessary.&lt;/p&gt;

&lt;p&gt;The Localazy Gradle plugin you integrated in the step &lt;strong&gt;2.&lt;/strong&gt; is taking care of everything for you. When your app is built, it downloads and places translated files into it. Everything is processed during the build, so your source code and resources are left untouched. &lt;/p&gt;

&lt;p&gt;Did I promise that you can forget about manual XML files handling? Here it is! &lt;/p&gt;

&lt;h2&gt;
  
  
  OTA/online updates
&lt;/h2&gt;

&lt;p&gt;And there's more you are about to get. The Localazy Gradle plugin automatically integrates the &lt;a href="https://localazy.com/docs/android/localazy-android-library" rel="noopener noreferrer"&gt;Localazy Android library&lt;/a&gt; with your app. And this small library &lt;strong&gt;keeps translations in your app up-to-date, and it can even download new languages&lt;/strong&gt;. You no longer need to re-submit your app to Play Store just because you need to fix translations or add new languages. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And all of this with no single change to the source code of your app!&lt;/strong&gt; Everything is done automatically during the build process. &lt;/p&gt;

&lt;p&gt;You can disable this feature if you want so, but I recommend using it as it also optimizes the translation and review process, so you spend your time with the most important translations first. &lt;/p&gt;

&lt;p&gt;Btw, the &lt;a href="https://localazy.com/docs/android/localazy-android-library" rel="noopener noreferrer"&gt;Localazy Android library&lt;/a&gt; comes with a bunch of nice features, so you can, for example, create a language selector that is always up to date. &lt;/p&gt;

&lt;h2&gt;
  
  
  And what about you?
&lt;/h2&gt;

&lt;p&gt;Of course, the final choice is yours, but as Localazy is free for usual apps, I'm no longer going to have headaches from all those XML files and values folders. Having like 6 folders for different densities and another set of them for mipmap images is enough for &lt;br&gt;
me :-).&lt;/p&gt;

</description>
      <category>android</category>
      <category>localization</category>
      <category>localazy</category>
      <category>gradle</category>
    </item>
    <item>
      <title>Shortcomings of floating windows on Android</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Sun, 08 Nov 2020 10:37:23 +0000</pubDate>
      <link>https://dev.to/localazy/shortcomings-of-floating-windows-on-android-3mk3</link>
      <guid>https://dev.to/localazy/shortcomings-of-floating-windows-on-android-3mk3</guid>
      <description>&lt;p&gt;Have you ever wondered how to make those floating windows used by Facebook Heads and other apps? Have you ever wanted to use the same technology in your app? It’s easy, and I will guide you through the whole process.&lt;/p&gt;

&lt;p&gt;I'm the author of &lt;a href="https://floatingapps.net" rel="noopener noreferrer"&gt;Floating Apps&lt;/a&gt;; the first app of its kind on Google Play and the most popular one with over 8 million downloads. After 6 years of the development of the app, I know a bit about it. It’s sometimes tricky, and I spent months reading documentation and Android source code and experimenting. I received feedback from tens of thousands of users and see various issues on different phones with different Android versions.&lt;/p&gt;

&lt;p&gt;Here's what I learned along the way. &lt;/p&gt;

&lt;p&gt;Before reading this article, it's recommended to go through &lt;a href="https://localazy.com/blog/floating-windows-on-android-8-the-final-app" rel="noopener noreferrer"&gt;Floating Windows on Android 8: The Final App&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this article, I teach you how to overcome some key shortcomings of floating technology.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixed Support
&lt;/h2&gt;

&lt;p&gt;Below, I write about the most significant shortcomings of floating technology. On some devices, some of the described problems may not appear at all. On other devices, some issues exist, some not. &lt;/p&gt;

&lt;p&gt;However, if you want your app to be available to all users, you can't rely on mixed support. Your app would crash frequently, or it wouldn't be usable for a large group of users.&lt;/p&gt;

&lt;p&gt;Believe me, I had the privilege to see my app running on thousands of different devices, and I came across weird and illogical bugs and non-standard behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dialogs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The standard Android's &lt;code&gt;AlertDialog&lt;/code&gt; needs an underlying activity, and so it's not possible to use it in floating windows - it would cause the app to crash. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The best solution is to implement custom dialogs based on floating technology. &lt;/p&gt;

&lt;p&gt;In Floating Apps, I implemented many different dialog types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;yes/no&lt;/li&gt;
&lt;li&gt;ok/cancel&lt;/li&gt;
&lt;li&gt;ok only&lt;/li&gt;
&lt;li&gt;select file&lt;/li&gt;
&lt;li&gt;show list&lt;/li&gt;
&lt;li&gt;list of checkboxes&lt;/li&gt;
&lt;li&gt;rich-text&lt;/li&gt;
&lt;li&gt;custom view&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also implemented modality. When the dialog is shown, the related window is blocked, and the dialog is brought to the front instead of the window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A simple confirmation dialog.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fquicknote_8%2Ffa1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fquicknote_8%2Ffa1.png" alt="Dialog in Floating Apps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Popup Menus
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With popup menus, there is a similar problem as for &lt;code&gt;AlertDialog&lt;/code&gt;. It's not supported in floating windows. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As for &lt;code&gt;AlertDialog&lt;/code&gt;, I would recommend implementing custom popup menus based on floating technology. &lt;/p&gt;

&lt;p&gt;I did for Floating Apps. It relies on popups heavily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A simple popup menu shown over its parent window.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fquicknote_8%2Ffa2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fquicknote_8%2Ffa2.png" alt="Popup menu in Floating Apps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy &amp;amp; Paste
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The copy/paste feature is a real problem. It works well only on a few phones. As a rule of thumb, it's better to consider it unsupported. &lt;/p&gt;

&lt;p&gt;Usually, it's possible to highlight/select text. To be more precise, it's possible to select words with a long tap.  However, handles used for working with the selection are missing, and therefore, there is no way to select anything else than single words.&lt;/p&gt;

&lt;p&gt;And there's another issue. The standard popup with actions (copy, paste, select all, translate, etc.) is somehow connected to the underlying activity on most devices and won’t appear at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For short texts, it's acceptable to implement &lt;code&gt;OnLongClickListener&lt;/code&gt; and use custom popup. However, as it's not possible to select the text this way, it's necessary to introduce &lt;em&gt;Copy all&lt;/em&gt;/&lt;em&gt;Cut all&lt;/em&gt; functionality. The Facebook Heads uses this solution everywhere. In Floating Apps, I use it too for edit fields. &lt;/p&gt;

&lt;p&gt;In Floating Apps, there are mini-apps for notes taking and text editing, and being able to use &lt;em&gt;Copy all&lt;/em&gt; only, it wouldn't be a suitable solution. For this reason, these apps use WebView in which a full-size &lt;code&gt;contenteditable&lt;/code&gt; &lt;code&gt;div&lt;/code&gt; is shown with custom logic for text selection, copying and pasting. It's not perfect, but better than nothing :-).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A custom copy/paste popup menu.&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fquicknote_8%2Ffa3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fquicknote_8%2Ffa3.png" alt="Copy/paste in Floating Apps"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Permissions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Obtaining permissions from Android M and above requires a dialog to be shown and the method for issuing the request is available in &lt;code&gt;Activity&lt;/code&gt; as well as the mechanism for catching the result. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In fact, there are two possible solutions to this problem. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can ask the user to grant all the necessary permissions in the main app before you allow her to access floating windows. Honestly, this solution may scare your users, and as Floating Apps has around 8 required permissions, I believe that almost nobody would grant them all before any real experience with the app.&lt;/li&gt;
&lt;li&gt;Ask the user for the required permission when you need it. This is the correct way how to do it. In this case, the best you can do is to show an invisible activity and raise the permission request dialog from it. It may interrupt the current task, but as this is an only one-time action, it's not a big deal, and there is no better way to do. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Floating Apps, every single mini-app defines a set of required permissions, and before the app is launched, the service checks whether all permissions are granted. If not, an invisible activity is shown and ask the user to grant missing ones. &lt;/p&gt;
&lt;h2&gt;
  
  
  System Interactions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some things, such as social logins, requesting SD card access, opening with, sharing, etc., can only be invoked from running activity. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I tend to move "configuration" level things to the main app, which is a normal Android app, and once configured, it's usually possible to use them from inside the service.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;Open With&lt;/code&gt; / &lt;code&gt;Share With&lt;/code&gt; functionality, a hidden activity is a solution both for showing the dialog to open/share with and for receiving data from other apps.   &lt;/p&gt;

&lt;p&gt;However, you may need to sacrifice functionality that is not supported in the service. For example, I once wanted to implement floating navigation with MapBox, but their SDK expects  an instance of Activity to be available. No luck. &lt;/p&gt;
&lt;h2&gt;
  
  
  Z-Order
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most recently added &lt;code&gt;View&lt;/code&gt; is rendered on top of all others. There is no mechanism for switching the z-order of &lt;code&gt;View&lt;/code&gt;s in &lt;code&gt;WindowManager&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Floating Apps, the switching of floating windows is a complex process. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The whole window is rendered to an image, and a new &lt;code&gt;ImageView&lt;/code&gt; is injected to &lt;code&gt;WindowManager&lt;/code&gt; with the image of the original window on exactly the same position. In fact, it looks exactly the same as the original window. &lt;/li&gt;
&lt;li&gt;The original window is removed from the &lt;code&gt;WindowManager&lt;/code&gt; but kept alive.&lt;/li&gt;
&lt;li&gt;The original window is added back to the &lt;code&gt;WindowManager&lt;/code&gt;, which means that it's effectively brought to the top. &lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ImageView&lt;/code&gt; with the rendered image of the window is removed from the &lt;code&gt;WindowManager&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using the approach above, switching windows seems like a seamless process without blinking and other undesired effects.&lt;/p&gt;

&lt;p&gt;Of course, in Floating Apps, this is automated, and there is a complex logic around this that also controls the current z-order of all windows and intelligently decides when the switching is necessary and when not.&lt;/p&gt;

&lt;p&gt;However, removing and re-adding the window may cause a problem with components rendering content directly to the video memory such as &lt;code&gt;SurfaceView&lt;/code&gt;, &lt;code&gt;VideoView&lt;/code&gt;, etc. For this reason, Floating Apps automatically disable this process for windows of a certain type. &lt;/p&gt;
&lt;h2&gt;
  
  
  Browser Limitations
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;WebView&lt;/code&gt; works well in floating windows, and it's mostly possible to reimplement its interactions such as uploading and downloading files, invalid certificate notifications, requests for location permission, etc. to use floating windows/dialogs described above. &lt;/p&gt;

&lt;p&gt;However, there are a few actions that may lead to crashes or are unsupported. I hope I listed all the key issues.&lt;/p&gt;
&lt;h3&gt;
  
  
  Open With
&lt;/h3&gt;

&lt;p&gt;Android tries to offer available apps, aka  &lt;code&gt;Open With&lt;/code&gt; feature, when opening links with an unknown protocol. As there is no activity, it may cause crashes. However, this behavior can be easily overridden.&lt;/p&gt;
&lt;h3&gt;
  
  
  Copy/Paste
&lt;/h3&gt;

&lt;p&gt;The problem with copy/paste described above also exists for &lt;code&gt;WebView&lt;/code&gt;, and it's even worse. &lt;/p&gt;

&lt;p&gt;It's possible to use &lt;code&gt;WebView&lt;/code&gt; to implement a workaround for unsupported text selection because we have full control over the code. However, for other websites, the situation is much complicated. &lt;/p&gt;

&lt;p&gt;However, it's possible to get the selected text in the &lt;code&gt;WebView&lt;/code&gt; using something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"javascript:process(window.getSelection().toString());"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And paste the text to &lt;code&gt;WebView&lt;/code&gt; using simulating key strokes with &lt;code&gt;dispatchKeyEvent&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Long-Click On Links &amp;amp; Images
&lt;/h3&gt;

&lt;p&gt;Long-clicks may lead to crashes depending on the implementation, and certainly, it's not possible to invoke standard actions like &lt;code&gt;Open In New Window&lt;/code&gt;, &lt;code&gt;Download Image&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;The solution is to set custom &lt;code&gt;OnLongClickListener&lt;/code&gt; for the &lt;code&gt;WebView&lt;/code&gt; and get use &lt;code&gt;webView.getHitTestResult()&lt;/code&gt; to get information about the clicked element. &lt;/p&gt;

&lt;p&gt;Don't forget to &lt;code&gt;return false&lt;/code&gt; when &lt;code&gt;webView.getHitTestResult()&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt; to let the &lt;code&gt;WebView&lt;/code&gt; process the long click correctly, e.g. for selecting texts.  &lt;/p&gt;

&lt;h3&gt;
  
  
  HTML  &amp;lt;select&amp;gt;
&lt;/h3&gt;

&lt;p&gt;HTML element &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; shows a dialog with available options and cause your app to crash. It's a big deal, and the only solution is to hook the element, override its action, and show custom UI based on floating technology. &lt;/p&gt;

&lt;p&gt;It's not a simple task and needs a lot of testing. Don't forget that options in &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; may be nested and disabled. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Note
&lt;/h2&gt;

&lt;p&gt;You learned how to implement floating windows for your app. It's a powerful technology, but it comes with its price. &lt;/p&gt;

&lt;p&gt;Use it wisely where it makes sense and try to avoid situations in which you get to shortcomings mentioned in this article and some others I forgot. It's not a simple task to correctly implement all the workarounds to be reliable across different phones and Android versions.&lt;/p&gt;

&lt;p&gt;Enjoy it and feel free to share your apps with me. &lt;/p&gt;

&lt;p&gt;Also, I will be glad if you decide to localize your apps with &lt;a href="https://localazy.com" rel="noopener noreferrer"&gt;Localazy&lt;/a&gt;. I put the very same love into it as I did with Floating Apps. &lt;/p&gt;

&lt;p&gt;Continue to &lt;a href="https://localazy.com/blog/floating-windows-on-android-10-tips-and-tricks" rel="noopener noreferrer"&gt;the last article with tips &amp;amp; tricks&lt;/a&gt; I learned the hard way. &lt;/p&gt;

&lt;h2&gt;
  
  
  Stay Tuned
&lt;/h2&gt;

&lt;p&gt;Eager to learn more about Android development? Follow me (&lt;a href="https://twitter.com/vaclavhodek" rel="noopener noreferrer"&gt;@vaclavhodek&lt;/a&gt;) and Localazy (&lt;a href="https://twitter.com/localazy" rel="noopener noreferrer"&gt;@localazy&lt;/a&gt;) on Twitter, or like &lt;a href="https://www.facebook.com/localazy" rel="noopener noreferrer"&gt;Localazy on Facebook&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;Floating Windows on Android&lt;/strong&gt; series. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-1-jetpack-compose-and-room" rel="noopener noreferrer"&gt;Floating Windows on Android 1: Jetpack Compose &amp;amp; Room&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-2-foreground-service" rel="noopener noreferrer"&gt;Floating Windows on Android 2: Foreground Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-3-permissions" rel="noopener noreferrer"&gt;Floating Windows on Android 3: Permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-4-floating-window" rel="noopener noreferrer"&gt;Floating Windows on Android 4: Floating Window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-5-moving-window" rel="noopener noreferrer"&gt;Floating Windows on Android 5: Moving Window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-6-keyboard-input" rel="noopener noreferrer"&gt;Floating Windows on Android 6: Keyboard Input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-7-boot-receiver" rel="noopener noreferrer"&gt;Floating Windows on Android 7: Boot Receiver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-8-the-final-app" rel="noopener noreferrer"&gt;Floating Windows on Android 8: The Final App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-9-shortcomings" rel="noopener noreferrer"&gt;Floating Windows on Android 9: Shortcomings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-10-tips-and-tricks" rel="noopener noreferrer"&gt;Floating Windows on Android 10: Tips &amp;amp; Tricks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>Kotlin &amp; Gmail API - listing emails</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Fri, 06 Nov 2020 09:36:19 +0000</pubDate>
      <link>https://dev.to/localazy/kotlin-gmail-api-listing-emails-211n</link>
      <guid>https://dev.to/localazy/kotlin-gmail-api-listing-emails-211n</guid>
      <description>&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Let's demonstrate the basic functionality on a useful idea. I have a folder/label in Gmail with a lot of emails. I want to list all email addresses - all senders.&lt;/p&gt;

&lt;p&gt;Everything I need to do is a small console app to go through all emails with the given label and extracting the &lt;code&gt;From&lt;/code&gt; header. If it contains the email address in format &lt;code&gt;Name &amp;lt;email@address.com&amp;gt;&lt;/code&gt;, extract only the email address.&lt;/p&gt;

&lt;p&gt;Let's dive into it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Get credentials
&lt;/h2&gt;

&lt;p&gt;Before we start, you need to create a new project in &lt;a href="https://console.cloud.google.com" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Under the new project, navigate to &lt;strong&gt;API &amp;amp; Services&lt;/strong&gt; and enable &lt;strong&gt;Gmail API&lt;/strong&gt; in &lt;strong&gt;Library&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Credentials&lt;/strong&gt;, click the &lt;strong&gt;+ CREATE CREDENTIALS&lt;/strong&gt;, select &lt;strong&gt;OAuth client ID&lt;/strong&gt; and setup it like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fkotlin_gmail_api%2Fconsole.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fkotlin_gmail_api%2Fconsole.png" alt="OAuth client ID setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Save&lt;/strong&gt; and download the credentials for your newly created ID:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fkotlin_gmail_api%2Fcredentials.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.localazy.com%2Fkotlin_gmail_api%2Fcredentials.png" alt="OAuth client ID setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save the downloaded file as &lt;code&gt;credentials.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kotlin project
&lt;/h2&gt;

&lt;p&gt;Create a new Kotlin project with Gradle and add Google's dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"org.jetbrains.kotlin:kotlin-stdlib"&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'com.google.api-client:google-api-client:1.23.0'&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'com.google.oauth-client:google-oauth-client-jetty:1.23.0'&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'com.google.apis:google-api-services-gmail:v1-rev83-1.23.0'&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0-M1'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Authorization scopes
&lt;/h2&gt;

&lt;p&gt;As we only want to go through labels, messages (we need just headers - metadata), we need a small and safe set of scopes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;SCOPES&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;GmailScopes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GMAIL_LABELS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;GmailScopes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GMAIL_READONLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;GmailScopes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GMAIL_METADATA&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beware that if you provide the &lt;code&gt;GmailScopes.GMAIL_METADATA&lt;/code&gt;, you are not able to access the whole message. You have to omit it if you want to get the message body. &lt;/p&gt;

&lt;h2&gt;
  
  
  Authorize with Gmail
&lt;/h2&gt;

&lt;p&gt;Fortunately, Google libraries come with everything we may need including the server for receiving the authorization request. The whole implementation is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpTransport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NetHttpTransport&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Credential&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;inputStream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"credentials.json"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inputStream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;clientSecrets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GoogleClientSecrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JSON_FACTORY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;InputStreamReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;flow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GoogleAuthorizationCodeFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpTransport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JSON_FACTORY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clientSecrets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDataStoreFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FileDataStoreFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TOKENS_DIRECTORY_PATH&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAccessType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"offline"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;receiver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalServerReceiver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8888&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;AuthorizationCodeInstalledApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code above outputs the request for authorization to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Please open the following address &lt;span class="k"&gt;in &lt;/span&gt;your browser:
  https://accounts.google.com/o/oauth2/auth?access_type&lt;span class="o"&gt;=&lt;/span&gt;offline&amp;amp;client_id&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click the link, authorize the app, and the authorization token is received and stored in &lt;code&gt;TOKENS_DIRECTORY_PATH&lt;/code&gt;. You only need to do this for the first time. Next time, the stored token is used. &lt;/p&gt;

&lt;h2&gt;
  
  
  Build client  &amp;amp; get labels
&lt;/h2&gt;

&lt;p&gt;We can now use the &lt;code&gt;getCredentials()&lt;/code&gt; function above to build an authorized client, list all labels, and find the required one identified by &lt;code&gt;labelName&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Build a new authorized API client service.&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;httpTransport&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GoogleNetHttpTransport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newTrustedTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpTransport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JSON_FACTORY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;getCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpTransport&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setApplicationName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;APPLICATION_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Find the requested label&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"me"&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;labelList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labelList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;labelName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Label `$labelName` is unknown."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  List all emails
&lt;/h2&gt;

&lt;p&gt;For listing all email messages, let's use a few of Kotlin's goodies - &lt;code&gt;tailrec&lt;/code&gt; extension function with lambda as the last parameter. &lt;/p&gt;

&lt;p&gt;We need to invoke the list request repeatedly until the &lt;code&gt;nextPageToken&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;, and doing so with &lt;code&gt;tailrec&lt;/code&gt; is safer. &lt;/p&gt;

&lt;p&gt;For each message, we invoke the &lt;code&gt;process&lt;/code&gt; lambda to perform an actual operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;tailrec&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Gmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;nextPageToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;labelIds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pageToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nextPageToken&lt;/span&gt;
        &lt;span class="n"&gt;includeSpamTrash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nextPageToken&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;processMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nextPageToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Process message
&lt;/h2&gt;

&lt;p&gt;The code for listing emails above returns only &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;threadId&lt;/code&gt; for each of the messages, so we need to fetch message details, extract &lt;code&gt;From&lt;/code&gt; header, and eventually process it. &lt;/p&gt;

&lt;p&gt;To speed up the process, let's use Kotlin's coroutines to perform the message fetching in parallel. First, introduce a custom dispatcher, so we can limit the number of threads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;MAX_FETCH_THREADS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRuntime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;availableProcessors&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;executors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newFixedThreadPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MAX_FETCH_THREADS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dispatcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;CoroutineDispatcher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CoroutineContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Runnable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;executors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For extracting the email address from the &lt;code&gt;Name &amp;lt;email@address.com&amp;gt;&lt;/code&gt; format, the code is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseAddress&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;substringAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substringBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can put things together. Of course, you should introduce some logic for catching exceptions, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Gmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processFroms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;runBlocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;processMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"METADATA"&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"From"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseAddress&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;With all the code above, we can unique list of all senders like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;senders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableSetOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processFroms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;senders&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;senders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;The complete source code is &lt;a href="https://github.com/vaclavhodek/gmail-email-extractor" rel="noopener noreferrer"&gt;available on Github&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://localazy.com" rel="noopener noreferrer"&gt;Localazy&lt;/a&gt; - the best developer-friendly solution for localization of web, desktop and mobile apps. &lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>google</category>
      <category>gmail</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>#8 Floating Windows on Android: The Final App</title>
      <dc:creator>vaclavhodek</dc:creator>
      <pubDate>Tue, 03 Nov 2020 13:51:23 +0000</pubDate>
      <link>https://dev.to/localazy/8-floating-windows-on-android-the-final-app-1kd1</link>
      <guid>https://dev.to/localazy/8-floating-windows-on-android-the-final-app-1kd1</guid>
      <description>&lt;p&gt;Have you ever wondered how to make those floating windows used by Facebook Heads and other apps? Have you ever wanted to use the same technology in your app? It’s easy, and I will guide you through the whole process.&lt;/p&gt;

&lt;p&gt;I'm the author of &lt;a href="https://floatingapps.net"&gt;Floating Apps&lt;/a&gt;; the first app of its kind on Google Play and the most popular one with over 8 million downloads. After 6 years of the development of the app, I know a bit about it. It’s sometimes tricky, and I spent months reading documentation and Android source code and experimenting. I received feedback from tens of thousands of users and see various issues on different phones with different Android versions.&lt;/p&gt;

&lt;p&gt;Here's what I learned along the way. &lt;/p&gt;

&lt;p&gt;Before reading this article, it's recommended to go through &lt;a href="https://localazy.com/blog/floating-windows-on-android-7-boot-receiver"&gt;Floating Windows on Android 7: Boot Receiver&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this article, I will teach you how to wrap everything together to get the note-taking app with floating technology.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final App
&lt;/h2&gt;

&lt;p&gt;To finalize our app, we need to finish adding notes from the floating window. It's not hard but not simple because the view model we created in the first article is not available in the service.&lt;/p&gt;

&lt;p&gt;As a workaround, we encapsulate the whole data management for notes into a class and prepare it to be usable both from the view model and our service.&lt;/p&gt;

&lt;p&gt;As you can notice below, the newly created class also comes with support for broadcasting the &lt;code&gt;NOTES_RECEIVER_ACTION&lt;/code&gt; event when notes are added or removed. More on the broadcasting event is in the next chapter. &lt;/p&gt;

&lt;p&gt;There is also &lt;code&gt;enableMultiInstanceInvalidation()&lt;/code&gt; for Room enabled. It solves the problem with the concurrent changes to the same database. &lt;/p&gt;

&lt;p&gt;There is the complete source code for our new &lt;code&gt;NotesDb&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotesDb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;databaseBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationContext&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="nc"&gt;AppDatabase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="s"&gt;"db-notes"&lt;/span&gt;  
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;enableMultiInstanceInvalidation&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sendBroadcast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;noteObj&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MAX_VALUE&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toInt&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;noteObj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sendBroadcast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sendBroadcast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nc"&gt;GlobalScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sendBroadcast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
      &lt;span class="p"&gt;}&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sendBroadcast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nc"&gt;GlobalScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sendBroadcast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
      &lt;span class="p"&gt;}&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nc"&gt;GlobalScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="nf"&gt;setter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendBroadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NOTES_RECEIVER_ACTION&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to update our &lt;code&gt;NotesViewModel&lt;/code&gt; (not shown here) to use &lt;code&gt;NotesDb&lt;/code&gt; and finish the &lt;code&gt;Window&lt;/code&gt;. When the user clicks on the add icon, the note is added, and the text field is cleared. And it's really that simple as adding a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NotesDb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;initWindow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  

    &lt;span class="c1"&gt;// Add note and clear the edit field.&lt;/span&gt;
    &lt;span class="n"&gt;rootView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_button&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setOnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EditText&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="c1"&gt;// Don't forget to pass true for sendBroadcast parameter.&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
        &lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
      &lt;span class="p"&gt;}&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;

     &lt;span class="c1"&gt;// ... the rest of initWindow() ...     &lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ... unrelated code is omitted for brevity ...   &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Notify The Main App
&lt;/h2&gt;

&lt;p&gt;Jetpack Compose automatically refreshes notes if we change them in the view model. However, as it's impossible to get the view model in the service, we must invoke the refresh manually. &lt;/p&gt;

&lt;p&gt;First, let's introduce a simple broadcast receiver that invokes lambda when the event is received. The event is sent by the &lt;code&gt;NotesDb&lt;/code&gt; introduced above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;NOTES_RECEIVER_ACTION&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.localazy.quicknote.actions.UPDATE_NOTES"&lt;/span&gt;  

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotesReceiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BroadcastReceiver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now use the broadcast receiver in the view model - we create a broadcast aware view model.&lt;/p&gt;

&lt;p&gt;When the event is received, we simply load notes from the Room database again. It leads to a change in the view model and refreshes the list of notes in our main app. The data-related logic is kept inside the view model and separated from UI, and it should be this way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotesViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AndroidViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationContext&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;updateReceiver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NotesReceiver&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;loadItemsFromDb&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerReceiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updateReceiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IntentFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NOTES_RECEIVER_ACTION&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  
    &lt;span class="nf"&gt;loadItemsFromDb&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCleared&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCleared&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unregisterReceiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updateReceiver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ... unrelated code is omitted for brevity ...  &lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;At this point, we have finished our note-taking app!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Localization &amp;amp; Volunteers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I skyrocketed Floating Apps by translating it to 30 languages&lt;/strong&gt;, and one of the things I would like to teach you is how you can do the same. &lt;/p&gt;

&lt;p&gt;We now have an excellent note-taking app that brings value to our users. Let's convert some of them to volunteers/contributors that help us to localize the app to more languages.&lt;/p&gt;

&lt;p&gt;First, we need to give them the right tool for it, and &lt;strong&gt;ask them to help us&lt;/strong&gt;. So let's create a language selector that is great for normal users as they can switch language, but it can also help us to communicate with our potential contributors. In &lt;a href="https://localazy.com/blog/floating-windows-on-android-1-jetpack-compose-and-room"&gt;the first article&lt;/a&gt;, we have integrated Localazy and its awesome localization library for Android, which is actually integrated automatically. By default, it resolves the language the same way as Android does. But we can force different locale if we want so. &lt;/p&gt;

&lt;p&gt;Let's create a view model for our language selector. As the Localazy library is available, it's quite simple. &lt;code&gt;LocalazyWrapperListener&lt;/code&gt; simplifies the standard &lt;code&gt;LocalazyListener&lt;/code&gt;, and you can find it on &lt;a href="https://github.com/vaclavhodek/quicknote_8/blob/master/app/src/main/java/com/localazy/quicknote/localazy/LocalazyWrapperListener.kt"&gt;Github&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocaleViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AndroidViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;localazyListener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalazyWrapperListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;locales&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocalazyLocale&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;  

  &lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nc"&gt;Localazy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;localazyListener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;locales&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Localazy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLocales&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a simple &lt;code&gt;@Composable&lt;/code&gt; to show a list of available languages with a '&lt;em&gt;help us translate&lt;/em&gt;' message. Notice that the message is kept in English and is not translated. That's for purpose because we want to attract people who are likely to understand English good enough to supply an accurate translation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;  
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ShowLocales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
  &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocalazyLocale&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;  
  &lt;span class="n"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LocalazyLocale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="n"&gt;onHelp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;  
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nc"&gt;Column&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nc"&gt;LazyColumnFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="nc"&gt;TextButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"${it.localizedName}${if (!it.isFullyTranslated) "&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;incomplete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s"&gt;" else ""}"&lt;/span&gt;  
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
      &lt;span class="p"&gt;}&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
    &lt;span class="nc"&gt;TextButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;onHelp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Help us translate the app!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the last thing to do is to wrap everything into &lt;code&gt;LocaleActivity&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocaleActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;localesViewModel&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModels&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocaleViewModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;  

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="nc"&gt;ShowLocales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="n"&gt;localesViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="n"&gt;onChange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  

          &lt;span class="c1"&gt;// Change the locale and persist the new choice.&lt;/span&gt;
          &lt;span class="nc"&gt;Localazy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forceLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  

          &lt;span class="c1"&gt;// Stop the service and reopen MainActivity with clearing top.  &lt;/span&gt;
          &lt;span class="c1"&gt;// MainActivity restarts the service, so the locale change  &lt;/span&gt;
          &lt;span class="c1"&gt;// is applied across both activity and the service.  &lt;/span&gt;
          &lt;span class="nf"&gt;startFloatingService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;INTENT_COMMAND_EXIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
          &lt;span class="nf"&gt;startActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="nd"&gt;@LocaleActivity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
            &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FLAG_ACTIVITY_CLEAR_TOP&lt;/span&gt;  
          &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="p"&gt;},&lt;/span&gt;  
        &lt;span class="n"&gt;onHelp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
          &lt;span class="c1"&gt;// Open the project on Localazy to allow contributors to help us with translating.  &lt;/span&gt;
          &lt;span class="nf"&gt;startActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
            &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACTION_VIEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Localazy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProjectUri&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
              &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FLAG_ACTIVITY_NEW_TASK&lt;/span&gt;  
            &lt;span class="p"&gt;}&lt;/span&gt;  
          &lt;span class="p"&gt;)&lt;/span&gt;  
        &lt;span class="p"&gt;}&lt;/span&gt;  
      &lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have to add the newly created &lt;code&gt;LocaleActivity&lt;/code&gt; to &lt;code&gt;AndroidManifest.xml&lt;/code&gt; and also add a way to open it from &lt;code&gt;MainActivity&lt;/code&gt;. See &lt;em&gt;Results&lt;/em&gt; below for the video of the fully functional app and the full code on &lt;a href="https://github.com/vaclavhodek/quicknote_8"&gt;Github&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Btw, notice that there is no &lt;code&gt;values-XX&lt;/code&gt; folder in the project, just the base language's &lt;code&gt;values&lt;/code&gt; folder.&lt;/strong&gt; All other languages are supplied by Localazy automatically. You don't need to care about it at all ;-).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's simple, but it works!&lt;/strong&gt; Just asking my users, I get hundreds of people helping me translate the app, suggest new ideas, and hunt bugs. I owe all my knowledge shared with you in these articles to users who helped me along the way! Thanks!&lt;/p&gt;

&lt;p&gt;If you want to read more about this topic, be sure to check &lt;a href="https://localazy.com/blog/how-i-converted-floating-apps-to-localazy"&gt;how I converted Floating Apps to Localazy&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;And here comes our final app!&lt;/strong&gt; Fully working notes-taking app with floating technology and externally manage languages with seamless locale switching. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/8U7mRdjpouw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We are not yet done!&lt;/strong&gt; I prepared two more articles to teach you tips, tricks, and shortcomings of floating technology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;The whole source code for this article is &lt;a href="https://github.com/vaclavhodek/quicknote_8"&gt;available on Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay Tuned
&lt;/h2&gt;

&lt;p&gt;Eager to learn more about Android development? Follow me (&lt;a href="https://twitter.com/vaclavhodek"&gt;@vaclavhodek&lt;/a&gt;) and Localazy (&lt;a href="https://twitter.com/localazy"&gt;@localazy&lt;/a&gt;) on Twitter, or like &lt;a href="https://www.facebook.com/localazy"&gt;Localazy on Facebook&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Series
&lt;/h2&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;Floating Windows on Android&lt;/strong&gt; series. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-1-jetpack-compose-and-room"&gt;Floating Windows on Android 1: Jetpack Compose &amp;amp; Room&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-2-foreground-service"&gt;Floating Windows on Android 2: Foreground Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-3-permissions"&gt;Floating Windows on Android 3: Permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-4-floating-window"&gt;Floating Windows on Android 4: Floating Window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-5-moving-window"&gt;Floating Windows on Android 5: Moving Window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-6-keyboard-input"&gt;Floating Windows on Android 6: Keyboard Input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-7-boot-receiver"&gt;Floating Windows on Android 7: Boot Receiver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-8-the-final-app"&gt;Floating Windows on Android 8: The Final App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-9-shortcomings"&gt;Floating Windows on Android 9: Shortcomings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://localazy.com/blog/floating-windows-on-android-10-tips-and-tricks"&gt;Floating Windows on Android 10: Tips &amp;amp; Tricks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
