Skip to content

Commit a8ac10d

Browse files
committed
Merge branch 'daniel/i18n-release'
2 parents 802be74 + bae325c commit a8ac10d

30 files changed

+867
-707
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
},
1818
"main": "src/main.ts",
1919
"dependencies": {
20+
"@formatjs/intl-displaynames": "^3.3.4",
2021
"@linusborg/vue-simple-portal": "^0.1.4",
21-
"@nimiq/hub-api": "^1.2.0",
22+
"@nimiq/hub-api": "^1.2.5",
2223
"@nimiq/iqons": "^1.5.2",
2324
"@nimiq/network-client": "^0.5.0",
2425
"@nimiq/style": "^0.8.2",

src/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export default defineComponent({
106106
overflow: hidden; // To prevent horizontal scrollbars during panel sliding
107107
108108
/* Default: >= 1500px */
109-
--sidebar-width: 23rem;
109+
--sidebar-width: 24rem;
110110
--account-column-width: 70rem;
111111
--address-column-width: 150rem;
112112

src/components/AccountMenu.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,9 @@ export default defineComponent({
324324
325325
.menu .item {
326326
align-items: center;
327-
line-height: 3.75rem;
327+
line-height: 1.2;
328328
width: 100%;
329-
padding: 0.625rem 1.25rem;
330-
white-space: nowrap;
329+
padding: 1.125rem 1.25rem;
331330
border-radius: 0.5rem;
332331
color: var(--text-70);
333332
@@ -354,6 +353,7 @@ export default defineComponent({
354353
355354
.menu .item .alert {
356355
margin-left: auto;
356+
flex-shrink: 0;
357357
font-size: 2.5rem;
358358
color: var(--nimiq-orange);
359359
}
@@ -362,6 +362,7 @@ export default defineComponent({
362362
width: 2.75rem;
363363
height: 3rem;
364364
margin: -0.125rem 1rem -0.125rem 0;
365+
flex-shrink: 0;
365366
opacity: 0.6;
366367
}
367368

src/components/MobileActionBar.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<template>
22
<div class="mobile-action-bar flex-row">
33
<button class="receive nq-button-s flex-row" @click="$router.push('/receive')" @mousedown.prevent>
4-
<ArrowRightSmallIcon />Receive
4+
<ArrowRightSmallIcon />{{ $t('Receive') }}
55
</button>
66
<button class="send nq-button-pill light-blue flex-row"
77
@click="$router.push('/send')" @mousedown.prevent
88
:disabled="!activeAddressInfo || !activeAddressInfo.balance"
99
>
10-
<ArrowRightSmallIcon />Send
10+
<ArrowRightSmallIcon />{{ $t('Send') }}
1111
</button>
1212
<button class="reset scan-qr" @click="$router.push('/scan')">
1313
<ScanQrCodeIcon/>

src/components/NetworkMap.vue

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
</h3>
3131
<p v-if="peer.locationData.country"
3232
:class="{'self': peer.type === 0 /* SELF */, 'connected': peer.connected}">
33-
{{ peer.locationData.city ? `${peer.locationData.city},` : '' }}
34-
{{ peer.locationData.countryFull }}
33+
{{ getPeerCity(peer) ? `${getPeerCity(peer)},` : '' }}
34+
{{ getPeerCountry(peer) }}
3535
</p>
3636
</div>
3737
</template>
@@ -41,11 +41,31 @@
4141
</template>
4242

4343
<script lang="ts">
44+
import { shouldPolyfill as shouldPolyFillIntlDisplayNames } from '@formatjs/intl-displaynames/should-polyfill';
4445
import { defineComponent, onMounted, onUnmounted, ref, computed, watch } from '@vue/composition-api';
4546
import { Tooltip, HexagonIcon } from '@nimiq/vue-components';
4647
import { NetworkClient } from '@nimiq/network-client';
4748
import { getNetworkClient } from '../network';
48-
import NetworkMap, { NodeHexagon, WIDTH, HEIGHT, SCALING_FACTOR, NodeType } from '../lib/NetworkMap';
49+
import NetworkMap, { NodeHexagon, WIDTH, HEIGHT, SCALING_FACTOR, Node, NodeType } from '../lib/NetworkMap';
50+
import { useSettingsStore } from '../stores/Settings';
51+
52+
type IntlDisplayNames = import('@formatjs/intl-displaynames').DisplayNames;
53+
type IntlDisplayNamesOptions = import('@formatjs/intl-displaynames').DisplayNamesOptions;
54+
// eslint-disable-next-line @typescript-eslint/no-namespace
55+
declare namespace Intl {
56+
let DisplayNames: undefined | {
57+
new (
58+
locales?: string | string[],
59+
options?: IntlDisplayNamesOptions,
60+
): IntlDisplayNames,
61+
62+
readonly polyfilled?: true,
63+
};
64+
}
65+
66+
const intlDisplayNamesReadyPromise = !Intl.DisplayNames?.polyfilled && shouldPolyFillIntlDisplayNames()
67+
? import('@formatjs/intl-displaynames/polyfill')
68+
: Promise.resolve();
4969
5070
export default defineComponent({
5171
setup(props, context) {
@@ -115,6 +135,40 @@ export default defineComponent({
115135
const ownXCoordinate = computed(() => ownNode.value ? ownNode.value.x : null);
116136
watch(ownXCoordinate, (x) => x !== null && context.emit('own-x-coordinate', (x / 2) * SCALING_FACTOR));
117137
138+
const { language } = useSettingsStore();
139+
let i18nCountryName: IntlDisplayNames | null = null;
140+
141+
watch(language, async () => {
142+
i18nCountryName = null;
143+
// TODO polyfill can be removed in the future and i18nCountryName then changed to a non-async computed prop
144+
await intlDisplayNamesReadyPromise;
145+
if (Intl.DisplayNames!.polyfilled) {
146+
// has to be imported after the polyfill is ready
147+
await import(
148+
/* webpackChunkName: "country-names-[request]" */
149+
/* webpackInclude: /\/\w{2}\.js$/ */
150+
`@formatjs/intl-displaynames/locale-data/${language.value}`);
151+
}
152+
i18nCountryName = new Intl.DisplayNames!(language.value, { type: 'region' });
153+
});
154+
155+
function getPeerCity(peer: Node) {
156+
const fallbackCityName = peer.locationData.city;
157+
const { i18nCityNames } = peer.locationData;
158+
if (!i18nCityNames) return fallbackCityName;
159+
// Try to find a translation for current language
160+
const availableLanguage = i18nCityNames[language.value]
161+
? language.value
162+
: Object.keys(i18nCityNames).find((locale) => locale.startsWith(language.value)); // accept zh-CH for zh
163+
return availableLanguage ? i18nCityNames[availableLanguage] : fallbackCityName;
164+
}
165+
166+
function getPeerCountry(peer: Node) {
167+
return i18nCountryName && peer.locationData.country
168+
? i18nCountryName.of(peer.locationData.country)
169+
: peer.locationData.country;
170+
}
171+
118172
return {
119173
$container,
120174
$network,
@@ -123,6 +177,8 @@ export default defineComponent({
123177
scale,
124178
width,
125179
height,
180+
getPeerCity,
181+
getPeerCountry,
126182
};
127183
},
128184
components: {

src/components/TestnetFaucet.vue

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,17 @@ type FaucetInfoResponse = {
3434
type FaucetTapResponse = {
3535
success: false,
3636
msg: string,
37-
error: 'VAPTCHA_UNAVAILABLE'
37+
error?: 'VAPTCHA_UNAVAILABLE'
3838
| 'INVALID_CAPTCHA'
3939
| 'INVALID_ADDRESS'
40-
| 'RATE_LIMIT'
4140
| 'GEOBLOCKED'
4241
| 'OUT_OF_FUNDS'
4342
| 'TRANSACTION_FAILED',
43+
} | {
44+
success: false,
45+
msg: string,
46+
error: 'RATE_LIMIT',
47+
wait: number,
4448
} | {
4549
success: true,
4650
msg: string,
@@ -110,12 +114,32 @@ export default defineComponent({
110114
}
111115
112116
loading.value = false;
113-
114-
if (result.error === 'TRANSACTION_FAILED') {
115-
// Reset button for user to try again
116-
unavailableMsg.value = context.root.$t('Faucet error - please try again');
117-
} else {
118-
errorMsg.value = result.msg; // Disables button and shows the error message.
117+
errorMsg.value = '';
118+
unavailableMsg.value = '';
119+
120+
switch (result.error) {
121+
case 'RATE_LIMIT':
122+
errorMsg.value = context.root.$t(
123+
'You can receive more free NIM in {waitTime} hours.',
124+
{ waitTime: Math.ceil(result.wait / 3600) },
125+
);
126+
break;
127+
case 'GEOBLOCKED':
128+
errorMsg.value = context.root.$t(
129+
'This service is currently not available in your region.',
130+
);
131+
break;
132+
case 'OUT_OF_FUNDS':
133+
errorMsg.value = context.root.$t('There are currently no free NIM available.');
134+
break;
135+
case 'TRANSACTION_FAILED':
136+
// Set unavailableMsg instead of errorMsg to keep button active for user to try again
137+
unavailableMsg.value = context.root.$t('Faucet error - please try again.');
138+
break;
139+
default:
140+
// 'INVALID_CAPTCHA', 'VAPTCHA_UNAVAILABLE', 'INVALID_ADDRESS' or unspecified errors should
141+
// not occur via this frontend, therefore no need to translate them.
142+
errorMsg.value = `${context.root.$t('Request failed')}: ${result.msg}`;
119143
}
120144
121145
return false;
@@ -154,9 +178,12 @@ button {
154178
155179
.unavailable,
156180
.error {
181+
padding: 0 5rem;
157182
font-weight: 600;
183+
text-align: center;
158184
159185
svg {
186+
display: inline-block;
160187
width: 2.5rem;
161188
height: 2.5rem;
162189
margin-right: 1rem;

src/components/TransactionList.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,7 @@ export default defineComponent({
603603
.nq-h1 {
604604
margin-top: 0;
605605
margin-bottom: 1.5rem;
606+
text-align: center;
606607
}
607608
608609
span {

src/components/TransactionListItem.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,12 @@ export default defineComponent({
154154
});
155155
156156
// Date
157+
const { language } = useSettingsStore();
157158
const date = computed(() => props.transaction.timestamp && new Date(props.transaction.timestamp * 1000));
158159
const dateDay = computed(() => date.value && twoDigit(date.value.getDate()));
159-
const MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
160-
const dateMonth = computed(() => date.value && MONTHS[date.value.getMonth()]);
160+
const monthFormatter = computed(() => new Intl.DateTimeFormat(language.value, { month: 'short' }));
161+
const dateMonth = computed(() => date.value && monthFormatter.value
162+
&& monthFormatter.value.format(date.value as Date));
161163
const dateTime = computed(() => date.value
162164
&& `${twoDigit(date.value.getHours())}:${twoDigit(date.value.getMinutes())}`);
163165
@@ -238,6 +240,7 @@ svg {
238240
> .month {
239241
font-size: var(--small-label-size);
240242
letter-spacing: 0.0667em;
243+
text-transform: uppercase;
241244
}
242245
}
243246

src/components/layouts/AddressOverview.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ export default defineComponent({
416416
font-size: var(--body-size);
417417
padding: 0.5rem;
418418
font-weight: 600;
419+
word-break: keep-all;
419420
border-radius: 0.5rem;
420421
z-index: 1;
421422
pointer-events: none;

src/components/layouts/Settings.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
{{ $t('Change your language setting.') }}
2020
</p>
2121
</div>
22-
<select id="language" name="language" @input="setLanguage($event.target.value)" disabled>
22+
<select id="language" name="language" @input="setLanguage($event.target.value)">
2323
<option
2424
v-for="lang in Languages" :key="lang.code"
2525
:value="lang.code" :selected="language === lang.code"

0 commit comments

Comments
 (0)