From 8ee6be30ebda4b8ce592c2fa4ec0688ecde420bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 4 Apr 2023 09:39:07 +0200 Subject: [PATCH 1/2] add vue-i18n dependecy --- Makefile | 1 + lnbits/helpers.py | 1 + lnbits/static/vendor/vue-i18n.js | 2312 ++++++++++++++++++++++++++++++ package-lock.json | 6 + package.json | 1 + tools/build.py | 1 + 6 files changed, 2322 insertions(+) create mode 100644 lnbits/static/vendor/vue-i18n.js diff --git a/Makefile b/Makefile index 25add4fca..1f08db53f 100644 --- a/Makefile +++ b/Makefile @@ -89,3 +89,4 @@ updatevendor: cp ./node_modules/quasar/dist/quasar.css ./lnbits/static/vendor/ cp ./node_modules/chart.js/dist/Chart.css ./lnbits/static/vendor/ cp ./node_modules/vue-qrcode-reader/dist/vue-qrcode-reader.css ./lnbits/static/vendor/ + cp ./node_modules/vue-i18n/dist/vue-i18n.js ./lnbits/static/vendor/ diff --git a/lnbits/helpers.py b/lnbits/helpers.py index 184a8f857..6a44ffbad 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -23,6 +23,7 @@ vendored_js = [ "/static/vendor/vue-router.js", "/static/vendor/vue-qrcode-reader.browser.js", "/static/vendor/vue-qrcode.js", + "/static/vendor/vue-i18n.js", "/static/vendor/vuex.js", "/static/vendor/quasar.ie.polyfills.umd.min.js", "/static/vendor/quasar.umd.js", diff --git a/lnbits/static/vendor/vue-i18n.js b/lnbits/static/vendor/vue-i18n.js new file mode 100644 index 000000000..545386059 --- /dev/null +++ b/lnbits/static/vendor/vue-i18n.js @@ -0,0 +1,2312 @@ +/*! + * vue-i18n v8.28.2 + * (c) 2022 kazuya kawaguchi + * Released under the MIT License. + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.VueI18n = factory()); +}(this, (function () { 'use strict'; + + /* */ + + /** + * constants + */ + + var numberFormatKeys = [ + 'compactDisplay', + 'currency', + 'currencyDisplay', + 'currencySign', + 'localeMatcher', + 'notation', + 'numberingSystem', + 'signDisplay', + 'style', + 'unit', + 'unitDisplay', + 'useGrouping', + 'minimumIntegerDigits', + 'minimumFractionDigits', + 'maximumFractionDigits', + 'minimumSignificantDigits', + 'maximumSignificantDigits' + ]; + + var dateTimeFormatKeys = [ + 'dateStyle', + 'timeStyle', + 'calendar', + 'localeMatcher', + "hour12", + "hourCycle", + "timeZone", + "formatMatcher", + 'weekday', + 'era', + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + 'timeZoneName' ]; + + /** + * utilities + */ + + function warn (msg, err) { + if (typeof console !== 'undefined') { + console.warn('[vue-i18n] ' + msg); + /* istanbul ignore if */ + if (err) { + console.warn(err.stack); + } + } + } + + function error (msg, err) { + if (typeof console !== 'undefined') { + console.error('[vue-i18n] ' + msg); + /* istanbul ignore if */ + if (err) { + console.error(err.stack); + } + } + } + + var isArray = Array.isArray; + + function isObject (obj) { + return obj !== null && typeof obj === 'object' + } + + function isBoolean (val) { + return typeof val === 'boolean' + } + + function isString (val) { + return typeof val === 'string' + } + + var toString = Object.prototype.toString; + var OBJECT_STRING = '[object Object]'; + function isPlainObject (obj) { + return toString.call(obj) === OBJECT_STRING + } + + function isNull (val) { + return val === null || val === undefined + } + + function isFunction (val) { + return typeof val === 'function' + } + + function parseArgs () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var locale = null; + var params = null; + if (args.length === 1) { + if (isObject(args[0]) || isArray(args[0])) { + params = args[0]; + } else if (typeof args[0] === 'string') { + locale = args[0]; + } + } else if (args.length === 2) { + if (typeof args[0] === 'string') { + locale = args[0]; + } + /* istanbul ignore if */ + if (isObject(args[1]) || isArray(args[1])) { + params = args[1]; + } + } + + return { locale: locale, params: params } + } + + function looseClone (obj) { + return JSON.parse(JSON.stringify(obj)) + } + + function remove (arr, item) { + if (arr.delete(item)) { + return arr + } + } + + function arrayFrom (arr) { + var ret = []; + arr.forEach(function (a) { return ret.push(a); }); + return ret + } + + function includes (arr, item) { + return !!~arr.indexOf(item) + } + + var hasOwnProperty = Object.prototype.hasOwnProperty; + function hasOwn (obj, key) { + return hasOwnProperty.call(obj, key) + } + + function merge (target) { + var arguments$1 = arguments; + + var output = Object(target); + for (var i = 1; i < arguments.length; i++) { + var source = arguments$1[i]; + if (source !== undefined && source !== null) { + var key = (void 0); + for (key in source) { + if (hasOwn(source, key)) { + if (isObject(source[key])) { + output[key] = merge(output[key], source[key]); + } else { + output[key] = source[key]; + } + } + } + } + } + return output + } + + function looseEqual (a, b) { + if (a === b) { return true } + var isObjectA = isObject(a); + var isObjectB = isObject(b); + if (isObjectA && isObjectB) { + try { + var isArrayA = isArray(a); + var isArrayB = isArray(b); + if (isArrayA && isArrayB) { + return a.length === b.length && a.every(function (e, i) { + return looseEqual(e, b[i]) + }) + } else if (!isArrayA && !isArrayB) { + var keysA = Object.keys(a); + var keysB = Object.keys(b); + return keysA.length === keysB.length && keysA.every(function (key) { + return looseEqual(a[key], b[key]) + }) + } else { + /* istanbul ignore next */ + return false + } + } catch (e) { + /* istanbul ignore next */ + return false + } + } else if (!isObjectA && !isObjectB) { + return String(a) === String(b) + } else { + return false + } + } + + /** + * Sanitizes html special characters from input strings. For mitigating risk of XSS attacks. + * @param rawText The raw input from the user that should be escaped. + */ + function escapeHtml(rawText) { + return rawText + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + } + + /** + * Escapes html tags and special symbols from all provided params which were returned from parseArgs().params. + * This method performs an in-place operation on the params object. + * + * @param {any} params Parameters as provided from `parseArgs().params`. + * May be either an array of strings or a string->any map. + * + * @returns The manipulated `params` object. + */ + function escapeParams(params) { + if(params != null) { + Object.keys(params).forEach(function (key) { + if(typeof(params[key]) == 'string') { + params[key] = escapeHtml(params[key]); + } + }); + } + return params + } + + /* */ + + function extend (Vue) { + if (!Vue.prototype.hasOwnProperty('$i18n')) { + // $FlowFixMe + Object.defineProperty(Vue.prototype, '$i18n', { + get: function get () { return this._i18n } + }); + } + + Vue.prototype.$t = function (key) { + var values = [], len = arguments.length - 1; + while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ]; + + var i18n = this.$i18n; + return i18n._t.apply(i18n, [ key, i18n.locale, i18n._getMessages(), this ].concat( values )) + }; + + Vue.prototype.$tc = function (key, choice) { + var values = [], len = arguments.length - 2; + while ( len-- > 0 ) values[ len ] = arguments[ len + 2 ]; + + var i18n = this.$i18n; + return i18n._tc.apply(i18n, [ key, i18n.locale, i18n._getMessages(), this, choice ].concat( values )) + }; + + Vue.prototype.$te = function (key, locale) { + var i18n = this.$i18n; + return i18n._te(key, i18n.locale, i18n._getMessages(), locale) + }; + + Vue.prototype.$d = function (value) { + var ref; + + var args = [], len = arguments.length - 1; + while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; + return (ref = this.$i18n).d.apply(ref, [ value ].concat( args )) + }; + + Vue.prototype.$n = function (value) { + var ref; + + var args = [], len = arguments.length - 1; + while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; + return (ref = this.$i18n).n.apply(ref, [ value ].concat( args )) + }; + } + + /* */ + + /** + * Mixin + * + * If `bridge` mode, empty mixin is returned, + * else regulary mixin implementation is returned. + */ + function defineMixin (bridge) { + if ( bridge === void 0 ) bridge = false; + + function mounted () { + if (this !== this.$root && this.$options.__INTLIFY_META__ && this.$el) { + this.$el.setAttribute('data-intlify', this.$options.__INTLIFY_META__); + } + } + + return bridge + ? { mounted: mounted } // delegate `vue-i18n-bridge` mixin implementation + : { // regulary + beforeCreate: function beforeCreate () { + var options = this.$options; + options.i18n = options.i18n || ((options.__i18nBridge || options.__i18n) ? {} : null); + + if (options.i18n) { + if (options.i18n instanceof VueI18n) { + // init locale messages via custom blocks + if ((options.__i18nBridge || options.__i18n)) { + try { + var localeMessages = options.i18n && options.i18n.messages ? options.i18n.messages : {}; + var _i18n = options.__i18nBridge || options.__i18n; + _i18n.forEach(function (resource) { + localeMessages = merge(localeMessages, JSON.parse(resource)); + }); + Object.keys(localeMessages).forEach(function (locale) { + options.i18n.mergeLocaleMessage(locale, localeMessages[locale]); + }); + } catch (e) { + { + error("Cannot parse locale messages via custom blocks.", e); + } + } + } + this._i18n = options.i18n; + this._i18nWatcher = this._i18n.watchI18nData(); + } else if (isPlainObject(options.i18n)) { + var rootI18n = this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n + ? this.$root.$i18n + : null; + // component local i18n + if (rootI18n) { + options.i18n.root = this.$root; + options.i18n.formatter = rootI18n.formatter; + options.i18n.fallbackLocale = rootI18n.fallbackLocale; + options.i18n.formatFallbackMessages = rootI18n.formatFallbackMessages; + options.i18n.silentTranslationWarn = rootI18n.silentTranslationWarn; + options.i18n.silentFallbackWarn = rootI18n.silentFallbackWarn; + options.i18n.pluralizationRules = rootI18n.pluralizationRules; + options.i18n.preserveDirectiveContent = rootI18n.preserveDirectiveContent; + } + + // init locale messages via custom blocks + if ((options.__i18nBridge || options.__i18n)) { + try { + var localeMessages$1 = options.i18n && options.i18n.messages ? options.i18n.messages : {}; + var _i18n$1 = options.__i18nBridge || options.__i18n; + _i18n$1.forEach(function (resource) { + localeMessages$1 = merge(localeMessages$1, JSON.parse(resource)); + }); + options.i18n.messages = localeMessages$1; + } catch (e) { + { + warn("Cannot parse locale messages via custom blocks.", e); + } + } + } + + var ref = options.i18n; + var sharedMessages = ref.sharedMessages; + if (sharedMessages && isPlainObject(sharedMessages)) { + options.i18n.messages = merge(options.i18n.messages, sharedMessages); + } + + this._i18n = new VueI18n(options.i18n); + this._i18nWatcher = this._i18n.watchI18nData(); + + if (options.i18n.sync === undefined || !!options.i18n.sync) { + this._localeWatcher = this.$i18n.watchLocale(); + } + + if (rootI18n) { + rootI18n.onComponentInstanceCreated(this._i18n); + } + } else { + { + warn("Cannot be interpreted 'i18n' option."); + } + } + } else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) { + // root i18n + this._i18n = this.$root.$i18n; + } else if (options.parent && options.parent.$i18n && options.parent.$i18n instanceof VueI18n) { + // parent i18n + this._i18n = options.parent.$i18n; + } + }, + + beforeMount: function beforeMount () { + var options = this.$options; + options.i18n = options.i18n || ((options.__i18nBridge || options.__i18n) ? {} : null); + + if (options.i18n) { + if (options.i18n instanceof VueI18n) { + // init locale messages via custom blocks + this._i18n.subscribeDataChanging(this); + this._subscribing = true; + } else if (isPlainObject(options.i18n)) { + this._i18n.subscribeDataChanging(this); + this._subscribing = true; + } else { + { + warn("Cannot be interpreted 'i18n' option."); + } + } + } else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) { + this._i18n.subscribeDataChanging(this); + this._subscribing = true; + } else if (options.parent && options.parent.$i18n && options.parent.$i18n instanceof VueI18n) { + this._i18n.subscribeDataChanging(this); + this._subscribing = true; + } + }, + + mounted: mounted, + + beforeDestroy: function beforeDestroy () { + if (!this._i18n) { return } + + var self = this; + this.$nextTick(function () { + if (self._subscribing) { + self._i18n.unsubscribeDataChanging(self); + delete self._subscribing; + } + + if (self._i18nWatcher) { + self._i18nWatcher(); + self._i18n.destroyVM(); + delete self._i18nWatcher; + } + + if (self._localeWatcher) { + self._localeWatcher(); + delete self._localeWatcher; + } + }); + } + } + } + + /* */ + + var interpolationComponent = { + name: 'i18n', + functional: true, + props: { + tag: { + type: [String, Boolean, Object], + default: 'span' + }, + path: { + type: String, + required: true + }, + locale: { + type: String + }, + places: { + type: [Array, Object] + } + }, + render: function render (h, ref) { + var data = ref.data; + var parent = ref.parent; + var props = ref.props; + var slots = ref.slots; + + var $i18n = parent.$i18n; + if (!$i18n) { + { + warn('Cannot find VueI18n instance!'); + } + return + } + + var path = props.path; + var locale = props.locale; + var places = props.places; + var params = slots(); + var children = $i18n.i( + path, + locale, + onlyHasDefaultPlace(params) || places + ? useLegacyPlaces(params.default, places) + : params + ); + + var tag = (!!props.tag && props.tag !== true) || props.tag === false ? props.tag : 'span'; + return tag ? h(tag, data, children) : children + } + }; + + function onlyHasDefaultPlace (params) { + var prop; + for (prop in params) { + if (prop !== 'default') { return false } + } + return Boolean(prop) + } + + function useLegacyPlaces (children, places) { + var params = places ? createParamsFromPlaces(places) : {}; + + if (!children) { return params } + + // Filter empty text nodes + children = children.filter(function (child) { + return child.tag || child.text.trim() !== '' + }); + + var everyPlace = children.every(vnodeHasPlaceAttribute); + if (everyPlace) { + warn('`place` attribute is deprecated in next major version. Please switch to Vue slots.'); + } + + return children.reduce( + everyPlace ? assignChildPlace : assignChildIndex, + params + ) + } + + function createParamsFromPlaces (places) { + { + warn('`places` prop is deprecated in next major version. Please switch to Vue slots.'); + } + + return Array.isArray(places) + ? places.reduce(assignChildIndex, {}) + : Object.assign({}, places) + } + + function assignChildPlace (params, child) { + if (child.data && child.data.attrs && child.data.attrs.place) { + params[child.data.attrs.place] = child; + } + return params + } + + function assignChildIndex (params, child, index) { + params[index] = child; + return params + } + + function vnodeHasPlaceAttribute (vnode) { + return Boolean(vnode.data && vnode.data.attrs && vnode.data.attrs.place) + } + + /* */ + + var numberComponent = { + name: 'i18n-n', + functional: true, + props: { + tag: { + type: [String, Boolean, Object], + default: 'span' + }, + value: { + type: Number, + required: true + }, + format: { + type: [String, Object] + }, + locale: { + type: String + } + }, + render: function render (h, ref) { + var props = ref.props; + var parent = ref.parent; + var data = ref.data; + + var i18n = parent.$i18n; + + if (!i18n) { + { + warn('Cannot find VueI18n instance!'); + } + return null + } + + var key = null; + var options = null; + + if (isString(props.format)) { + key = props.format; + } else if (isObject(props.format)) { + if (props.format.key) { + key = props.format.key; + } + + // Filter out number format options only + options = Object.keys(props.format).reduce(function (acc, prop) { + var obj; + + if (includes(numberFormatKeys, prop)) { + return Object.assign({}, acc, ( obj = {}, obj[prop] = props.format[prop], obj )) + } + return acc + }, null); + } + + var locale = props.locale || i18n.locale; + var parts = i18n._ntp(props.value, locale, key, options); + + var values = parts.map(function (part, index) { + var obj; + + var slot = data.scopedSlots && data.scopedSlots[part.type]; + return slot ? slot(( obj = {}, obj[part.type] = part.value, obj.index = index, obj.parts = parts, obj )) : part.value + }); + + var tag = (!!props.tag && props.tag !== true) || props.tag === false ? props.tag : 'span'; + return tag + ? h(tag, { + attrs: data.attrs, + 'class': data['class'], + staticClass: data.staticClass + }, values) + : values + } + }; + + /* */ + + function bind (el, binding, vnode) { + if (!assert(el, vnode)) { return } + + t(el, binding, vnode); + } + + function update (el, binding, vnode, oldVNode) { + if (!assert(el, vnode)) { return } + + var i18n = vnode.context.$i18n; + if (localeEqual(el, vnode) && + (looseEqual(binding.value, binding.oldValue) && + looseEqual(el._localeMessage, i18n.getLocaleMessage(i18n.locale)))) { return } + + t(el, binding, vnode); + } + + function unbind (el, binding, vnode, oldVNode) { + var vm = vnode.context; + if (!vm) { + warn('Vue instance does not exists in VNode context'); + return + } + + var i18n = vnode.context.$i18n || {}; + if (!binding.modifiers.preserve && !i18n.preserveDirectiveContent) { + el.textContent = ''; + } + el._vt = undefined; + delete el['_vt']; + el._locale = undefined; + delete el['_locale']; + el._localeMessage = undefined; + delete el['_localeMessage']; + } + + function assert (el, vnode) { + var vm = vnode.context; + if (!vm) { + warn('Vue instance does not exists in VNode context'); + return false + } + + if (!vm.$i18n) { + warn('VueI18n instance does not exists in Vue instance'); + return false + } + + return true + } + + function localeEqual (el, vnode) { + var vm = vnode.context; + return el._locale === vm.$i18n.locale + } + + function t (el, binding, vnode) { + var ref$1, ref$2; + + var value = binding.value; + + var ref = parseValue(value); + var path = ref.path; + var locale = ref.locale; + var args = ref.args; + var choice = ref.choice; + if (!path && !locale && !args) { + warn('value type not supported'); + return + } + + if (!path) { + warn('`path` is required in v-t directive'); + return + } + + var vm = vnode.context; + if (choice != null) { + el._vt = el.textContent = (ref$1 = vm.$i18n).tc.apply(ref$1, [ path, choice ].concat( makeParams(locale, args) )); + } else { + el._vt = el.textContent = (ref$2 = vm.$i18n).t.apply(ref$2, [ path ].concat( makeParams(locale, args) )); + } + el._locale = vm.$i18n.locale; + el._localeMessage = vm.$i18n.getLocaleMessage(vm.$i18n.locale); + } + + function parseValue (value) { + var path; + var locale; + var args; + var choice; + + if (isString(value)) { + path = value; + } else if (isPlainObject(value)) { + path = value.path; + locale = value.locale; + args = value.args; + choice = value.choice; + } + + return { path: path, locale: locale, args: args, choice: choice } + } + + function makeParams (locale, args) { + var params = []; + + locale && params.push(locale); + if (args && (Array.isArray(args) || isPlainObject(args))) { + params.push(args); + } + + return params + } + + var Vue; + + function install (_Vue, options) { + if ( options === void 0 ) options = { bridge: false }; + + /* istanbul ignore if */ + if (install.installed && _Vue === Vue) { + warn('already installed.'); + return + } + install.installed = true; + + Vue = _Vue; + + var version = (Vue.version && Number(Vue.version.split('.')[0])) || -1; + /* istanbul ignore if */ + if (version < 2) { + warn(("vue-i18n (" + (install.version) + ") need to use Vue 2.0 or later (Vue: " + (Vue.version) + ").")); + return + } + + extend(Vue); + Vue.mixin(defineMixin(options.bridge)); + Vue.directive('t', { bind: bind, update: update, unbind: unbind }); + Vue.component(interpolationComponent.name, interpolationComponent); + Vue.component(numberComponent.name, numberComponent); + + // use simple mergeStrategies to prevent i18n instance lose '__proto__' + var strats = Vue.config.optionMergeStrategies; + strats.i18n = function (parentVal, childVal) { + return childVal === undefined + ? parentVal + : childVal + }; + } + + /* */ + + var BaseFormatter = function BaseFormatter () { + this._caches = Object.create(null); + }; + + BaseFormatter.prototype.interpolate = function interpolate (message, values) { + if (!values) { + return [message] + } + var tokens = this._caches[message]; + if (!tokens) { + tokens = parse(message); + this._caches[message] = tokens; + } + return compile(tokens, values) + }; + + + + var RE_TOKEN_LIST_VALUE = /^(?:\d)+/; + var RE_TOKEN_NAMED_VALUE = /^(?:\w)+/; + + function parse (format) { + var tokens = []; + var position = 0; + + var text = ''; + while (position < format.length) { + var char = format[position++]; + if (char === '{') { + if (text) { + tokens.push({ type: 'text', value: text }); + } + + text = ''; + var sub = ''; + char = format[position++]; + while (char !== undefined && char !== '}') { + sub += char; + char = format[position++]; + } + var isClosed = char === '}'; + + var type = RE_TOKEN_LIST_VALUE.test(sub) + ? 'list' + : isClosed && RE_TOKEN_NAMED_VALUE.test(sub) + ? 'named' + : 'unknown'; + tokens.push({ value: sub, type: type }); + } else if (char === '%') { + // when found rails i18n syntax, skip text capture + if (format[(position)] !== '{') { + text += char; + } + } else { + text += char; + } + } + + text && tokens.push({ type: 'text', value: text }); + + return tokens + } + + function compile (tokens, values) { + var compiled = []; + var index = 0; + + var mode = Array.isArray(values) + ? 'list' + : isObject(values) + ? 'named' + : 'unknown'; + if (mode === 'unknown') { return compiled } + + while (index < tokens.length) { + var token = tokens[index]; + switch (token.type) { + case 'text': + compiled.push(token.value); + break + case 'list': + compiled.push(values[parseInt(token.value, 10)]); + break + case 'named': + if (mode === 'named') { + compiled.push((values)[token.value]); + } else { + { + warn(("Type of token '" + (token.type) + "' and format of value '" + mode + "' don't match!")); + } + } + break + case 'unknown': + { + warn("Detect 'unknown' type of token!"); + } + break + } + index++; + } + + return compiled + } + + /* */ + + /** + * Path parser + * - Inspired: + * Vue.js Path parser + */ + + // actions + var APPEND = 0; + var PUSH = 1; + var INC_SUB_PATH_DEPTH = 2; + var PUSH_SUB_PATH = 3; + + // states + var BEFORE_PATH = 0; + var IN_PATH = 1; + var BEFORE_IDENT = 2; + var IN_IDENT = 3; + var IN_SUB_PATH = 4; + var IN_SINGLE_QUOTE = 5; + var IN_DOUBLE_QUOTE = 6; + var AFTER_PATH = 7; + var ERROR = 8; + + var pathStateMachine = []; + + pathStateMachine[BEFORE_PATH] = { + 'ws': [BEFORE_PATH], + 'ident': [IN_IDENT, APPEND], + '[': [IN_SUB_PATH], + 'eof': [AFTER_PATH] + }; + + pathStateMachine[IN_PATH] = { + 'ws': [IN_PATH], + '.': [BEFORE_IDENT], + '[': [IN_SUB_PATH], + 'eof': [AFTER_PATH] + }; + + pathStateMachine[BEFORE_IDENT] = { + 'ws': [BEFORE_IDENT], + 'ident': [IN_IDENT, APPEND], + '0': [IN_IDENT, APPEND], + 'number': [IN_IDENT, APPEND] + }; + + pathStateMachine[IN_IDENT] = { + 'ident': [IN_IDENT, APPEND], + '0': [IN_IDENT, APPEND], + 'number': [IN_IDENT, APPEND], + 'ws': [IN_PATH, PUSH], + '.': [BEFORE_IDENT, PUSH], + '[': [IN_SUB_PATH, PUSH], + 'eof': [AFTER_PATH, PUSH] + }; + + pathStateMachine[IN_SUB_PATH] = { + "'": [IN_SINGLE_QUOTE, APPEND], + '"': [IN_DOUBLE_QUOTE, APPEND], + '[': [IN_SUB_PATH, INC_SUB_PATH_DEPTH], + ']': [IN_PATH, PUSH_SUB_PATH], + 'eof': ERROR, + 'else': [IN_SUB_PATH, APPEND] + }; + + pathStateMachine[IN_SINGLE_QUOTE] = { + "'": [IN_SUB_PATH, APPEND], + 'eof': ERROR, + 'else': [IN_SINGLE_QUOTE, APPEND] + }; + + pathStateMachine[IN_DOUBLE_QUOTE] = { + '"': [IN_SUB_PATH, APPEND], + 'eof': ERROR, + 'else': [IN_DOUBLE_QUOTE, APPEND] + }; + + /** + * Check if an expression is a literal value. + */ + + var literalValueRE = /^\s?(?:true|false|-?[\d.]+|'[^']*'|"[^"]*")\s?$/; + function isLiteral (exp) { + return literalValueRE.test(exp) + } + + /** + * Strip quotes from a string + */ + + function stripQuotes (str) { + var a = str.charCodeAt(0); + var b = str.charCodeAt(str.length - 1); + return a === b && (a === 0x22 || a === 0x27) + ? str.slice(1, -1) + : str + } + + /** + * Determine the type of a character in a keypath. + */ + + function getPathCharType (ch) { + if (ch === undefined || ch === null) { return 'eof' } + + var code = ch.charCodeAt(0); + + switch (code) { + case 0x5B: // [ + case 0x5D: // ] + case 0x2E: // . + case 0x22: // " + case 0x27: // ' + return ch + + case 0x5F: // _ + case 0x24: // $ + case 0x2D: // - + return 'ident' + + case 0x09: // Tab + case 0x0A: // Newline + case 0x0D: // Return + case 0xA0: // No-break space + case 0xFEFF: // Byte Order Mark + case 0x2028: // Line Separator + case 0x2029: // Paragraph Separator + return 'ws' + } + + return 'ident' + } + + /** + * Format a subPath, return its plain form if it is + * a literal string or number. Otherwise prepend the + * dynamic indicator (*). + */ + + function formatSubPath (path) { + var trimmed = path.trim(); + // invalid leading 0 + if (path.charAt(0) === '0' && isNaN(path)) { return false } + + return isLiteral(trimmed) ? stripQuotes(trimmed) : '*' + trimmed + } + + /** + * Parse a string path into an array of segments + */ + + function parse$1 (path) { + var keys = []; + var index = -1; + var mode = BEFORE_PATH; + var subPathDepth = 0; + var c; + var key; + var newChar; + var type; + var transition; + var action; + var typeMap; + var actions = []; + + actions[PUSH] = function () { + if (key !== undefined) { + keys.push(key); + key = undefined; + } + }; + + actions[APPEND] = function () { + if (key === undefined) { + key = newChar; + } else { + key += newChar; + } + }; + + actions[INC_SUB_PATH_DEPTH] = function () { + actions[APPEND](); + subPathDepth++; + }; + + actions[PUSH_SUB_PATH] = function () { + if (subPathDepth > 0) { + subPathDepth--; + mode = IN_SUB_PATH; + actions[APPEND](); + } else { + subPathDepth = 0; + if (key === undefined) { return false } + key = formatSubPath(key); + if (key === false) { + return false + } else { + actions[PUSH](); + } + } + }; + + function maybeUnescapeQuote () { + var nextChar = path[index + 1]; + if ((mode === IN_SINGLE_QUOTE && nextChar === "'") || + (mode === IN_DOUBLE_QUOTE && nextChar === '"')) { + index++; + newChar = '\\' + nextChar; + actions[APPEND](); + return true + } + } + + while (mode !== null) { + index++; + c = path[index]; + + if (c === '\\' && maybeUnescapeQuote()) { + continue + } + + type = getPathCharType(c); + typeMap = pathStateMachine[mode]; + transition = typeMap[type] || typeMap['else'] || ERROR; + + if (transition === ERROR) { + return // parse error + } + + mode = transition[0]; + action = actions[transition[1]]; + if (action) { + newChar = transition[2]; + newChar = newChar === undefined + ? c + : newChar; + if (action() === false) { + return + } + } + + if (mode === AFTER_PATH) { + return keys + } + } + } + + + + + + var I18nPath = function I18nPath () { + this._cache = Object.create(null); + }; + + /** + * External parse that check for a cache hit first + */ + I18nPath.prototype.parsePath = function parsePath (path) { + var hit = this._cache[path]; + if (!hit) { + hit = parse$1(path); + if (hit) { + this._cache[path] = hit; + } + } + return hit || [] + }; + + /** + * Get path value from path string + */ + I18nPath.prototype.getPathValue = function getPathValue (obj, path) { + if (!isObject(obj)) { return null } + + var paths = this.parsePath(path); + if (paths.length === 0) { + return null + } else { + var length = paths.length; + var last = obj; + var i = 0; + while (i < length) { + var value = last[paths[i]]; + if (value === undefined || value === null) { + return null + } + last = value; + i++; + } + + return last + } + }; + + /* */ + + + + var htmlTagMatcher = /<\/?[\w\s="/.':;#-\/]+>/; + var linkKeyMatcher = /(?:@(?:\.[a-zA-Z]+)?:(?:[\w\-_|./]+|\([\w\-_:|./]+\)))/g; + var linkKeyPrefixMatcher = /^@(?:\.([a-zA-Z]+))?:/; + var bracketsMatcher = /[()]/g; + var defaultModifiers = { + 'upper': function (str) { return str.toLocaleUpperCase(); }, + 'lower': function (str) { return str.toLocaleLowerCase(); }, + 'capitalize': function (str) { return ("" + (str.charAt(0).toLocaleUpperCase()) + (str.substr(1))); } + }; + + var defaultFormatter = new BaseFormatter(); + + var VueI18n = function VueI18n (options) { + var this$1 = this; + if ( options === void 0 ) options = {}; + + // Auto install if it is not done yet and `window` has `Vue`. + // To allow users to avoid auto-installation in some cases, + // this code should be placed here. See #290 + /* istanbul ignore if */ + if (!Vue && typeof window !== 'undefined' && window.Vue) { + install(window.Vue); + } + + var locale = options.locale || 'en-US'; + var fallbackLocale = options.fallbackLocale === false + ? false + : options.fallbackLocale || 'en-US'; + var messages = options.messages || {}; + var dateTimeFormats = options.dateTimeFormats || options.datetimeFormats || {}; + var numberFormats = options.numberFormats || {}; + + this._vm = null; + this._formatter = options.formatter || defaultFormatter; + this._modifiers = options.modifiers || {}; + this._missing = options.missing || null; + this._root = options.root || null; + this._sync = options.sync === undefined ? true : !!options.sync; + this._fallbackRoot = options.fallbackRoot === undefined + ? true + : !!options.fallbackRoot; + this._fallbackRootWithEmptyString = options.fallbackRootWithEmptyString === undefined + ? true + : !!options.fallbackRootWithEmptyString; + this._formatFallbackMessages = options.formatFallbackMessages === undefined + ? false + : !!options.formatFallbackMessages; + this._silentTranslationWarn = options.silentTranslationWarn === undefined + ? false + : options.silentTranslationWarn; + this._silentFallbackWarn = options.silentFallbackWarn === undefined + ? false + : !!options.silentFallbackWarn; + this._dateTimeFormatters = {}; + this._numberFormatters = {}; + this._path = new I18nPath(); + this._dataListeners = new Set(); + this._componentInstanceCreatedListener = options.componentInstanceCreatedListener || null; + this._preserveDirectiveContent = options.preserveDirectiveContent === undefined + ? false + : !!options.preserveDirectiveContent; + this.pluralizationRules = options.pluralizationRules || {}; + this._warnHtmlInMessage = options.warnHtmlInMessage || 'off'; + this._postTranslation = options.postTranslation || null; + this._escapeParameterHtml = options.escapeParameterHtml || false; + + if ('__VUE_I18N_BRIDGE__' in options) { + this.__VUE_I18N_BRIDGE__ = options.__VUE_I18N_BRIDGE__; + } + + /** + * @param choice {number} a choice index given by the input to $tc: `$tc('path.to.rule', choiceIndex)` + * @param choicesLength {number} an overall amount of available choices + * @returns a final choice index + */ + this.getChoiceIndex = function (choice, choicesLength) { + var thisPrototype = Object.getPrototypeOf(this$1); + if (thisPrototype && thisPrototype.getChoiceIndex) { + var prototypeGetChoiceIndex = (thisPrototype.getChoiceIndex); + return (prototypeGetChoiceIndex).call(this$1, choice, choicesLength) + } + + // Default (old) getChoiceIndex implementation - english-compatible + var defaultImpl = function (_choice, _choicesLength) { + _choice = Math.abs(_choice); + + if (_choicesLength === 2) { + return _choice + ? _choice > 1 + ? 1 + : 0 + : 1 + } + + return _choice ? Math.min(_choice, 2) : 0 + }; + + if (this$1.locale in this$1.pluralizationRules) { + return this$1.pluralizationRules[this$1.locale].apply(this$1, [choice, choicesLength]) + } else { + return defaultImpl(choice, choicesLength) + } + }; + + + this._exist = function (message, key) { + if (!message || !key) { return false } + if (!isNull(this$1._path.getPathValue(message, key))) { return true } + // fallback for flat key + if (message[key]) { return true } + return false + }; + + if (this._warnHtmlInMessage === 'warn' || this._warnHtmlInMessage === 'error') { + Object.keys(messages).forEach(function (locale) { + this$1._checkLocaleMessage(locale, this$1._warnHtmlInMessage, messages[locale]); + }); + } + + this._initVM({ + locale: locale, + fallbackLocale: fallbackLocale, + messages: messages, + dateTimeFormats: dateTimeFormats, + numberFormats: numberFormats + }); + }; + + var prototypeAccessors = { vm: { configurable: true },messages: { configurable: true },dateTimeFormats: { configurable: true },numberFormats: { configurable: true },availableLocales: { configurable: true },locale: { configurable: true },fallbackLocale: { configurable: true },formatFallbackMessages: { configurable: true },missing: { configurable: true },formatter: { configurable: true },silentTranslationWarn: { configurable: true },silentFallbackWarn: { configurable: true },preserveDirectiveContent: { configurable: true },warnHtmlInMessage: { configurable: true },postTranslation: { configurable: true },sync: { configurable: true } }; + + VueI18n.prototype._checkLocaleMessage = function _checkLocaleMessage (locale, level, message) { + var paths = []; + + var fn = function (level, locale, message, paths) { + if (isPlainObject(message)) { + Object.keys(message).forEach(function (key) { + var val = message[key]; + if (isPlainObject(val)) { + paths.push(key); + paths.push('.'); + fn(level, locale, val, paths); + paths.pop(); + paths.pop(); + } else { + paths.push(key); + fn(level, locale, val, paths); + paths.pop(); + } + }); + } else if (isArray(message)) { + message.forEach(function (item, index) { + if (isPlainObject(item)) { + paths.push(("[" + index + "]")); + paths.push('.'); + fn(level, locale, item, paths); + paths.pop(); + paths.pop(); + } else { + paths.push(("[" + index + "]")); + fn(level, locale, item, paths); + paths.pop(); + } + }); + } else if (isString(message)) { + var ret = htmlTagMatcher.test(message); + if (ret) { + var msg = "Detected HTML in message '" + message + "' of keypath '" + (paths.join('')) + "' at '" + locale + "'. Consider component interpolation with '' to avoid XSS. See https://bit.ly/2ZqJzkp"; + if (level === 'warn') { + warn(msg); + } else if (level === 'error') { + error(msg); + } + } + } + }; + + fn(level, locale, message, paths); + }; + + VueI18n.prototype._initVM = function _initVM (data) { + var silent = Vue.config.silent; + Vue.config.silent = true; + this._vm = new Vue({ data: data, __VUE18N__INSTANCE__: true }); + Vue.config.silent = silent; + }; + + VueI18n.prototype.destroyVM = function destroyVM () { + this._vm.$destroy(); + }; + + VueI18n.prototype.subscribeDataChanging = function subscribeDataChanging (vm) { + this._dataListeners.add(vm); + }; + + VueI18n.prototype.unsubscribeDataChanging = function unsubscribeDataChanging (vm) { + remove(this._dataListeners, vm); + }; + + VueI18n.prototype.watchI18nData = function watchI18nData () { + var this$1 = this; + return this._vm.$watch('$data', function () { + var listeners = arrayFrom(this$1._dataListeners); + var i = listeners.length; + while(i--) { + Vue.nextTick(function () { + listeners[i] && listeners[i].$forceUpdate(); + }); + } + }, { deep: true }) + }; + + VueI18n.prototype.watchLocale = function watchLocale (composer) { + if (!composer) { + /* istanbul ignore if */ + if (!this._sync || !this._root) { return null } + var target = this._vm; + return this._root.$i18n.vm.$watch('locale', function (val) { + target.$set(target, 'locale', val); + target.$forceUpdate(); + }, { immediate: true }) + } else { + // deal with vue-i18n-bridge + if (!this.__VUE_I18N_BRIDGE__) { return null } + var self = this; + var target$1 = this._vm; + return this.vm.$watch('locale', function (val) { + target$1.$set(target$1, 'locale', val); + if (self.__VUE_I18N_BRIDGE__ && composer) { + composer.locale.value = val; + } + target$1.$forceUpdate(); + }, { immediate: true }) + } + }; + + VueI18n.prototype.onComponentInstanceCreated = function onComponentInstanceCreated (newI18n) { + if (this._componentInstanceCreatedListener) { + this._componentInstanceCreatedListener(newI18n, this); + } + }; + + prototypeAccessors.vm.get = function () { return this._vm }; + + prototypeAccessors.messages.get = function () { return looseClone(this._getMessages()) }; + prototypeAccessors.dateTimeFormats.get = function () { return looseClone(this._getDateTimeFormats()) }; + prototypeAccessors.numberFormats.get = function () { return looseClone(this._getNumberFormats()) }; + prototypeAccessors.availableLocales.get = function () { return Object.keys(this.messages).sort() }; + + prototypeAccessors.locale.get = function () { return this._vm.locale }; + prototypeAccessors.locale.set = function (locale) { + this._vm.$set(this._vm, 'locale', locale); + }; + + prototypeAccessors.fallbackLocale.get = function () { return this._vm.fallbackLocale }; + prototypeAccessors.fallbackLocale.set = function (locale) { + this._localeChainCache = {}; + this._vm.$set(this._vm, 'fallbackLocale', locale); + }; + + prototypeAccessors.formatFallbackMessages.get = function () { return this._formatFallbackMessages }; + prototypeAccessors.formatFallbackMessages.set = function (fallback) { this._formatFallbackMessages = fallback; }; + + prototypeAccessors.missing.get = function () { return this._missing }; + prototypeAccessors.missing.set = function (handler) { this._missing = handler; }; + + prototypeAccessors.formatter.get = function () { return this._formatter }; + prototypeAccessors.formatter.set = function (formatter) { this._formatter = formatter; }; + + prototypeAccessors.silentTranslationWarn.get = function () { return this._silentTranslationWarn }; + prototypeAccessors.silentTranslationWarn.set = function (silent) { this._silentTranslationWarn = silent; }; + + prototypeAccessors.silentFallbackWarn.get = function () { return this._silentFallbackWarn }; + prototypeAccessors.silentFallbackWarn.set = function (silent) { this._silentFallbackWarn = silent; }; + + prototypeAccessors.preserveDirectiveContent.get = function () { return this._preserveDirectiveContent }; + prototypeAccessors.preserveDirectiveContent.set = function (preserve) { this._preserveDirectiveContent = preserve; }; + + prototypeAccessors.warnHtmlInMessage.get = function () { return this._warnHtmlInMessage }; + prototypeAccessors.warnHtmlInMessage.set = function (level) { + var this$1 = this; + + var orgLevel = this._warnHtmlInMessage; + this._warnHtmlInMessage = level; + if (orgLevel !== level && (level === 'warn' || level === 'error')) { + var messages = this._getMessages(); + Object.keys(messages).forEach(function (locale) { + this$1._checkLocaleMessage(locale, this$1._warnHtmlInMessage, messages[locale]); + }); + } + }; + + prototypeAccessors.postTranslation.get = function () { return this._postTranslation }; + prototypeAccessors.postTranslation.set = function (handler) { this._postTranslation = handler; }; + + prototypeAccessors.sync.get = function () { return this._sync }; + prototypeAccessors.sync.set = function (val) { this._sync = val; }; + + VueI18n.prototype._getMessages = function _getMessages () { return this._vm.messages }; + VueI18n.prototype._getDateTimeFormats = function _getDateTimeFormats () { return this._vm.dateTimeFormats }; + VueI18n.prototype._getNumberFormats = function _getNumberFormats () { return this._vm.numberFormats }; + + VueI18n.prototype._warnDefault = function _warnDefault (locale, key, result, vm, values, interpolateMode) { + if (!isNull(result)) { return result } + if (this._missing) { + var missingRet = this._missing.apply(null, [locale, key, vm, values]); + if (isString(missingRet)) { + return missingRet + } + } else { + if (!this._isSilentTranslationWarn(key)) { + warn( + "Cannot translate the value of keypath '" + key + "'. " + + 'Use the value of keypath as default.' + ); + } + } + + if (this._formatFallbackMessages) { + var parsedArgs = parseArgs.apply(void 0, values); + return this._render(key, interpolateMode, parsedArgs.params, key) + } else { + return key + } + }; + + VueI18n.prototype._isFallbackRoot = function _isFallbackRoot (val) { + return (this._fallbackRootWithEmptyString? !val : isNull(val)) && !isNull(this._root) && this._fallbackRoot + }; + + VueI18n.prototype._isSilentFallbackWarn = function _isSilentFallbackWarn (key) { + return this._silentFallbackWarn instanceof RegExp + ? this._silentFallbackWarn.test(key) + : this._silentFallbackWarn + }; + + VueI18n.prototype._isSilentFallback = function _isSilentFallback (locale, key) { + return this._isSilentFallbackWarn(key) && (this._isFallbackRoot() || locale !== this.fallbackLocale) + }; + + VueI18n.prototype._isSilentTranslationWarn = function _isSilentTranslationWarn (key) { + return this._silentTranslationWarn instanceof RegExp + ? this._silentTranslationWarn.test(key) + : this._silentTranslationWarn + }; + + VueI18n.prototype._interpolate = function _interpolate ( + locale, + message, + key, + host, + interpolateMode, + values, + visitedLinkStack + ) { + if (!message) { return null } + + var pathRet = this._path.getPathValue(message, key); + if (isArray(pathRet) || isPlainObject(pathRet)) { return pathRet } + + var ret; + if (isNull(pathRet)) { + /* istanbul ignore else */ + if (isPlainObject(message)) { + ret = message[key]; + if (!(isString(ret) || isFunction(ret))) { + if (!this._isSilentTranslationWarn(key) && !this._isSilentFallback(locale, key)) { + warn(("Value of key '" + key + "' is not a string or function !")); + } + return null + } + } else { + return null + } + } else { + /* istanbul ignore else */ + if (isString(pathRet) || isFunction(pathRet)) { + ret = pathRet; + } else { + if (!this._isSilentTranslationWarn(key) && !this._isSilentFallback(locale, key)) { + warn(("Value of key '" + key + "' is not a string or function!")); + } + return null + } + } + + // Check for the existence of links within the translated string + if (isString(ret) && (ret.indexOf('@:') >= 0 || ret.indexOf('@.') >= 0)) { + ret = this._link(locale, message, ret, host, 'raw', values, visitedLinkStack); + } + + return this._render(ret, interpolateMode, values, key) + }; + + VueI18n.prototype._link = function _link ( + locale, + message, + str, + host, + interpolateMode, + values, + visitedLinkStack + ) { + var ret = str; + + // Match all the links within the local + // We are going to replace each of + // them with its translation + var matches = ret.match(linkKeyMatcher); + + // eslint-disable-next-line no-autofix/prefer-const + for (var idx in matches) { + // ie compatible: filter custom array + // prototype method + if (!matches.hasOwnProperty(idx)) { + continue + } + var link = matches[idx]; + var linkKeyPrefixMatches = link.match(linkKeyPrefixMatcher); + var linkPrefix = linkKeyPrefixMatches[0]; + var formatterName = linkKeyPrefixMatches[1]; + + // Remove the leading @:, @.case: and the brackets + var linkPlaceholder = link.replace(linkPrefix, '').replace(bracketsMatcher, ''); + + if (includes(visitedLinkStack, linkPlaceholder)) { + { + warn(("Circular reference found. \"" + link + "\" is already visited in the chain of " + (visitedLinkStack.reverse().join(' <- ')))); + } + return ret + } + visitedLinkStack.push(linkPlaceholder); + + // Translate the link + var translated = this._interpolate( + locale, message, linkPlaceholder, host, + interpolateMode === 'raw' ? 'string' : interpolateMode, + interpolateMode === 'raw' ? undefined : values, + visitedLinkStack + ); + + if (this._isFallbackRoot(translated)) { + if (!this._isSilentTranslationWarn(linkPlaceholder)) { + warn(("Fall back to translate the link placeholder '" + linkPlaceholder + "' with root locale.")); + } + /* istanbul ignore if */ + if (!this._root) { throw Error('unexpected error') } + var root = this._root.$i18n; + translated = root._translate( + root._getMessages(), root.locale, root.fallbackLocale, + linkPlaceholder, host, interpolateMode, values + ); + } + translated = this._warnDefault( + locale, linkPlaceholder, translated, host, + isArray(values) ? values : [values], + interpolateMode + ); + + if (this._modifiers.hasOwnProperty(formatterName)) { + translated = this._modifiers[formatterName](translated); + } else if (defaultModifiers.hasOwnProperty(formatterName)) { + translated = defaultModifiers[formatterName](translated); + } + + visitedLinkStack.pop(); + + // Replace the link with the translated + ret = !translated ? ret : ret.replace(link, translated); + } + + return ret + }; + + VueI18n.prototype._createMessageContext = function _createMessageContext (values, formatter, path, interpolateMode) { + var this$1 = this; + + var _list = isArray(values) ? values : []; + var _named = isObject(values) ? values : {}; + var list = function (index) { return _list[index]; }; + var named = function (key) { return _named[key]; }; + var messages = this._getMessages(); + var locale = this.locale; + + return { + list: list, + named: named, + values: values, + formatter: formatter, + path: path, + messages: messages, + locale: locale, + linked: function (linkedKey) { return this$1._interpolate(locale, messages[locale] || {}, linkedKey, null, interpolateMode, undefined, [linkedKey]); } + } + }; + + VueI18n.prototype._render = function _render (message, interpolateMode, values, path) { + if (isFunction(message)) { + return message( + this._createMessageContext(values, this._formatter || defaultFormatter, path, interpolateMode) + ) + } + + var ret = this._formatter.interpolate(message, values, path); + + // If the custom formatter refuses to work - apply the default one + if (!ret) { + ret = defaultFormatter.interpolate(message, values, path); + } + + // if interpolateMode is **not** 'string' ('row'), + // return the compiled data (e.g. ['foo', VNode, 'bar']) with formatter + return interpolateMode === 'string' && !isString(ret) ? ret.join('') : ret + }; + + VueI18n.prototype._appendItemToChain = function _appendItemToChain (chain, item, blocks) { + var follow = false; + if (!includes(chain, item)) { + follow = true; + if (item) { + follow = item[item.length - 1] !== '!'; + item = item.replace(/!/g, ''); + chain.push(item); + if (blocks && blocks[item]) { + follow = blocks[item]; + } + } + } + return follow + }; + + VueI18n.prototype._appendLocaleToChain = function _appendLocaleToChain (chain, locale, blocks) { + var follow; + var tokens = locale.split('-'); + do { + var item = tokens.join('-'); + follow = this._appendItemToChain(chain, item, blocks); + tokens.splice(-1, 1); + } while (tokens.length && (follow === true)) + return follow + }; + + VueI18n.prototype._appendBlockToChain = function _appendBlockToChain (chain, block, blocks) { + var follow = true; + for (var i = 0; (i < block.length) && (isBoolean(follow)); i++) { + var locale = block[i]; + if (isString(locale)) { + follow = this._appendLocaleToChain(chain, locale, blocks); + } + } + return follow + }; + + VueI18n.prototype._getLocaleChain = function _getLocaleChain (start, fallbackLocale) { + if (start === '') { return [] } + + if (!this._localeChainCache) { + this._localeChainCache = {}; + } + + var chain = this._localeChainCache[start]; + if (!chain) { + if (!fallbackLocale) { + fallbackLocale = this.fallbackLocale; + } + chain = []; + + // first block defined by start + var block = [start]; + + // while any intervening block found + while (isArray(block)) { + block = this._appendBlockToChain( + chain, + block, + fallbackLocale + ); + } + + // last block defined by default + var defaults; + if (isArray(fallbackLocale)) { + defaults = fallbackLocale; + } else if (isObject(fallbackLocale)) { + /* $FlowFixMe */ + if (fallbackLocale['default']) { + defaults = fallbackLocale['default']; + } else { + defaults = null; + } + } else { + defaults = fallbackLocale; + } + + // convert defaults to array + if (isString(defaults)) { + block = [defaults]; + } else { + block = defaults; + } + if (block) { + this._appendBlockToChain( + chain, + block, + null + ); + } + this._localeChainCache[start] = chain; + } + return chain + }; + + VueI18n.prototype._translate = function _translate ( + messages, + locale, + fallback, + key, + host, + interpolateMode, + args + ) { + var chain = this._getLocaleChain(locale, fallback); + var res; + for (var i = 0; i < chain.length; i++) { + var step = chain[i]; + res = + this._interpolate(step, messages[step], key, host, interpolateMode, args, [key]); + if (!isNull(res)) { + if (step !== locale && "development" !== 'production' && !this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) { + warn(("Fall back to translate the keypath '" + key + "' with '" + step + "' locale.")); + } + return res + } + } + return null + }; + + VueI18n.prototype._t = function _t (key, _locale, messages, host) { + var ref; + + var values = [], len = arguments.length - 4; + while ( len-- > 0 ) values[ len ] = arguments[ len + 4 ]; + if (!key) { return '' } + + var parsedArgs = parseArgs.apply(void 0, values); + if(this._escapeParameterHtml) { + parsedArgs.params = escapeParams(parsedArgs.params); + } + + var locale = parsedArgs.locale || _locale; + + var ret = this._translate( + messages, locale, this.fallbackLocale, key, + host, 'string', parsedArgs.params + ); + if (this._isFallbackRoot(ret)) { + if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) { + warn(("Fall back to translate the keypath '" + key + "' with root locale.")); + } + /* istanbul ignore if */ + if (!this._root) { throw Error('unexpected error') } + return (ref = this._root).$t.apply(ref, [ key ].concat( values )) + } else { + ret = this._warnDefault(locale, key, ret, host, values, 'string'); + if (this._postTranslation && ret !== null && ret !== undefined) { + ret = this._postTranslation(ret, key); + } + return ret + } + }; + + VueI18n.prototype.t = function t (key) { + var ref; + + var values = [], len = arguments.length - 1; + while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ]; + return (ref = this)._t.apply(ref, [ key, this.locale, this._getMessages(), null ].concat( values )) + }; + + VueI18n.prototype._i = function _i (key, locale, messages, host, values) { + var ret = + this._translate(messages, locale, this.fallbackLocale, key, host, 'raw', values); + if (this._isFallbackRoot(ret)) { + if (!this._isSilentTranslationWarn(key)) { + warn(("Fall back to interpolate the keypath '" + key + "' with root locale.")); + } + if (!this._root) { throw Error('unexpected error') } + return this._root.$i18n.i(key, locale, values) + } else { + return this._warnDefault(locale, key, ret, host, [values], 'raw') + } + }; + + VueI18n.prototype.i = function i (key, locale, values) { + /* istanbul ignore if */ + if (!key) { return '' } + + if (!isString(locale)) { + locale = this.locale; + } + + return this._i(key, locale, this._getMessages(), null, values) + }; + + VueI18n.prototype._tc = function _tc ( + key, + _locale, + messages, + host, + choice + ) { + var ref; + + var values = [], len = arguments.length - 5; + while ( len-- > 0 ) values[ len ] = arguments[ len + 5 ]; + if (!key) { return '' } + if (choice === undefined) { + choice = 1; + } + + var predefined = { 'count': choice, 'n': choice }; + var parsedArgs = parseArgs.apply(void 0, values); + parsedArgs.params = Object.assign(predefined, parsedArgs.params); + values = parsedArgs.locale === null ? [parsedArgs.params] : [parsedArgs.locale, parsedArgs.params]; + return this.fetchChoice((ref = this)._t.apply(ref, [ key, _locale, messages, host ].concat( values )), choice) + }; + + VueI18n.prototype.fetchChoice = function fetchChoice (message, choice) { + /* istanbul ignore if */ + if (!message || !isString(message)) { return null } + var choices = message.split('|'); + + choice = this.getChoiceIndex(choice, choices.length); + if (!choices[choice]) { return message } + return choices[choice].trim() + }; + + VueI18n.prototype.tc = function tc (key, choice) { + var ref; + + var values = [], len = arguments.length - 2; + while ( len-- > 0 ) values[ len ] = arguments[ len + 2 ]; + return (ref = this)._tc.apply(ref, [ key, this.locale, this._getMessages(), null, choice ].concat( values )) + }; + + VueI18n.prototype._te = function _te (key, locale, messages) { + var args = [], len = arguments.length - 3; + while ( len-- > 0 ) args[ len ] = arguments[ len + 3 ]; + + var _locale = parseArgs.apply(void 0, args).locale || locale; + return this._exist(messages[_locale], key) + }; + + VueI18n.prototype.te = function te (key, locale) { + return this._te(key, this.locale, this._getMessages(), locale) + }; + + VueI18n.prototype.getLocaleMessage = function getLocaleMessage (locale) { + return looseClone(this._vm.messages[locale] || {}) + }; + + VueI18n.prototype.setLocaleMessage = function setLocaleMessage (locale, message) { + if (this._warnHtmlInMessage === 'warn' || this._warnHtmlInMessage === 'error') { + this._checkLocaleMessage(locale, this._warnHtmlInMessage, message); + } + this._vm.$set(this._vm.messages, locale, message); + }; + + VueI18n.prototype.mergeLocaleMessage = function mergeLocaleMessage (locale, message) { + if (this._warnHtmlInMessage === 'warn' || this._warnHtmlInMessage === 'error') { + this._checkLocaleMessage(locale, this._warnHtmlInMessage, message); + } + this._vm.$set(this._vm.messages, locale, merge( + typeof this._vm.messages[locale] !== 'undefined' && Object.keys(this._vm.messages[locale]).length + ? Object.assign({}, this._vm.messages[locale]) + : {}, + message + )); + }; + + VueI18n.prototype.getDateTimeFormat = function getDateTimeFormat (locale) { + return looseClone(this._vm.dateTimeFormats[locale] || {}) + }; + + VueI18n.prototype.setDateTimeFormat = function setDateTimeFormat (locale, format) { + this._vm.$set(this._vm.dateTimeFormats, locale, format); + this._clearDateTimeFormat(locale, format); + }; + + VueI18n.prototype.mergeDateTimeFormat = function mergeDateTimeFormat (locale, format) { + this._vm.$set(this._vm.dateTimeFormats, locale, merge(this._vm.dateTimeFormats[locale] || {}, format)); + this._clearDateTimeFormat(locale, format); + }; + + VueI18n.prototype._clearDateTimeFormat = function _clearDateTimeFormat (locale, format) { + // eslint-disable-next-line no-autofix/prefer-const + for (var key in format) { + var id = locale + "__" + key; + + if (!this._dateTimeFormatters.hasOwnProperty(id)) { + continue + } + + delete this._dateTimeFormatters[id]; + } + }; + + VueI18n.prototype._localizeDateTime = function _localizeDateTime ( + value, + locale, + fallback, + dateTimeFormats, + key, + options + ) { + var _locale = locale; + var formats = dateTimeFormats[_locale]; + + var chain = this._getLocaleChain(locale, fallback); + for (var i = 0; i < chain.length; i++) { + var current = _locale; + var step = chain[i]; + formats = dateTimeFormats[step]; + _locale = step; + // fallback locale + if (isNull(formats) || isNull(formats[key])) { + if (step !== locale && "development" !== 'production' && !this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) { + warn(("Fall back to '" + step + "' datetime formats from '" + current + "' datetime formats.")); + } + } else { + break + } + } + + if (isNull(formats) || isNull(formats[key])) { + return null + } else { + var format = formats[key]; + + var formatter; + if (options) { + formatter = new Intl.DateTimeFormat(_locale, Object.assign({}, format, options)); + } else { + var id = _locale + "__" + key; + formatter = this._dateTimeFormatters[id]; + if (!formatter) { + formatter = this._dateTimeFormatters[id] = new Intl.DateTimeFormat(_locale, format); + } + } + + return formatter.format(value) + } + }; + + VueI18n.prototype._d = function _d (value, locale, key, options) { + /* istanbul ignore if */ + if (!VueI18n.availabilities.dateTimeFormat) { + warn('Cannot format a Date value due to not supported Intl.DateTimeFormat.'); + return '' + } + + if (!key) { + var dtf = !options ? new Intl.DateTimeFormat(locale) : new Intl.DateTimeFormat(locale, options); + return dtf.format(value) + } + + var ret = + this._localizeDateTime(value, locale, this.fallbackLocale, this._getDateTimeFormats(), key, options); + if (this._isFallbackRoot(ret)) { + if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) { + warn(("Fall back to datetime localization of root: key '" + key + "'.")); + } + /* istanbul ignore if */ + if (!this._root) { throw Error('unexpected error') } + return this._root.$i18n.d(value, key, locale) + } else { + return ret || '' + } + }; + + VueI18n.prototype.d = function d (value) { + var args = [], len = arguments.length - 1; + while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; + + var locale = this.locale; + var key = null; + var options = null; + + if (args.length === 1) { + if (isString(args[0])) { + key = args[0]; + } else if (isObject(args[0])) { + if (args[0].locale) { + locale = args[0].locale; + } + if (args[0].key) { + key = args[0].key; + } + } + + options = Object.keys(args[0]).reduce(function (acc, key) { + var obj; + + if (includes(dateTimeFormatKeys, key)) { + return Object.assign({}, acc, ( obj = {}, obj[key] = args[0][key], obj )) + } + return acc + }, null); + + } else if (args.length === 2) { + if (isString(args[0])) { + key = args[0]; + } + if (isString(args[1])) { + locale = args[1]; + } + } + + return this._d(value, locale, key, options) + }; + + VueI18n.prototype.getNumberFormat = function getNumberFormat (locale) { + return looseClone(this._vm.numberFormats[locale] || {}) + }; + + VueI18n.prototype.setNumberFormat = function setNumberFormat (locale, format) { + this._vm.$set(this._vm.numberFormats, locale, format); + this._clearNumberFormat(locale, format); + }; + + VueI18n.prototype.mergeNumberFormat = function mergeNumberFormat (locale, format) { + this._vm.$set(this._vm.numberFormats, locale, merge(this._vm.numberFormats[locale] || {}, format)); + this._clearNumberFormat(locale, format); + }; + + VueI18n.prototype._clearNumberFormat = function _clearNumberFormat (locale, format) { + // eslint-disable-next-line no-autofix/prefer-const + for (var key in format) { + var id = locale + "__" + key; + + if (!this._numberFormatters.hasOwnProperty(id)) { + continue + } + + delete this._numberFormatters[id]; + } + }; + + VueI18n.prototype._getNumberFormatter = function _getNumberFormatter ( + value, + locale, + fallback, + numberFormats, + key, + options + ) { + var _locale = locale; + var formats = numberFormats[_locale]; + + var chain = this._getLocaleChain(locale, fallback); + for (var i = 0; i < chain.length; i++) { + var current = _locale; + var step = chain[i]; + formats = numberFormats[step]; + _locale = step; + // fallback locale + if (isNull(formats) || isNull(formats[key])) { + if (step !== locale && "development" !== 'production' && !this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) { + warn(("Fall back to '" + step + "' number formats from '" + current + "' number formats.")); + } + } else { + break + } + } + + if (isNull(formats) || isNull(formats[key])) { + return null + } else { + var format = formats[key]; + + var formatter; + if (options) { + // If options specified - create one time number formatter + formatter = new Intl.NumberFormat(_locale, Object.assign({}, format, options)); + } else { + var id = _locale + "__" + key; + formatter = this._numberFormatters[id]; + if (!formatter) { + formatter = this._numberFormatters[id] = new Intl.NumberFormat(_locale, format); + } + } + return formatter + } + }; + + VueI18n.prototype._n = function _n (value, locale, key, options) { + /* istanbul ignore if */ + if (!VueI18n.availabilities.numberFormat) { + { + warn('Cannot format a Number value due to not supported Intl.NumberFormat.'); + } + return '' + } + + if (!key) { + var nf = !options ? new Intl.NumberFormat(locale) : new Intl.NumberFormat(locale, options); + return nf.format(value) + } + + var formatter = this._getNumberFormatter(value, locale, this.fallbackLocale, this._getNumberFormats(), key, options); + var ret = formatter && formatter.format(value); + if (this._isFallbackRoot(ret)) { + if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) { + warn(("Fall back to number localization of root: key '" + key + "'.")); + } + /* istanbul ignore if */ + if (!this._root) { throw Error('unexpected error') } + return this._root.$i18n.n(value, Object.assign({}, { key: key, locale: locale }, options)) + } else { + return ret || '' + } + }; + + VueI18n.prototype.n = function n (value) { + var args = [], len = arguments.length - 1; + while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; + + var locale = this.locale; + var key = null; + var options = null; + + if (args.length === 1) { + if (isString(args[0])) { + key = args[0]; + } else if (isObject(args[0])) { + if (args[0].locale) { + locale = args[0].locale; + } + if (args[0].key) { + key = args[0].key; + } + + // Filter out number format options only + options = Object.keys(args[0]).reduce(function (acc, key) { + var obj; + + if (includes(numberFormatKeys, key)) { + return Object.assign({}, acc, ( obj = {}, obj[key] = args[0][key], obj )) + } + return acc + }, null); + } + } else if (args.length === 2) { + if (isString(args[0])) { + key = args[0]; + } + if (isString(args[1])) { + locale = args[1]; + } + } + + return this._n(value, locale, key, options) + }; + + VueI18n.prototype._ntp = function _ntp (value, locale, key, options) { + /* istanbul ignore if */ + if (!VueI18n.availabilities.numberFormat) { + { + warn('Cannot format to parts a Number value due to not supported Intl.NumberFormat.'); + } + return [] + } + + if (!key) { + var nf = !options ? new Intl.NumberFormat(locale) : new Intl.NumberFormat(locale, options); + return nf.formatToParts(value) + } + + var formatter = this._getNumberFormatter(value, locale, this.fallbackLocale, this._getNumberFormats(), key, options); + var ret = formatter && formatter.formatToParts(value); + if (this._isFallbackRoot(ret)) { + if (!this._isSilentTranslationWarn(key)) { + warn(("Fall back to format number to parts of root: key '" + key + "' .")); + } + /* istanbul ignore if */ + if (!this._root) { throw Error('unexpected error') } + return this._root.$i18n._ntp(value, locale, key, options) + } else { + return ret || [] + } + }; + + Object.defineProperties( VueI18n.prototype, prototypeAccessors ); + + var availabilities; + // $FlowFixMe + Object.defineProperty(VueI18n, 'availabilities', { + get: function get () { + if (!availabilities) { + var intlDefined = typeof Intl !== 'undefined'; + availabilities = { + dateTimeFormat: intlDefined && typeof Intl.DateTimeFormat !== 'undefined', + numberFormat: intlDefined && typeof Intl.NumberFormat !== 'undefined' + }; + } + + return availabilities + } + }); + + VueI18n.install = install; + VueI18n.version = '8.28.2'; + + return VueI18n; + +}))); diff --git a/package-lock.json b/package-lock.json index 727d5102a..ecab1dd5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "quasar": "1.13.2", "underscore": "^1.13.6", "vue": "2.6.12", + "vue-i18n": "^8.28.2", "vue-qrcode-reader": "2.2", "vue-router": "3.4.3", "vuex": "3.5.1" @@ -536,6 +537,11 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==" }, + "node_modules/vue-i18n": { + "version": "8.28.2", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.28.2.tgz", + "integrity": "sha512-C5GZjs1tYlAqjwymaaCPDjCyGo10ajUphiwA922jKt9n7KPpqR7oM1PCwYzhB/E7+nT3wfdG3oRre5raIT1rKA==" + }, "node_modules/vue-qrcode-reader": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-2.2.0.tgz", diff --git a/package.json b/package.json index 113f3257b..f7f4b7a04 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "quasar": "1.13.2", "underscore": "^1.13.6", "vue": "2.6.12", + "vue-i18n": "^8.28.2", "vue-qrcode-reader": "2.2", "vue-router": "3.4.3", "vuex": "3.5.1" diff --git a/tools/build.py b/tools/build.py index 0c6ae3cb5..372e0f1dc 100644 --- a/tools/build.py +++ b/tools/build.py @@ -13,6 +13,7 @@ vendored_js = [ "/static/vendor/vue-router.js", "/static/vendor/vue-qrcode-reader.browser.js", "/static/vendor/vue-qrcode.js", + "/static/vendor/vue-i18n.js", "/static/vendor/vuex.js", "/static/vendor/quasar.ie.polyfills.umd.min.js", "/static/vendor/quasar.umd.js", From 8c3b4a87b10e0bc499fc90eac0bac2959d6cd776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 5 Apr 2023 12:51:05 +0200 Subject: [PATCH 2/2] add i18n to frontend, still incomplete, but a start --- lnbits/core/static/js/wallet.js | 10 +- lnbits/core/templates/admin/index.html | 56 +++-- lnbits/core/templates/core/_api_docs.html | 2 +- lnbits/core/templates/core/extensions.html | 8 +- lnbits/core/templates/core/index.html | 43 +--- lnbits/core/templates/core/wallet.html | 263 ++++++++++++--------- lnbits/static/i18n/de.js | 90 +++++++ lnbits/static/i18n/en.js | 86 +++++++ lnbits/static/i18n/es.js | 87 +++++++ lnbits/static/i18n/i18n.js | 1 + lnbits/static/i18n/jp.js | 85 +++++++ lnbits/static/js/base.js | 19 ++ lnbits/static/js/components.js | 28 +-- lnbits/templates/base.html | 62 ++++- 14 files changed, 634 insertions(+), 206 deletions(-) create mode 100644 lnbits/static/i18n/de.js create mode 100644 lnbits/static/i18n/en.js create mode 100644 lnbits/static/i18n/es.js create mode 100644 lnbits/static/i18n/i18n.js create mode 100644 lnbits/static/i18n/jp.js diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index 7068e9ef1..e570adc14 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -148,27 +148,27 @@ new Vue({ { name: 'memo', align: 'left', - label: 'Memo', + label: this.$t('memo'), field: 'memo' }, { name: 'date', align: 'left', - label: 'Date', + label: this.$t('date'), field: 'date', sortable: true }, { name: 'sat', align: 'right', - label: 'Amount (' + LNBITS_DENOMINATION + ')', + label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')', field: 'sat', sortable: true }, { name: 'fee', align: 'right', - label: 'Fee (m' + LNBITS_DENOMINATION + ')', + label: this.$t('fee') + ' (m' + LNBITS_DENOMINATION + ')', field: 'fee' } ], @@ -510,7 +510,7 @@ new Vue({ payInvoice: function () { let dismissPaymentMsg = this.$q.notify({ timeout: 0, - message: 'Processing payment...' + message: this.$t('processing_payment') }) LNbits.api diff --git a/lnbits/core/templates/admin/index.html b/lnbits/core/templates/admin/index.html index 4ddca6c0e..0511a4877 100644 --- a/lnbits/core/templates/admin/index.html +++ b/lnbits/core/templates/admin/index.html @@ -3,12 +3,14 @@
- Save your changes + {%raw%}{{ $t('save_tooltip') }}{%endraw%} - - Restart the server for changes to take effect - + {%raw%}{{ $t('restart_tooltip') }}{%endraw%} - Add funds to a wallet. + {%raw%}{{ $t('add_funds_tooltip') }}{%endraw%} - + - Delete all settings and reset to defaults. + {%raw%}{{ $t('reset_defaults_tooltip') }}{%endraw%}
@@ -74,25 +72,25 @@ @@ -113,7 +111,7 @@ -

TopUp a wallet

+

@@ -123,7 +121,7 @@ filled v-model="wallet.id" label="Wallet ID" - hint="Use the wallet ID to topup any wallet" + :hint="$t('topup_hint')" >
@@ -135,15 +133,25 @@ type="number" filled v-model="wallet.amount" - label="Topup amount" + :label="$t('amount')" >
- + - Cancel +
diff --git a/lnbits/core/templates/core/_api_docs.html b/lnbits/core/templates/core/_api_docs.html index aeb502333..03cfbf3e1 100644 --- a/lnbits/core/templates/core/_api_docs.html +++ b/lnbits/core/templates/core/_api_docs.html @@ -1,7 +1,7 @@ diff --git a/lnbits/core/templates/core/extensions.html b/lnbits/core/templates/core/extensions.html index 3ed45a300..c5ee942c6 100644 --- a/lnbits/core/templates/core/extensions.html +++ b/lnbits/core/templates/core/extensions.html @@ -11,8 +11,8 @@ color="primary" type="a" :href="['/install?usr=', user.id].join('')" - >Manage Extensions + :label="$t('manage_extensions')" + >

@@ -27,9 +27,7 @@ -

- You don't have any extensions installed :( -

+

- Press to claim bitcoin - + v-text="$t('press_to_claim')" + > {% else %} Add a new wallet + :label="$t('add_wallet')" + > {% endif %} @@ -40,27 +39,7 @@

{{SITE_TITLE}}

{{SITE_TAGLINE}}
-

- Easy to set up and lightweight, LNbits can run on any - lightning-network funding source, currently supporting LND, - c-lightning, OpenNode, LNPay and even LNbits itself! -

-

- You can run LNbits for yourself, or easily offer a custodian - solution for others. -

-

- Each wallet has its own API keys and there is no limit to the number - of wallets you can make. Being able to partition funds makes LNbits - a useful tool for money management and as a development tool. -

-

- Extensions add extra functionality to LNbits so you can experiment - with a range of cutting-edge technologies on the lightning network. - We have made developing extensions as easy as possible, and as a - free and open-source project, we encourage people to develop and - submit their own. -

+

View project in GitHub + :label="$t('view_github')" + > Donate + :label="$t('donate')" + >

{{SITE_DESCRIPTION | safe}}

@@ -94,7 +73,7 @@
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 46cfc690f..00f36b234 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -37,8 +37,8 @@ > Paste Request + :label="$t('paste_request')" + >
Create Invoice + :label="$t('create_invoice')" + >
scan - Use camera to scan an invoice/QR + :label="$t('scan')" + > + {% raw %}{{$t('camera_tooltip')}}{% endraw %}
@@ -92,12 +95,18 @@
-
Transactions
+
- Export to CSV + @@ -109,7 +118,9 @@ color="grey" @click="showChart" > - Show chart + {% raw %}{{$t('chart_tooltip')}}{% endraw %}
@@ -120,7 +131,7 @@ clearable v-model="paymentsTable.filter" debounce="300" - placeholder="Search by tag, memo, amount" + :placeholder="$t('search_by_tag_memo_amount')" class="q-mb-md" > @@ -131,7 +142,7 @@ :row-key="paymentTableRowKey" :columns="paymentsTable.columns" :pagination.sync="paymentsTable.pagination" - no-data-label="No transactions made yet" + :no-data-label="$t('no_transactions')" :filter="paymentsTable.filter" > {% raw %} @@ -159,7 +170,7 @@ color="grey" @click="props.expand = !props.expand" > - Pending + {{$t('pending')}}
- Invoice waiting to be paid + @@ -232,15 +243,15 @@ outline color="grey" @click="copyText(props.row.bolt11)" - >Copy invoice + :label="$t('copy_invoice')" + > Close + :label="$t('close')" + >
@@ -249,7 +260,7 @@ :name="'call_received'" :color="'green'" > - Payment Received + @@ -260,14 +271,14 @@ :name="'call_made'" :color="'pink'" > - Payment Sent +
- Outgoing payment pending + @@ -304,26 +315,17 @@ -

- This is an LNURL-withdraw QR code for slurping - everything from this wallet. Do not share with anyone. -

-

- It is compatible with balanceCheck and - balanceNotify so your wallet may keep - pulling the funds continuously from here after the first - withdraw. -

+

@@ -333,15 +335,11 @@ -

- This QR code contains your wallet URL with full access. - You can scan it from your phone to open your wallet from - there. -

+

@@ -371,9 +369,9 @@ unelevated class="q-mt-sm" color="primary" + :label="$t('update_name')" @click="updateWalletName()" - >Update name + >
@@ -381,20 +379,17 @@ -

- This whole wallet will be deleted, the funds will be - UNRECOVERABLE. -

+

Delete wallet + :label="$t('delete_wallet')" + >
@@ -445,7 +440,7 @@ filled dense v-model.number="receive.data.amount" - label="Amount ({{LNBITS_DENOMINATION}}) *" + :label="$('amount')+' ({{LNBITS_DENOMINATION}}) *'" mask="#.##" fill-mask="0" reverse-fill-mask @@ -459,7 +454,7 @@ dense v-model="receive.unit" type="text" - label="Unit" + :label="$t('unit')" :options="receive.units" > {% raw %}
@@ -493,13 +488,17 @@ type="submit" > - Withdraw from {{receive.lnurl.domain}} + {{$t('withdraw_from')}} {{receive.lnurl.domain}} - Create invoice + - Cancel +
- Copy invoice - Close + +
{% endraw %} @@ -545,24 +551,42 @@

- Description: {{ parse.invoice.description }}
+ : {{ + parse.invoice.description }}
Expire date: {{ parse.invoice.expireDate }}
Hash: {{ parse.invoice.hash }}

{% endraw %}
- Pay - Cancel + +
- Not enough funds! - Cancel + +
@@ -584,9 +608,13 @@

Login - Cancel +
{% endraw %} @@ -657,9 +685,13 @@ Send {{LNBITS_DENOMINATION}} - Cancel +
{% endraw %} @@ -675,7 +707,7 @@ dense v-model.trim="parse.data.request" type="textarea" - label="Paste an invoice, payment request or lnurl code *" + :label="$t('paste_invoice_label')" >
@@ -684,11 +716,15 @@ color="primary" :disable="parse.data.request == ''" type="submit" - >Read - Cancel + :label="$t('read')" + > +
@@ -700,8 +736,13 @@ >
- - Cancel +
@@ -719,9 +760,13 @@ >
- Cancel +
@@ -755,31 +800,21 @@
Warning
-

- Login functionality to be released in a future update, for now, - make sure you bookmark this page for future access to your - wallet! -

-

- This service is in BETA, and we hold no responsibility for people - losing access to funds. {% if service_fee > 0 %} To encourage you to - run your own LNbits installation, any balance on {% raw %}{{ - disclaimerDialog.location.host }}{% endraw %} will incur a charge of - {{ service_fee }}% service fee per week. {% endif - %} -

+

Copy wallet URL - I understand + :label="$t('copy_wallet_url')" + > +
diff --git a/lnbits/static/i18n/de.js b/lnbits/static/i18n/de.js new file mode 100644 index 000000000..c1ab5aee2 --- /dev/null +++ b/lnbits/static/i18n/de.js @@ -0,0 +1,90 @@ +window.localisation.de = { + server: 'Server', + theme: 'Theme', + funding: 'Funding', + users: 'Benutzer', + unit: 'Einheit', + restart: 'Server neu starten', + save: 'Speichern', + save_tooltip: 'Änderungen speichern', + topup: 'Aufladen', + topup_wallet: 'Wallet aufladen', + topup_hint: 'Nutze die Wallet-ID um eine beliebige Wallet aufzuladen', + restart_tooltip: 'Starte den Server neu um die Änderungen zu übernehmen', + add_funds_tooltip: 'Füge Geld zu einer Wallet hinzu.', + reset_defaults: 'Zurücksetzen', + reset_defaults_tooltip: + 'Lösche alle Einstellungen und setze auf die Standardeinstellungen zurück.', + download_backup: 'Datenbank-Backup herunterladen', + name_your_wallet: 'Benenne deine %{name} Wallet', + paste_invoice_label: + 'Füge eine Rechnung, Zahlungsanforderung oder lnurl ein *', + lnbits_description: + 'Einfach zu installieren und leichtgewichtig, LNbits kann auf jeder Lightning-Netzwerk-Funding-Quelle laufen, derzeit unterstützt LND, c-lightning, OpenNode, LNPay und sogar LNbits selbst! Du kannst LNbits für dich selbst laufen lassen oder anderen eine kundige Lösung anbieten. Jede Wallet hat ihre eigenen API-Schlüssel und es gibt keine Begrenzung für die Anzahl der Wallets, die du erstellen kannst. Die Möglichkeit, Gelder zu partitionieren, macht LNbits zu einem nützlichen Werkzeug für die Geldverwaltung und als Entwicklungswerkzeug. Erweiterungen fügen der LNbits zusätzliche Funktionalität hinzu, so dass du mit einer Reihe von vorderen Technologien auf dem Lightning-Netzwerk experimentieren kannst. Wir haben es so einfach wie möglich gemacht, Erweiterungen zu entwickeln, und als freies und Open-Source-Projekt ermutigen wir Menschen, sich selbst zu entwickeln und ihre eigenen Beiträge einzureichen.', + export_to_phone: 'Exportieren Sie auf das Telefon mit QR-Code', + export_to_phone_desc: + 'Dieser QR-Code enthält Ihre Wallet-URL mit vollständigem Zugriff. Sie können es von Ihrem Telefon aus scannen, um Ihre Wallet dort zu öffnen.', + wallets: 'Wallets', + add_wallet: 'Füge eine neue Wallet hinzu', + delete_wallet: 'Wallet löschen', + delete_wallet_desc: + 'Diese ganze Wallet wird gelöscht, die Mittel sind UNWIEDERBRINGLICH.', + rename_wallet: 'Wallet umbenennen', + update_name: 'Name aktualisieren', + press_to_claim: 'Drücken Sie, um Bitcoin zu beanspruchen', + donate: 'Spenden', + view_github: 'Auf GitHub anzeigen', + voidwallet_active: 'VoidWallet ist aktiv! Zahlungen deaktiviert', + use_with_caution: + 'VERWENDEN SIE MIT VORSICHT - %{name} Wallet ist noch in der BETA', + toggle_darkmode: 'Dark Mode umschalten', + view_swagger_docs: 'LNbits Swagger API-Dokumente anzeigen', + api_docs: 'Api docs', + commit_version: 'Commit version', + runs_on: 'Läuft auf', + credit_hint: 'Drücken Sie Enter, um das Konto zu belasten', + credit_label: '%{denomination} zu belasten', + paste_request: 'Anfrage einfügen', + create_invoice: 'Rechnung erstellen', + camera_tooltip: + 'Verwenden Sie die Kamera, um eine Rechnung / QR-Code zu scannen', + export_csv: 'Exportieren Sie nach CSV', + transactions: 'Transaktionen', + chart_tooltip: 'Diagramm anzeigen', + pending: 'Ausstehend', + copy_invoice: 'Rechnung kopieren', + close: 'Schließen', + cancel: 'Stornieren', + scan: 'Scannen', + read: 'Lesen', + pay: 'Zahlen', + memo: 'Memo', + date: 'Datum', + processing_payment: 'Zahlung wird bearbeitet ...', + not_enough_funds: 'Nicht genug Geld!', + search_by_tag_memo_amount: 'Suche nach Tag, Memo, Betrag', + invoice_waiting: 'Rechnung wartet auf Zahlung', + payment_received: 'Zahlung erhalten', + payment_sent: 'Zahlung gesendet', + outgoing_payment_pending: 'Ausgehende Zahlung ausstehend', + drain_funds: 'Leeren Sie die Mittel', + drain_funds_desc: + 'Dies ist ein LNURL-withdraw QR-Code zum Absaugen aller Mittel aus dieser Brieftasche. Teilen Sie es nicht mit jemandem. Es ist mit balanceCheck und balanceNotify kompatibel, so dass Ihre Brieftasche die Mittel nach dem ersten Abzug kontinuierlich von hier ziehen kann.', + i_understand: 'Ich verstehe', + copy_wallet_url: 'Wallet-URL kopieren', + disclaimer_dialog: + 'Login-Funktionalität wird in einem zukünftigen Update veröffentlicht, für jetzt, stellen Sie sicher, dass Sie diese Seite als Lesezeichen speichern, um zukünftigen Zugriff auf Ihre Wallet zu erhalten! Dieser Service ist in BETA und wir übernehmen keine Verantwortung für Personen, die den Zugriff auf ihre Mittel verlieren.', + no_transactions: 'Noch keine Transaktionen', + manage_extensions: 'Erweiterungen verwalten', + manage_server: 'Server verwalten', + extensions: 'Erweiterungen', + no_extensions: 'Sie haben keine Erweiterungen installiert :(', + created: 'Erstellt', + payment_hash: 'Zahlungs-Hash', + fee: 'Gebühr', + amount: 'Menge', + description: 'Beschreibung', + expiry: 'Ablauf', + webhook: 'Webhook', + payment_proof: 'Zahlungsbeleg' +} diff --git a/lnbits/static/i18n/en.js b/lnbits/static/i18n/en.js new file mode 100644 index 000000000..9b50873d8 --- /dev/null +++ b/lnbits/static/i18n/en.js @@ -0,0 +1,86 @@ +window.localisation.en = { + server: 'Server', + theme: 'Theme', + funding: 'Funding', + users: 'Users', + restart: 'Restart server', + save: 'Save', + save_tooltip: 'Save your changes', + topup: 'Topup', + topup_wallet: 'Topup a wallet', + topup_hint: 'Use the wallet ID to topup any wallet', + restart_tooltip: 'Restart the server for changes to take effect', + add_funds_tooltip: 'Add funds to a wallet.', + reset_defaults: 'Reset to defaults', + reset_defaults_tooltip: 'Delete all settings and reset to defaults.', + download_backup: 'Download database backup', + name_your_wallet: 'Name your %{name} wallet', + paste_invoice_label: 'Paste an invoice, payment request or lnurl code *', + lnbits_description: + 'Easy to set up and lightweight, LNbits can run on any lightning-network funding source, currently supporting LND, c-lightning, OpenNode, LNPay and even LNbits itself! You can run LNbits for yourself, or easily offer a custodian solution for others. Each wallet has its own API keys and there is no limit to the number of wallets you can make. Being able to partition funds makes LNbits a useful tool for money management and as a development tool. Extensions add extra functionality to LNbits so you can experiment with a range of cutting-edge technologies on the lightning network. We have made developing extensions as easy as possible, and as a free and open-source project, we encourage people to develop and submit their own.', + export_to_phone: 'Export to Phone with QR Code', + export_to_phone_desc: + 'This QR code contains your wallet URL with full access. You can scan it from your phone to open your wallet from there.', + wallets: 'Wallets', + add_wallet: 'Add a new wallet', + delete_wallet: 'Delete wallet', + delete_wallet_desc: + 'This whole wallet will be deleted, the funds will be UNRECOVERABLE.', + rename_wallet: 'Rename wallet', + update_name: 'Update name', + press_to_claim: 'Press to claim bitcoin', + donate: 'Donate', + view_github: 'View on GitHub', + voidwallet_active: 'VoidWallet is active! Payments disabled', + use_with_caution: 'USE WITH CAUTION - %{name} wallet is still in BETA', + toggle_darkmode: 'Toggle Dark Mode', + view_swagger_docs: 'View LNbits Swagger API docs', + api_docs: 'Api docs', + commit_version: 'Commit version', + runs_on: 'Runs on', + credit_hint: 'Press Enter to credit account', + credit_label: '%{denomination} to credit', + paste_request: 'Paste Request', + create_invoice: 'Create Invoice', + camera_tooltip: 'Use camera to scan an invoice/QR', + export_csv: 'Export to CSV', + transactions: 'Transactions', + chart_tooltip: 'Show chart', + pending: 'Pending', + copy_invoice: 'Copy invoice', + close: 'Close', + cancel: 'Cancel', + scan: 'Scan', + read: 'Read', + pay: 'Pay', + memo: 'Memo', + date: 'Date', + processing_payment: 'Processing payment...', + not_enough_funds: 'Not enough funds!', + search_by_tag_memo_amount: 'Search by tag, memo, amount', + invoice_waiting: 'Invoice waiting to be paid', + payment_received: 'Payment Received', + payment_sent: 'Payment Sent', + outgoing_payment_pending: 'Outgoing payment pending', + drain_funds: 'Drain Funds', + drain_funds_desc: + 'This is an LNURL-withdraw QR code for slurping everything from this wallet. Do not share with anyone. It is compatible with balanceCheck and balanceNotify so your wallet may keep pulling the funds continuously from here after the first withdraw.', + i_understand: 'I understand', + copy_wallet_url: 'Copy wallet URL', + disclaimer_dialog: + 'Login functionality to be released in a future update, for now, make sure you bookmark this page for future access to your wallet! This service is in BETA, and we hold no responsibility for people losing access to funds.', + no_transactions: 'No transactions made yet', + manage_extensions: 'Manage Extensions', + manage_server: 'Manage Server', + extensions: 'Extensions', + no_extensions: "You don't have any extensions installed :(", + created: 'Created', + payment_hash: 'Payment Hash', + fee: 'Fee', + amount: 'Amount', + unit: 'Unit', + description: 'Description', + expiry: 'Expiry', + webhook: 'Webhook', + payment_proof: 'Payment Proof' +} diff --git a/lnbits/static/i18n/es.js b/lnbits/static/i18n/es.js new file mode 100644 index 000000000..1c511cbbb --- /dev/null +++ b/lnbits/static/i18n/es.js @@ -0,0 +1,87 @@ +window.localisation.es = { + server: 'Servidor', + theme: 'Tema', + funding: 'Financiación', + unit: 'Unidad', + users: 'Usuarios', + restart: 'Reiniciar el servidor', + save: 'Guardar', + save_tooltip: 'Guardar cambios', + topup: 'Recargar', + topup_wallet: 'Recargar billetera', + topup_hint: 'Utilice el ID de billetera para recargar cualquier billetera', + restart_tooltip: 'Reinicie el servidor para aplicar los cambios', + add_funds_tooltip: 'Agregue fondos a una billetera.', + reset_defaults: 'Restablecer', + reset_defaults_tooltip: + 'Borrar todas las configuraciones y restablecer a los valores predeterminados.', + download_backup: 'Descargar copia de seguridad de la base de datos', + name_your_wallet: 'Nombre de su billetera %{name}', + paste_invoice_label: 'Pegue la factura aquí', + lnbits_description: + 'Fácil de instalar y liviano, LNbits puede ejecutarse en cualquier fuente de financiación de la red Lightning, actualmente compatible con LND, c-lightning, OpenNode, LNPay y hasta LNbits mismo! Puede ejecutar LNbits para usted mismo o ofrecer una solución competente a otros. Cada billetera tiene su propia clave API y no hay límite para la cantidad de billeteras que puede crear. La capacidad de particionar fondos hace de LNbits una herramienta útil para la administración de fondos y como herramienta de desarrollo. Las extensiones agregan funcionalidad adicional a LNbits, por lo que puede experimentar con una variedad de tecnologías de vanguardia en la red Lightning. Lo hemos hecho lo más simple posible para desarrollar extensiones y, como un proyecto gratuito y de código abierto, animamos a las personas a que se desarrollen a sí mismas y envíen sus propios contribuciones.', + export_to_phone: 'Exportar a teléfono con código QR', + export_to_phone_desc: + 'Este código QR contiene su URL de billetera con acceso completo. Puede escanearlo desde su teléfono para abrir su billetera allí.', + wallets: 'Billeteras', + add_wallet: 'Agregar nueva billetera', + delete_wallet: 'Eliminar billetera', + delete_wallet_desc: + 'Esta billetera completa se eliminará, los fondos son IRREVERSIBLES.', + rename_wallet: 'Cambiar el nombre de la billetera', + update_name: 'Actualizar nombre', + press_to_claim: 'Presione para reclamar Bitcoin', + donate: 'Donar', + view_github: 'Ver en GitHub', + voidwallet_active: '¡VoidWallet está activo! Pagos desactivados', + use_with_caution: 'USAR CON CUIDADO - %{name} Wallet aún está en BETA', + toggle_darkmode: 'Cambiar modo oscuro', + view_swagger_docs: 'Ver documentos de API de LNbits Swagger', + api_docs: 'Documentos de API', + commit_version: 'Versión de compromiso', + runs_on: 'Corre en', + credit_hint: 'Presione Enter para cargar la cuenta', + credit_label: 'Cargar %{denomination}', + paste_request: 'Pegar solicitud', + create_invoice: 'Crear factura', + camera_tooltip: 'Utilice la cámara para escanear una factura / código QR', + export_csv: 'Exportar a CSV', + transactions: 'Transacciones', + chart_tooltip: 'Mostrar gráfico', + pending: 'Pendiente', + copy_invoice: 'Copiar factura', + close: 'Cerrar', + cancel: 'Cancelar', + scan: 'Escanear', + read: 'Leer', + pay: 'Pagar', + memo: 'Memo', + date: 'Fecha', + processing_payment: 'Procesando pago ...', + not_enough_funds: '¡No hay suficientes fondos!', + search_by_tag_memo_amount: 'Buscar por etiqueta, memo, cantidad', + invoice_waiting: 'Factura esperando pago', + payment_received: 'Pago recibido', + payment_sent: 'Pago enviado', + outgoing_payment_pending: 'Pago saliente pendiente', + drain_funds: 'Drenar fondos', + drain_funds_desc: + 'Este es un código QR LNURL-withdraw para drenar todos los fondos de esta billetera. No lo comparta con nadie. Es compatible con balanceCheck y balanceNotify, por lo que su billetera puede continuar drenando los fondos de aquí después del primer drenaje.', + i_understand: 'Lo entiendo', + copy_wallet_url: 'Copiar URL de billetera', + disclaimer_dialog: + 'La funcionalidad de inicio de sesión se lanzará en una actualización futura, por ahora, asegúrese de guardar esta página como marcador para acceder a su billetera en el futuro. Este servicio está en BETA y no asumimos ninguna responsabilidad por personas que pierdan el acceso a sus fondos.', + no_transactions: 'No hay transacciones todavía', + manage_extensions: 'Administrar extensiones', + manage_server: 'Administrar servidor', + extensions: 'Extensiones', + no_extensions: 'No tienes extensiones instaladas :(', + created: 'Creado', + payment_hash: 'Hash de pago', + fee: 'Cuota', + amount: 'Cantidad', + description: 'Descripción', + expiry: 'Expiración', + webhook: 'Webhook', + payment_proof: 'Prueba de pago' +} diff --git a/lnbits/static/i18n/i18n.js b/lnbits/static/i18n/i18n.js new file mode 100644 index 000000000..83802d38a --- /dev/null +++ b/lnbits/static/i18n/i18n.js @@ -0,0 +1 @@ +window.localisation = {} diff --git a/lnbits/static/i18n/jp.js b/lnbits/static/i18n/jp.js new file mode 100644 index 000000000..2b5ff0b0c --- /dev/null +++ b/lnbits/static/i18n/jp.js @@ -0,0 +1,85 @@ +window.localisation.jp = { + server: 'サーバー', + theme: 'テーマ', + funding: '資金調達', + users: 'ユーザー', + restart: 'サーバーを再起動する', + save: '保存', + unit: '単位', + save_tooltip: '変更を保存する', + topup: 'トップアップ', + topup_wallet: 'ウォレットをトップアップする', + topup_hint: 'ウォレットIDを使用して、任意のウォレットをトップアップできます', + restart_tooltip: 'サーバーを再起動して変更を適用します', + add_funds_tooltip: 'ウォレットに資金を追加します。', + reset_defaults: 'リセット', + reset_defaults_tooltip: 'すべての設定を削除してデフォルトに戻します。', + download_backup: 'データベースのバックアップをダウンロードする', + name_your_wallet: 'あなたのウォレットの名前 %{name}', + paste_invoice_label: '請求書を貼り付けてください', + lnbits_description: + '簡単にインストールでき、軽量で、LNbitsは現在LND、c-lightning、OpenNode、LNPay、さらにLNbits自身で動作する任意のLightningネットワークの資金源で実行できます! LNbitsを自分で実行することも、他の人に優れたソリューションを提供することもできます。各ウォレットには独自のAPIキーがあり、作成できるウォレットの数に制限はありません。資金を分割する機能は、LNbitsを資金管理ツールとして使用したり、開発ツールとして使用したりするための便利なツールです。拡張機能は、LNbitsに追加の機能を追加します。そのため、LNbitsは最先端の技術をネットワークLightningで試すことができます。拡張機能を開発するのは簡単で、無料でオープンソースのプロジェクトであるため、人々が自分で開発し、自分の貢献を送信することを奨励しています。', + export_to_phone: '電話にエクスポート', + export_to_phone_desc: + 'ウォレットを電話にエクスポートすると、ウォレットを削除する前にウォレットを復元できます。ウォレットを削除すると、ウォレットの秘密鍵が削除され、ウォレットを復元することはできません。', + wallets: 'ウォレット', + add_wallet: 'ウォレットを追加', + delete_wallet: 'ウォレットを削除', + delete_wallet_desc: + 'ウォレットを削除すると、ウォレットの秘密鍵が削除され、ウォレットを復元することはできません。', + rename_wallet: 'ウォレットの名前を変更', + update_name: '名前を更新', + press_to_claim: 'クレームするには押してください', + donate: '寄付', + voidwallet_active: 'Voidwalletアクティブ', + use_with_caution: '注意して使用してください', + toggle_dark_mode: 'ダークモードを切り替える', + view_swagger_docs: 'Swaggerドキュメントを表示', + api_docs: 'APIドキュメント', + commit_version: 'コミットバージョン', + runs_on: 'で実行', + credit_hint: + 'クレジットカードを使用して資金を追加するには、LNbitsを使用してください。', + credit_label: 'クレジットカード', + paste_request: 'リクエストを貼り付ける', + create_invoice: '請求書を作成する', + camera_tooltip: 'QRコードを読み取る', + export_csv: 'CSVでエクスポート', + transactions: 'トランザクション', + chart_tooltip: 'チャートを表示するには、グラフの上にカーソルを合わせます', + pending: '保留中', + copy_invoice: '請求書をコピー', + close: '閉じる', + cancel: 'キャンセル', + scan: 'スキャン', + read: '読む', + pay: '支払う', + memo: 'メモ', + date: '日付', + processing_payment: '支払い処理中', + not_enough_funds: '資金が不足しています', + search_by_tag_memo_amount: 'タグ、メモ、金額で検索', + invoice_waiting: '請求書を待っています', + payment_received: 'お支払いありがとうございます', + payment_sent: '支払いが完了しました', + outgoing_payment_pending: '支払い保留中', + drain_funds: '資金を排出する', + drain_funds_desc: 'ウォレットの残高をすべて他のウォレットに送金します', + i_understand: '理解した', + copy_wallet_url: 'ウォレットURLをコピー', + disclaimer_dialog: + 'ウォレットを削除すると、ウォレットの秘密鍵が削除され、ウォレットを復元することはできません。ウォレットを削除する前に、ウォレットをエクスポートしてください。', + no_transactions: 'トランザクションはありません', + manage_extensions: '拡張機能を管理する', + manage_server: 'サーバーを管理する', + extensions: '拡張機能', + no_extensions: '拡張機能はありません', + created: '作成済み', + payment_hash: '支払いハッシュ', + fee: '料金', + amount: '量', + description: '説明', + expiry: '有効期限', + webhook: 'ウェブフック', + payment_proof: '支払い証明' +} diff --git a/lnbits/static/js/base.js b/lnbits/static/js/base.js index d424d5636..74af3b660 100644 --- a/lnbits/static/js/base.js +++ b/lnbits/static/js/base.js @@ -1,6 +1,14 @@ /* globals crypto, moment, Vue, axios, Quasar, _ */ +Vue.use(VueI18n) + window.LOCALE = 'en' +window.i18n = new VueI18n({ + locale: window.LOCALE, + fallbackLocale: window.LOCALE, + messages: window.localisation +}) + window.EventHub = new Vue() window.LNbits = { api: { @@ -320,6 +328,7 @@ window.LNbits = { } window.windowMixin = { + i18n: window.i18n, data: function () { return { g: { @@ -335,6 +344,10 @@ window.windowMixin = { }, methods: { + changeLanguage: function (newValue) { + window.i18n.locale = newValue + this.$q.localStorage.set('lnbits.lang', newValue) + }, changeColor: function (newValue) { document.body.setAttribute('data-theme', newValue) this.$q.localStorage.set('lnbits.theme', newValue) @@ -364,6 +377,12 @@ window.windowMixin = { } this.g.allowedThemes = window.allowedThemes ?? ['bitcoin'] + let locale = this.$q.localStorage.getItem('lnbits.lang') + if (locale) { + window.LOCALE = locale + window.i18n.locale = locale + } + addEventListener('offline', event => { this.g.offline = true }) diff --git a/lnbits/static/js/components.js b/lnbits/static/js/components.js index 57f79bdbc..4520e9249 100644 --- a/lnbits/static/js/components.js +++ b/lnbits/static/js/components.js @@ -28,7 +28,7 @@ Vue.component('lnbits-wallet-list', { }, template: ` - Wallets + - Add a wallet + @@ -112,7 +112,7 @@ Vue.component('lnbits-extension-list', { }, template: ` - Extensions + - Extensions + @@ -145,7 +145,7 @@ Vue.component('lnbits-extension-list', { - Manage Extensions + @@ -199,7 +199,7 @@ Vue.component('lnbits-admin-ui', { - Manage Server + @@ -228,34 +228,34 @@ Vue.component('lnbits-payment-details', {
-
Created:
+
:
{{ payment.date }} ({{ payment.dateFrom }})
-
Expiry:
+
:
{{ payment.expirydate }} ({{ payment.expirydateFrom }})
-
Description:
+
:
{{ payment.memo }}
-
Amount:
+
:
{{ (payment.amount / 1000).toFixed(3) }} {{LNBITS_DENOMINATION}}
-
Fee:
+
:
{{ (payment.fee / 1000).toFixed(3) }} {{LNBITS_DENOMINATION}}
-
Payment hash:
+
:
{{ payment.payment_hash }}
-
Webhook:
+
:
{{ payment.webhook }} @@ -264,7 +264,7 @@ Vue.component('lnbits-payment-details', {
-
Payment proof:
+
:
{{ payment.preimage }}
diff --git a/lnbits/templates/base.html b/lnbits/templates/base.html index 6d03d3f0d..2b9f270f4 100644 --- a/lnbits/templates/base.html +++ b/lnbits/templates/base.html @@ -46,15 +46,14 @@ {% block beta %} {% if VOIDWALLET %} - VoidWallet is active! Payments disabled + {% raw %}{{ $t('voidwallet_active') }}{% endraw %} {%endif%} USE WITH CAUTION - {{SITE_TITLE}} wallet is still in BETA + v-show="$q.screen.gt.sm" + v-text='$t("use_with_caution", { name: "{{ SITE_TITLE }}" })' + > {% endblock %} - OFFLINE + OFFLINE + + + + + EN + + + + + DE + + + + + ES + + + + + JP + + + + - Toggle Dark Mode + {% raw %}{{ $t('toggle_darkmode') }}{% endraw %} @@ -213,7 +245,8 @@
Commit version: {{LNBITS_VERSION}}{% raw %}{{ $t('commit_version') }}{% endraw %}: + {{LNBITS_VERSION}} @@ -226,8 +259,10 @@ target="_blank" rel="noopener" > - API DOCS - View LNbits Swagger API docs + {% raw %}{{ $t('api_docs') }}{% endraw %} + {% raw %}{{ $t('view_swagger_docs') }}{% endraw %} - View project in GitHub + {% raw %}{{ $t('view_github') }}{% endraw %} @@ -253,6 +288,11 @@ {% endfor %} + + + + +