Multilingual Angular 16

Here is a short quick-start-guide to create a new Angular 16 SPA with Material Design and multi-language support.

You need Node.js to install and build Angular.

Install the Angular CLI with the following commands:

npm i -g @angular/cli

Create the example SPA Teach:

ng new Teach

Start it:

cd Teach
ng serve

Open http://localhost:4200 with a browser.

Material Design

Add Angular Material Design:

ng add @angular/material

Replace the content of app.component.html with:

<mat-slide-toggle checked="true">Hello world!</mat-slide-toggle>

Edit the app.module.ts to include the component. Visit the Angular Material homepage to learn more about it.

import { MatSlideToggleModule } from '@angular/material/slide-toggle';

@NgModule({
  ...
  imports: [
    MatSlideToggleModule
  ]
  ...
})
export class AppModule { }

Localization

Internationalization or i18n in short.

ng add @angular/localize

Add i18n directive to the template app.component.html:

<mat-slide-toggle checked="true" i18n>Hello world!</mat-slide-toggle>

Here we are adding new locale German.

The index.html is not translated by Angular, use different index.html pages for each language.

Update angular.json.

{
  "projects": {
    "architect": {
      "build": {
        "builder": "@angular-devkit/build-angular:browser",
        "options": {
            "localize": true,
        },
        "configurations": {
            "de": {
              "index": {
                "input": "app/index.de.html",
                "output": "index.html"
              },
              "localize": ["de"]
            }
        }
      },
      "serve": {
        "builder": "@angular-devkit/build-angular:dev-server",
        "configurations": {
          "dev-de": {
            "browserTarget": "Teach:build:development,de"
          },
          "defaultConfiguration": "dev-de"
        }
      },
      "extract-i18n": {
        "builder": "@angular-devkit/build-angular:extract-i18n",
        "options": {
          "browserTarget": "Teach:build"
        }
      }
    },
    "i18n": {
      "sourceLocale": "en",
      "locales": {
        "de": {
          "baseHref": "",
          "translation": "./src/locale/messages.de.xlf"
        }
      }
    }
  }
}

Extract templates messages to src/locale/messages.xlf

ng extract-i18n --format=xlf2 --output-path src/locale

Make a copy of the src/locale/messages.xlf as src/locale/messages.de.xlf.

Edit src/locale/messages.de.xlf and add new line <target></target> below each <source>...</source> lines.

Start with a specific configuration to verify the result:

ng serve --configuration=dev-de

Build

The build stores the resulting files in dist folder. Containing the SPA in each language.

ng build

The files contains a hash-code in the name to mitigate caching problem. It doesn't help us when we use rewrite-function of the web-server as the hash-code is the same for each language.

Use a custom webpack-config to generate different hashes:

{
  "projects": {
    "architect": {
      "build": {
        "builder": "@angular-builders/custom-webpack:browser",
        "options": {
          "customWebpackConfig": {
            "path": "./webpack.config.js",
            "replaceDuplicatePlugins": true
          }
        },
        "configurations": {
          "en": {
            "localize": [ "en" ]
          },
          "de": {
            "localize": [ "de" ]
          }
        }
      }
    }
  }
}

Add webpack.config.js file:

module.exports = {
  optimization: {
    relContentHash: false
  }
}

Currently, the custom-webpack for Angular 16 is still in beta:

npm i --save-dev @angular-builders/custom-webpack@16.0.0-beta.0

Now we have to call build for each language separately or else the hash still stay the same:

ng build --configuration="production,en"
ng build --configuration="production,de" --delete-output-path=false

Anonymous Listener

Using SAM Single Abstract Method interfaces, creating implementation is realy easy with kotlin when you have an Java interface. Here I'm only interested in position value:

private val itemClickListener = OnItemClickListener { _, _, position, _ ->
  // do the onItemClick thing
}

But when you have a Kotlin interface it's getting verbose. The situtation is inconsistent, looks like Kotlin is still incomplete. Create an anonymoys object then override the single function:

@FunctionalInterface
interface OnPositiveClickListener {
  fun doPositiveClick(id: Long)
}

private val itemClickListener = object : OnPositiveClickListener {
  override fun doPositiveClick(id: Long) {
    // do the doPositiveClick thing
  }
}

For simplicity, use a workaround and declare the interface in Java instead of Kotlin:

// in .java file
public interface OnPositiveClickListener {
  void doPositiveClick(long id);
}

// in .kt kotlin
private val itemClickListener = OnPositiveClickListener {
  // do the doPositiveClick thing
}

Or don't use an inferface, use a functional interface instead:

fun interface OnPositiveClickListener {
  fun doPositiveClick(id: Long)
}

// in .kt kotlin
private val itemClickListener = OnPositiveClickListener {
  // do the doPositiveClick thing
}

Wisecards Android App

Last year in 2021 I re-continued to learn Chinese. I still use my Android Flashcard App, which is about 14 years in development now. While using it, the urge for some improvement. Much time is spent keeping up-to-date with changes in Android Development when coming back to a pet-project. The latest update was in 2018 and a year later I 'finished' migrating the code from Java to Kotlin and changed all database code from directly using Cursor to Room. The migration went well but using Room for all database access was not a great idea because some parts of the App become too slow for my taste. Some Room usage had to be rolled back, that was the time when I made a break. It still had no new feature, nothing to publish.

Now it's in a state where I can add new experimental features:

Wisecards Android App - Daily usage
Daily usage stats.
Wisecards Android App - Review Reminder
Review reminder.

CURL for Windows

Sometimes you are stuck on a Windows Server and want to execute a curl command to call a REST endpoint. There is an alternative for PowerShell Invoke-WebRequest or Invoke-RestMethod.

POST with Body:

Invoke-WebRequest -Headers @{"Authorization" = "Bearer F8977DS12FPQQMJD"} `
                  -ContentType "application/json" `
                  -Method POST `
                  -Body "module=trades" `
                  -Uri https://api.tea.ch/assets

GET

Invoke-WebRequest -Headers @{"Authorization" = "Bearer F8977DS12FPQQMJD"} `
                  -ContentType "application/json" `
                  -Method GET `
                  -Uri https://api.tea.ch/assets

In a single line:

Invoke-RestMethod -Method GET -Header @{"Authorization" = "Bearer F8977DS12FPQQMJD"} -ContentType "application/json" -uri "https://api.tea.ch/assets"

Hide a mat-tab in Angular

In Angular, hiding a mat-tab in a mat-tab-group is not straitforward. You cannot use [hidden]=isTabHidden or any other directive to remove the tab.

There is a workaround listed in the GitHub feature request 14227.

Here is the workaround that worked for me:

<mat-tab-group
  (selectedTabChange)="selectTab($event)"
  [selectedIndex]="activeTab"
  [ngClass]="{'hide-tab1': hideTab1Flag}">
  <mat-tab label="Tab0"></mat-tab>
  <mat-tab label="Tab1"></mat-tab>
  <mat-tab label="Tab2"></mat-tab>
</mat-tab-group>

The in the css file:

.hide-tab1 div.mat-tab-label:nth-child(2) {
  display: none;
}

Start a NuxtJS Project

I started rebuilding this website in January 2021 with NuxtJS. So far it the most pleasant to work with. It is a framework based on Vue.js allowing to use it with SSR (Server Side Rendering), go to the official documentation of NuxtJS at https://nuxtjs.org/

npm init nuxt-app teach

Here are my choices for the project:

  • JavaScript
  • No CSS framework
  • Axios and Content NuxtJS modules because I intent to use them in the project.
  • ESLint for some help checking my code.
  • I know Jest and use it as testing framework.
  • Universal SSR
  • Server (Node.js hosting)
  • jsconfig.json as it is recommended for VS Code
  • No CI as this is a personal project
  • Use git with repository on local network or usb-stick
continue reading

Change Prism Theme

NuxtJS Content module uses Prism for syntax highlighting. The themes are located in node_modules/prismjs/themes.

I copy the prism-tomorrow.css to the assets folder. The change must be configured in nuxt.config.js:

export default {
  content: {
    markdown: {
      prism: {
        theme: 'assets/prism-tomorrow.css'
      }
    }
  }
}

.NET 5 Notes

With .NET 5 comes new C# 9.0 language features. To try it out I use the following dotnet commands:

dotnet new console -o Test1
cd Test1
dotnet build
dotnet run

C# 9.0

  • No need for a Main() in Program.cs
  • Properties with setter only, using keyword init
  • Use new expression without class name.
  • Check for not null with is not null
using System;

var p1 = new Program("Hello World!");
Console.WriteLine(p1.Message);

Program p2 = new("new expression without class-name!");
if (p2 is not null)
{
  Console.WriteLine(p2.Message);
}

class Program
{
  public string Message { get; init; } 

  public Program(string message)
  {
    this.Message = message;
  }
}

Add a Build Date

For some content that are only known at build time like the build-date. You can use the env property. In nuxt.config.js add:

const months = ['January', 'February', 'March', 'April', 'Mai', 'June',
  'July', 'August', 'September', 'October', 'November', 'December']
const now = new Date()
const today = `${now.getDate()} ${months[now.getMonth()]}, ${now.getFullYear()}`

export default {
  env: {
    buildDate: today
  }
}

In the page or component script:

  data() {
    return {
      buildDate: process.env.buildDate
    }
  }

And reference it in template:

<div>{{ buildDate }}</div>

Macbook Pro Retina late2012 with GTX 1080 eGPU Upgrade

I upgraded my 6 year old Macbook Pro 13" from 3DMark 11 gaming performance P721 to P9412 and play recent games with highest settings.

I recently was looking for a new notebook with Thunderbolt 3 to replace my late 2012 Macbook Pro 13 inch. I wanted to play some games but didn't want to buy a desktop PC. So it has to be a notebook with Thunderbolt 3 so I could attach an eGPU to the USB-C port.

During my research I found egpu.io where I could read about external GPUs that are available on the market. I also learned about Thunderbolt 3 to Thunderbolt 2/1 adapter. There were other Macbook Pro owners who succeeded to get eGPU to run with an adapter.

continue reading

Chinese Steamed Eggs Recipe

One of my favorite dish that I can cook myself is Chinese Steamed Eggs its really easy to make.

Ingredients:

  • 3 Eggs
  • 250ml Water
  • 1 Tablespoon Oil
  • 1/4 Teaspoon Salt
  • 1 Spring onion
  • 1/2 Teaspoon light soy sauce
continue reading