feat: add language to topmenu + globals cleanup + i18n refactor (#3510)

This commit is contained in:
dni ⚡ 2025-11-12 15:07:53 +01:00 committed by GitHub
parent 5e36bb4fcd
commit b6f533be24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 90 additions and 76 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -66,7 +66,7 @@ window.LNbits = {
} }
newWallet.msat = data.balance_msat newWallet.msat = data.balance_msat
newWallet.sat = Math.floor(data.balance_msat / 1000) newWallet.sat = Math.floor(data.balance_msat / 1000)
newWallet.fsat = new Intl.NumberFormat(window.LOCALE).format( newWallet.fsat = new Intl.NumberFormat(window.i18n.global.locale).format(
newWallet.sat newWallet.sat
) )
if (newWallet.walletType === 'lightning-shared') { if (newWallet.walletType === 'lightning-shared') {
@ -105,7 +105,9 @@ window.LNbits = {
obj.msat = obj.amount obj.msat = obj.amount
obj.sat = obj.msat / 1000 obj.sat = obj.msat / 1000
obj.tag = obj.extra?.tag obj.tag = obj.extra?.tag
obj.fsat = new Intl.NumberFormat(window.LOCALE).format(obj.sat) obj.fsat = new Intl.NumberFormat(window.i18n.global.locale).format(
obj.sat
)
obj.isIn = obj.amount > 0 obj.isIn = obj.amount > 0
obj.isOut = obj.amount < 0 obj.isOut = obj.amount < 0
obj.isPending = obj.status === 'pending' obj.isPending = obj.status === 'pending'

View file

@ -0,0 +1,37 @@
window.app.component('lnbits-language-dropdown', {
template: '#lnbits-language-dropdown',
mixins: [window.windowMixin],
methods: {
activeLanguage(lang) {
return window.i18n.global.locale === lang
},
changeLanguage(newValue) {
this.g.locale = newValue
window.i18n.global.locale = newValue
this.$q.localStorage.set('lnbits.lang', newValue)
}
},
data() {
return {
langs: [
{value: 'en', label: 'English', display: '🇬🇧 EN'},
{value: 'de', label: 'Deutsch', display: '🇩🇪 DE'},
{value: 'es', label: 'Español', display: '🇪🇸 ES'},
{value: 'jp', label: '日本語', display: '🇯🇵 JP'},
{value: 'cn', label: '中文', display: '🇨🇳 CN'},
{value: 'fr', label: 'Français', display: '🇫🇷 FR'},
{value: 'it', label: 'Italiano', display: '🇮🇹 IT'},
{value: 'pi', label: 'Pirate', display: '🏴‍☠️ PI'},
{value: 'nl', label: 'Nederlands', display: '🇳🇱 NL'},
{value: 'we', label: 'Cymraeg', display: '🏴󠁧󠁢󠁷󠁬󠁳󠁿 CY'},
{value: 'pl', label: 'Polski', display: '🇵🇱 PL'},
{value: 'pt', label: 'Português', display: '🇵🇹 PT'},
{value: 'br', label: 'Português do Brasil', display: '🇧🇷 BR'},
{value: 'cs', label: 'Česky', display: '🇨🇿 CS'},
{value: 'sk', label: 'Slovensky', display: '🇸🇰 SK'},
{value: 'kr', label: '한국어', display: '🇰🇷 KR'},
{value: 'fi', label: 'Suomi', display: '🇫🇮 FI'}
]
}
}
})

View file

@ -1,33 +1,3 @@
window.langs = [
{value: 'en', label: 'English', display: '🇬🇧 EN'},
{value: 'de', label: 'Deutsch', display: '🇩🇪 DE'},
{value: 'es', label: 'Español', display: '🇪🇸 ES'},
{value: 'jp', label: '日本語', display: '🇯🇵 JP'},
{value: 'cn', label: '中文', display: '🇨🇳 CN'},
{value: 'fr', label: 'Français', display: '🇫🇷 FR'},
{value: 'it', label: 'Italiano', display: '🇮🇹 IT'},
{value: 'pi', label: 'Pirate', display: '🏴‍☠️ PI'},
{value: 'nl', label: 'Nederlands', display: '🇳🇱 NL'},
{value: 'we', label: 'Cymraeg', display: '🏴󠁧󠁢󠁷󠁬󠁳󠁿 CY'},
{value: 'pl', label: 'Polski', display: '🇵🇱 PL'},
{value: 'pt', label: 'Português', display: '🇵🇹 PT'},
{value: 'br', label: 'Português do Brasil', display: '🇧🇷 BR'},
{value: 'cs', label: 'Česky', display: '🇨🇿 CS'},
{value: 'sk', label: 'Slovensky', display: '🇸🇰 SK'},
{value: 'kr', label: '한국어', display: '🇰🇷 KR'},
{value: 'fi', label: 'Suomi', display: '🇫🇮 FI'}
]
window.LOCALE = 'en'
window.dateFormat = 'YYYY-MM-DD HH:mm'
window.i18n = new VueI18n.createI18n({
locale: window.LOCALE,
fallbackLocale: window.LOCALE,
messages: window.localisation
})
const websocketPrefix =
window.location.protocol === 'http:' ? 'ws://' : 'wss://'
const websocketUrl = `${websocketPrefix}${window.location.host}/api/v1/ws`
window.g = Vue.reactive({ window.g = Vue.reactive({
offline: !navigator.onLine, offline: !navigator.onLine,
visibleDrawer: false, visibleDrawer: false,
@ -39,9 +9,16 @@ window.g = Vue.reactive({
fiatTracking: false, fiatTracking: false,
wallets: [], wallets: [],
payments: [], payments: [],
langs: [],
walletEventListeners: [], walletEventListeners: [],
updatePayments: false, updatePayments: false,
updatePaymentsHash: '', updatePaymentsHash: '',
walletFlip: Quasar.LocalStorage.getItem('lnbits.walletFlip') ?? false walletFlip: Quasar.LocalStorage.getItem('lnbits.walletFlip') ?? false,
locale:
Quasar.LocalStorage.getItem('lnbits.lang') ?? navigator.languages[1] ?? 'en'
}) })
window.dateFormat = 'YYYY-MM-DD HH:mm'
const websocketPrefix =
window.location.protocol === 'http:' ? 'ws://' : 'wss://'
const websocketUrl = `${websocketPrefix}${window.location.host}/api/v1/ws`

View file

@ -225,7 +225,15 @@ window.app.use(Quasar, {
} }
} }
}) })
window.i18n = new VueI18n.createI18n({
locale: window.g.locale,
fallbackLocale: 'en',
messages: window.localisation
})
window.app.use(window.i18n) window.app.use(window.i18n)
window.app.provide('g', g) window.app.provide('g', g)
window.app.use(window.router) window.app.use(window.router)
window.app.component('DynamicComponent', DynamicComponent) window.app.component('DynamicComponent', DynamicComponent)

View file

@ -168,7 +168,7 @@ window.PagePayments = {
p.timeFrom = moment.utc(p.created_at).local().fromNow() p.timeFrom = moment.utc(p.created_at).local().fromNow()
p.outgoing = p.amount < 0 p.outgoing = p.amount < 0
p.amount = p.amount =
new Intl.NumberFormat(window.LOCALE).format(p.amount / 1000) + new Intl.NumberFormat(this.g.locale).format(p.amount / 1000) +
' sats' ' sats'
if (p.extra?.wallet_fiat_amount) { if (p.extra?.wallet_fiat_amount) {
p.amountFiat = this.formatCurrency( p.amountFiat = this.formatCurrency(
@ -180,7 +180,7 @@ window.PagePayments = {
p.internal_memo = p.extra.internal_memo p.internal_memo = p.extra.internal_memo
} }
p.fee_sats = p.fee_sats =
new Intl.NumberFormat(window.LOCALE).format(p.fee / 1000) + ' sats' new Intl.NumberFormat(this.g.locale).format(p.fee / 1000) + ' sats'
return p return p
}) })

View file

@ -41,13 +41,13 @@ window._lnbitsUtils = {
return Quasar.date.formatDate(new Date(isoDateString), window.dateFormat) return Quasar.date.formatDate(new Date(isoDateString), window.dateFormat)
}, },
formatCurrency(value, currency) { formatCurrency(value, currency) {
return new Intl.NumberFormat(window.LOCALE, { return new Intl.NumberFormat(window.i18n.global.locale, {
style: 'currency', style: 'currency',
currency: currency || 'sat' currency: currency || 'sat'
}).format(value) }).format(value)
}, },
formatSat(value) { formatSat(value) {
return new Intl.NumberFormat(window.LOCALE).format(value) return new Intl.NumberFormat(window.i18n.global.locale).format(value)
}, },
formatMsat(value) { formatMsat(value) {
return this.formatSat(value / 1000) return this.formatSat(value / 1000)

View file

@ -283,14 +283,6 @@ window.windowMixin = {
} }
this.applyBackgroundImage() this.applyBackgroundImage()
let locale = this.$q.localStorage.getItem('lnbits.lang')
if (locale) {
window.LOCALE = locale
window.i18n.global.locale = locale
}
this.g.langs = window.langs ?? []
addEventListener('offline', event => { addEventListener('offline', event => {
console.log('offline', event) console.log('offline', event)
this.g.offline = true this.g.offline = true

View file

@ -75,6 +75,7 @@
"js/components/lnbits-header.js", "js/components/lnbits-header.js",
"js/components/lnbits-drawer.js", "js/components/lnbits-drawer.js",
"js/components/lnbits-manage-extension-list.js", "js/components/lnbits-manage-extension-list.js",
"js/components/lnbits-language-dropdown.js",
"js/components/extension-settings.js", "js/components/extension-settings.js",
"js/components/data-fields.js", "js/components/data-fields.js",
"js/components/payment-list.js", "js/components/payment-list.js",

View file

@ -15,7 +15,8 @@ include('components/lnbits-footer.vue') %} {%
include('components/lnbits-header.vue') %} {% include('components/lnbits-header.vue') %} {%
include('components/lnbits-drawer.vue') %} {% include('components/lnbits-drawer.vue') %} {%
include('components/lnbits-home-logos.vue') %} {% include('components/lnbits-home-logos.vue') %} {%
include('components/lnbits-manage-extension-list.vue') %} include('components/lnbits-manage-extension-list.vue') %} {%
include('components/lnbits-language-dropdown.vue') %}
<template id="lnbits-wallet-list"> <template id="lnbits-wallet-list">
<q-list <q-list

View file

@ -70,6 +70,8 @@
<span>OFFLINE</span> <span>OFFLINE</span>
</q-badge> </q-badge>
<lnbits-language-dropdown></lnbits-language-dropdown>
<q-btn-dropdown <q-btn-dropdown
v-if="g.user || isUserAuthorized" v-if="g.user || isUserAuthorized"
flat flat

View file

@ -0,0 +1,19 @@
<template id="lnbits-language-dropdown">
<q-btn-dropdown dense flat rounded size="sm" icon="language" class="q-pl-md">
<q-list v-for="(lang, index) in langs" :key="index">
<q-item
clickable
v-close-popup
:active="activeLanguage(lang.value)"
@click="changeLanguage(lang.value)"
>
<q-item-section>
<q-item-label
v-text="lang.display ?? lang.value.toUpperCase()"
></q-item-label>
<q-tooltip><span v-text="lang.label"></span></q-tooltip>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</template>

View file

@ -366,33 +366,7 @@
<span v-text="$t('language')"></span> <span v-text="$t('language')"></span>
</div> </div>
<div class="col-8"> <div class="col-8">
<q-btn-dropdown <lnbits-language-dropdown />
dense
flat
round
size="sm"
icon="language"
class="q-pl-md"
>
<q-list v-for="(lang, index) in g.langs" :key="index">
<q-item
clickable
v-close-popup
:active="activeLanguage(lang.value)"
@click="changeLanguage(lang.value)"
><q-item-section>
<q-item-label
v-text="
lang.display ?? lang.value.toUpperCase()
"
></q-item-label>
<q-tooltip
><span v-text="lang.label"></span
></q-tooltip>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div> </div>
</div> </div>
<div class="row q-mb-md"> <div class="row q-mb-md">

View file

@ -127,6 +127,7 @@
"js/components/lnbits-header.js", "js/components/lnbits-header.js",
"js/components/lnbits-drawer.js", "js/components/lnbits-drawer.js",
"js/components/lnbits-manage-extension-list.js", "js/components/lnbits-manage-extension-list.js",
"js/components/lnbits-language-dropdown.js",
"js/components/extension-settings.js", "js/components/extension-settings.js",
"js/components/data-fields.js", "js/components/data-fields.js",
"js/components/payment-list.js", "js/components/payment-list.js",