Implement i18n support in frontend using Angular + Transifex + NGINX

This PR adds basic i18n support into the mempool frontend, together with
a smooth workflow for developers and translators to collaborate:

* Using the existing @angular/localize module, developers add i18n
metadata to any frontend strings their new features or changes modify

* Using the new npm script `i18n-extract-from-source`, developers
extract the i18n data from source code into `src/locale/messages.xlf`

* After pushing the updated `src/locale/messages.xlf` to GitHub, the
Transifex service will update its database from the new source data

* Using the Transifex website UI, translators can work together to
translate all the mempool frontend strings into their native languages

* Using the new npm script `i18n-pull-from-transifex`, developers can
pull in completed translations from Transifex, and commit them into git.

This flow requires an API key from Transifex, which can be obtained at
https://www.transifex.com/user/settings/api/ to be used with the python
script installed by `pip install transifex-client` - after preparing
these, run the npm script which will ask you for the API key the first
time. When downloading is complete, you can test building the frontend,
and if successful, commit the new strings files into git.

This PR implements a new locale selector in the footer of the homepage
dashboard, and includes WIP translations for the following languages:

* Czech (cs)
* German (de)
* Japanese (ja)
* Norwegian (nn)
* Spanish (es)
* Swedish (sv)
* Ukrainian (uk)
* Persian (fa)
* Portugese (pt)
* Turkish (tr)
* Dutch (nl)
* French (fr)
* Chinese (zh)
* Slovenian (sl)
* Korean (ko)
* Polish (pl)

The user-agent's `Accept-Language` header is used to automatically
detect their preferred language, which can be manually overriden by the
pull-down selector, which saves their preference to a cookie, which is
used by nginx to serve the correct HTML bundle to the user.

Remaining tasks include adding i18n metadata for strings in the Bisq and
Liquid frontend code, mouseover hover tooltip strings, hard-coded og
metadata inside HTML templates, and many other places. This will be done
in a separate PR.

When upgrading to add i18n support, mempool instance operators must take
care to install the new nginx.conf and nginx-mempool.conf files, and
tweak for their specific site configuration.

Fixes #81
This commit is contained in:
wiz
2020-12-02 04:19:33 +09:00
parent f151eb81c8
commit 4658b47007
60 changed files with 41826 additions and 451 deletions

View File

@@ -10,16 +10,16 @@
<br>
<h2>About the project</h2>
<h2 i18n="about.about-the-project">About the project</h2>
<div class="row row-cols-1">
<div class="col col-md-6 offset-md-3">
<p>The mempool open-source project aims to implement a high quality explorer and visualization website for the entire Bitcoin ecosystem, without distractions like altcoins, advertising, or third-party trackers.</p>
<p i18n>The mempool open-source project aims to implement a high quality explorer and visualization website for the entire Bitcoin ecosystem, without distractions like altcoins, advertising, or third-party trackers.</p>
</div>
</div>
<br>
<h2>Maintainers</h2>
<h2 i18n="about.maintainers">Maintainers</h2>
<div class="container text-center">
<div class="row row-cols-2">
@@ -29,7 +29,7 @@
@softsimon_
</a>
<br>
Development
<span i18n="about.development">Development</span>
</div>
<div class="col col-md-2">
<a href="https://twitter.com/wiz">
@@ -37,14 +37,14 @@
@wiz
</a>
<br>
Operations
<span i18n="about.operations">Operations</span>
</div>
</div>
</div>
<br><br>
<h2>Sponsors ❤️</h2>
<h2 i18n="about.sponsors.withHeart">Sponsors ❤️</h2>
<div *ngIf="sponsors === null">
<br>
@@ -60,9 +60,9 @@
</ng-template>
<br><br>
<button type="button" class="btn btn-primary" (click)="donationStatus = 2" [hidden]="donationStatus !== 1">Become a sponsor ❤️</button>
<button type="button" class="btn btn-primary" (click)="donationStatus = 2" [hidden]="donationStatus !== 1" i18n="about.become-a-sponsor">Become a sponsor ❤️</button>
<p *ngIf="donationStatus === 2 && !sponsorsEnabled">
Navigate to <a href="https://mempool.space/about" target="_blank">https://mempool.space/about</a> to sponsor
<span i18n="about.navigate-to">Navigate to</span> <a href="https://mempool.space/about" target="_blank">https://mempool.space/about</a> <span i18n="about.to-sponsor">to sponsor</span>
</p>
<div style="max-width: 300px;" class="mx-auto" [hidden]="donationStatus !== 2 || !sponsorsEnabled">
@@ -79,17 +79,17 @@
</div>
<input formControlName="handle" class="form-control" type="text" placeholder="Twitter handle (Optional)">
</div>
<div class="required" *ngIf="donationForm.get('amount').hasError('required')">Amount required</div>
<div class="required" *ngIf="donationForm.get('amount').hasError('min')">Minimum amount is 0.001 BTC</div>
<div class="required" *ngIf="donationForm.get('amount').hasError('required')" i18n="about.sponsor.amount-required">Amount required</div>
<div class="required" *ngIf="donationForm.get('amount').hasError('min')" i18n="about.sponsor.minimum-amount">Minimum amount is 0.001 BTC</div>
<div class="input-group mt-4">
<button class="btn btn-primary mx-auto" type="submit" [disabled]="donationForm.invalid">Request invoice</button>
<button class="btn btn-primary mx-auto" type="submit" [disabled]="donationForm.invalid" i18n="about.sponsor.request-invoice">Request invoice</button>
</div>
</form>
</div>
<ng-template #lowAmount>
<div class="input-group mb-4 text-small">
If you donate 0.01 BTC or more, your profile photo will be added to the list of sponsors above :)
<span i18n="about.sponsor.description">If you donate 0.01 BTC or more, your profile photo will be added to the list of sponsors above :)</span>
</div>
</ng-template>
@@ -167,13 +167,13 @@
<p style="font-size: 12px;">{{ donationObj.amount }} BTC</p>
</ng-template>
<p>Waiting for transaction... </p>
<p i18n="about.sponsor.waiting-for-transaction">Waiting for transaction... </p>
<div class="spinner-border text-light"></div>
</div>
<div *ngIf="donationStatus === 4" class="text-center">
<h2>Donation confirmed!<br>Thank you!</h2>
<p>If you specified a Twitter handle, the profile photo should now be visible on this page when you reload.</p>
<h2><span i18n="about.sponsor.donation-confirmed">Donation confirmed!</span><br><span i18n="about.sponsor.thank-you">Thank you!</span></h2>
<p i18n="about.sponsor.sponsor-completed">If you specified a Twitter handle, the profile photo should now be visible on this page when you reload.</p>
</div>
<br><br><br><br>
@@ -203,7 +203,7 @@
<br><br>
<div class="text-center">
<a [routerLink]="['/terms-of-service']">Terms of Service</a>
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
</div>
</div>