diff --git a/package-lock.json b/package-lock.json index 40ed8a15..2a0eeb24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -176,6 +176,11 @@ } } }, + "@ffz/icu-msgparser": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@ffz/icu-msgparser/-/icu-msgparser-1.0.1.tgz", + "integrity": "sha512-0+i29DZUJIqrK02rHcxDXmuLSOyp4EJERu4uQDOh7w39d5TqXHbFP2CRtSXjYYCQZWHoKwn37ZZScn+8+zH56g==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", diff --git a/package.json b/package.json index 4a9d9831..d934e736 100755 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "url": "https://github.com/FrankerFaceZ/FrankerFaceZ.git" }, "dependencies": { + "@ffz/icu-msgparser": "^1.0.1", "crypto-js": "^3.1.9-1", "dayjs": "^1.7.7", "displacejs": "^1.2.4", diff --git a/src/i18n.js b/src/i18n.js index 624609b9..9460c8dc 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -2,35 +2,32 @@ // ============================================================================ // Localization -// This is based on Polyglot, but with some changes to avoid dependencies on -// additional libraries and with support for Vue. // ============================================================================ +import Parser from '@ffz/icu-msgparser'; + import {SERVER} from 'utilities/constants'; import {get, pick_random, has, timeout} from 'utilities/object'; import Module from 'utilities/module'; +import NewTransCore from 'utilities/translation-core'; + const FACES = ['(・`ω´・)', ';;w;;', 'owo', 'ono', 'oAo', 'oxo', 'ovo;', 'UwU', '>w<', '^w^', '> w >', 'v.v'], - format_text = (phrase, token_regex, formatter) => { - const out = []; + transformText = (ast, fn) => { + return ast.map(node => { + if ( typeof node === 'string' ) + return fn(node); - let i = 0, match; - token_regex.lastIndex = 0; + else if ( typeof node === 'object' && node.o ) { + const out = Object.assign(node, {o: {}}); + for(const key of Object.keys(node.o)) + out.o[key] = transformText(node.o[key], fn) + } - while((match = token_regex.exec(phrase))) { - if ( match.index !== i ) - out.push(formatter(phrase.slice(i, match.index))) - - out.push(match[0]); - i = match.index + match[0].length; - } - - if ( i < phrase.length ) - out.push(formatter(phrase.slice(i))); - - return out.join('') + return node; + }) }, owo = text => text @@ -44,19 +41,12 @@ const FACES = ['(・`ω´・)', ';;w;;', 'owo', 'ono', 'oAo', 'oxo', 'ovo;', 'Uw TRANSFORMATIONS = { - double: (key, text) => - `${text} ${text}`, - - upper: (key, text, opts, locale, token_regex) => - format_text(text, token_regex, t => t.toUpperCase()), - - lower: (key, text, opts, locale, token_regex) => - format_text(text, token_regex, t => t.toLowerCase()), - - append_key: (key, text) => `${text} (${key})`, - - owo: (key, text, opts, locale, token_regex) => - format_text(text, token_regex, t => owo(t)) + double: (key, ast) => [...ast, ' ', ...ast], + upper: (key, ast) => transformText(ast, n => n.toUpperCase()), + lower: (key, ast) => transformText(ast, n => n.toLowerCase()), + append_key: (key, ast) => [...ast, ` (${key})`], + set_key: (key, ast) => [key], + owo: (key, ast) => transformText(ast, owo) }; @@ -69,14 +59,16 @@ export class TranslationManager extends Module { super(...args); this.inject('settings'); + this.parser = new Parser; + this._seen = new Set; this.availableLocales = ['en']; //, 'de', 'ja']; this.localeData = { - en: { name: 'English' }, - //de: { name: 'Deutsch' }, - //ja: { name: '日本語' } + en: { name: 'English' }/*, + de: { name: 'Deutsch' }, + ja: { name: '日本語' }*/ } @@ -92,6 +84,7 @@ export class TranslationManager extends Module { {value: 'upper', title: 'Upper Case'}, {value: 'lower', title: 'Lower Case'}, {value: 'append_key', title: 'Append Key'}, + {value: 'set_key', title: 'Set to Key'}, {value: 'double', title: 'Double'}, {value: 'owo', title: "owo what's this"} ] @@ -137,10 +130,8 @@ export class TranslationManager extends Module { } onEnable() { - this._ = new TranslationCore({ - formatters: { - 'humanTime': n => this.toHumanTime(n) - } + this._ = new NewTransCore({ //TranslationCore({ + warn: (...args) => this.log.warn(...args), }); if ( window.BroadcastChannel ) { @@ -210,48 +201,8 @@ export class TranslationManager extends Module { this.broadcast({type: 'seen', key}); } - - toLocaleString(thing) { - if ( thing && thing.toLocaleString ) - return thing.toLocaleString(this._.locale); - return thing; - } - - - toHumanTime(duration, factor = 1) { - // TODO: Make this better. Make all time handling better in fact. - - if ( duration instanceof Date ) - duration = (Date.now() - duration.getTime()) / 1000; - - duration = Math.floor(duration); - - const years = Math.floor((duration * factor) / 31536000) / factor; - if ( years >= 1 ) - return this.t('human-time.years', '%{count} year%{count|en_plural}', years); - - const days = Math.floor((duration %= 31536000) / 86400); - if ( days >= 1 ) - return this.t('human-time.days', '%{count} day%{count|en_plural}', days); - - const hours = Math.floor((duration %= 86400) / 3600); - if ( hours >= 1 ) - return this.t('human-time.hours', '%{count} hour%{count|en_plural}', hours); - - const minutes = Math.floor((duration %= 3600) / 60); - if ( minutes >= 1 ) - return this.t('human-time.minutes', '%{count} minute%{count|en_plural}', minutes); - - const seconds = duration % 60; - if ( seconds >= 1 ) - return this.t('human-time.seconds', '%{count} second%{count|en_plural}', seconds); - - return this.t('human-time.none', 'less than a second'); - } - - async loadLocale(locale) { - /*if ( locale === 'en' ) + if ( locale === 'en' ) return {}; if ( locale === 'de' ) @@ -363,7 +314,7 @@ export class TranslationManager extends Module { support: '対応' } } - }*/ + } const resp = await fetch(`${SERVER}/script/i18n/${locale}.json`); if ( ! resp.ok ) { @@ -384,6 +335,8 @@ export class TranslationManager extends Module { if ( new_locale === old_locale ) return []; + await this.loadDayjsLocale(new_locale); + this._.locale = new_locale; this._.clear(); this.log.info(`Changed Locale: ${new_locale} -- Old: ${old_locale}`); @@ -412,14 +365,49 @@ export class TranslationManager extends Module { return added; } + async loadDayjsLocale(locale) { + if ( locale === 'en' ) + return; + + try { + await import( + /* webpackMode: 'lazy' */ + /* webpackChunkName: 'i18n-[index]' */ + `dayjs/locale/${locale}` + ); + } catch(err) { + this.log.warn(`Unable to load day.js locale data for locale "${locale}"`, err); + } + } + has(key) { return this._.has(key); } + toLocaleString(...args) { + return this._.toLocaleString(...args); + } + + toHumanTime(...args) { + return this._.formatHumanTime(...args); + } + formatNumber(...args) { return this._.formatNumber(...args); } + formatDate(...args) { + return this._.formatDate(...args) + } + + formatTime(...args) { + return this._.formatTime(...args) + } + + formatDateTime(...args) { + return this._.formatDateTime(...args) + } + t(key, ...args) { this.see(key); return this._.t(key, ...args); @@ -437,7 +425,7 @@ export class TranslationManager extends Module { // TranslationCore // ============================================================================ -const REPLACE = String.prototype.replace, +const REPLACE = String.prototype.replace; /*, SPLIT = String.prototype.split; const DEFAULT_FORMATTERS = { @@ -460,7 +448,7 @@ export default class TranslationCore { this.onMissingKey = typeof options.onMissingKey === 'function' ? options.onMissingKey : allowMissing; this.transformPhrase = typeof options.transformPhrase === 'function' ? options.transformPhrase : transformPhrase; this.transformList = typeof options.transformList === 'function' ? options.transformList : transformList; - this.delimiter = options.delimiter || /\s*\|\|\|\|\s*/; + this.delimiter = options.delimiter || /\s*\|\|\|\|\s/; this.tokenRegex = options.tokenRegex || /%\{(.*?)(?:\|(.*?))?\}/g; this.formatters = Object.assign({}, DEFAULT_FORMATTERS, options.formatters || {}); } @@ -575,7 +563,7 @@ export default class TranslationCore { return this.transformList(p, opts, locale, this.tokenRegex, this.formatters); } -} +}*/ // ============================================================================ @@ -593,10 +581,7 @@ export function transformList(phrase, substitutions, locale, token_regex, format const options = typeof substitutions === 'number' ? {count: substitutions} : substitutions; if ( is_array ) - p = p[pluralTypeIndex( - locale || 'en', - has(options, 'count') ? options.count : 1 - )] || p[0]; + p = p[0]; const result = []; @@ -642,10 +627,7 @@ export function transformPhrase(phrase, substitutions, locale, token_regex, form const options = typeof substitutions === 'number' ? {count: substitutions} : substitutions; if ( is_array ) - result = result[pluralTypeIndex( - locale || 'en', - has(options, 'count') ? options.count : 1 - )] || result[0]; + result = result[0]; if ( typeof result === 'string' ) result = REPLACE.call(result, token_regex, (expr, arg, fmt) => { @@ -663,65 +645,4 @@ export function transformPhrase(phrase, substitutions, locale, token_regex, form }); return result; -} - - -// ============================================================================ -// Plural Nonsense -// ============================================================================ - -const PLURAL_TYPE_TO_LANG = { - arabic: ['ar'], - chinese: ['fa', 'id', 'ja', 'ko', 'lo', 'ms', 'th', 'tr', 'zh'], - german: ['da', 'de', 'en', 'es', 'es', 'fi', 'el', 'he', 'hu', 'it', 'nl', 'no', 'pt', 'sv'], - french: ['fr', 'tl', 'pt-br'], - russian: ['hr', 'ru', 'lt'], - czech: ['cs', 'sk'], - polish: ['pl'], - icelandic: ['is'] -}; - -const PLURAL_LANG_TO_TYPE = {}; - -for(const type in PLURAL_TYPE_TO_LANG) // eslint-disable-line guard-for-in - for(const lang of PLURAL_TYPE_TO_LANG[type]) - PLURAL_LANG_TO_TYPE[lang] = type; - -const PLURAL_TYPES = { - arabic: n => { - if ( n < 3 ) return n; - const n1 = n % 100; - if ( n1 >= 3 && n1 <= 10 ) return 3; - return n1 >= 11 ? 4 : 5; - }, - chinese: () => 0, - german: n => n !== 1 ? 1 : 0, - french: n => n > 1 ? 1 : 0, - russian: n => { - const n1 = n % 10, n2 = n % 100; - if ( n1 === 1 && n2 !== 11 ) return 0; - return n1 >= 2 && n1 <= 4 && (n2 < 10 || n2 >= 20) ? 1 : 2; - }, - czech: n => { - if ( n === 1 ) return 0; - return n >= 2 && n <= 4 ? 1 : 2; - }, - polish: n => { - if ( n === 1 ) return 0; - const n1 = n % 10, n2 = n % 100; - return n1 >= 2 && n1 <= 4 && (n2 < 10 || n2 >= 20) ? 1 : 2; - }, - icelandic: n => n % 10 !== 1 || n % 100 === 11 ? 1 : 0 -}; - - -export function pluralTypeIndex(locale, n) { - let type = PLURAL_LANG_TO_TYPE[locale]; - if ( ! type ) { - const idx = locale.indexOf('-'); - if ( idx !== -1 ) - type = PLURAL_LANG_TO_TYPE[locale.slice(0, idx)] - } - - return PLURAL_TYPES[type || 'german'](n); } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 522164cc..0b76d4f5 100644 --- a/src/main.js +++ b/src/main.js @@ -149,7 +149,7 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}` FrankerFaceZ.Logger = Logger; const VER = FrankerFaceZ.version_info = { - major: 4, minor: 0, revision: 0, extra: '-rc18.2', + major: 4, minor: 0, revision: 0, extra: '-rc19', commit: __git_commit__, build: __webpack_hash__, toString: () => @@ -166,7 +166,9 @@ FrankerFaceZ.utilities = { logging: require('utilities/logging'), object: require('utilities/object'), time: require('utilities/time'), - tooltip: require('utilities/tooltip') + tooltip: require('utilities/tooltip'), + i18n: require('utilities/translation-core'), + dayjs: require('dayjs') } diff --git a/src/modules/chat/actions/components/edit-chat.vue b/src/modules/chat/actions/components/edit-chat.vue index 3ac31855..ad86997b 100644 --- a/src/modules/chat/actions/components/edit-chat.vue +++ b/src/modules/chat/actions/components/edit-chat.vue @@ -14,7 +14,7 @@ >
- {{ t('setting.actions.variables', 'Available Variables: %{vars}', {vars}) }} + {{ t('setting.actions.variables', 'Available Variables: {vars}', {vars}) }}
diff --git a/src/modules/chat/actions/components/edit-url.vue b/src/modules/chat/actions/components/edit-url.vue index b835c186..0618ea62 100644 --- a/src/modules/chat/actions/components/edit-url.vue +++ b/src/modules/chat/actions/components/edit-url.vue @@ -14,7 +14,7 @@ >
- {{ t('setting.actions.variables', 'Available Variables: %{vars}', {vars}) }} + {{ t('setting.actions.variables', 'Available Variables: {vars}', {vars}) }}
diff --git a/src/modules/chat/actions/index.jsx b/src/modules/chat/actions/index.jsx index 2a3c1544..cb903bf5 100644 --- a/src/modules/chat/actions/index.jsx +++ b/src/modules/chat/actions/index.jsx @@ -40,7 +40,7 @@ export default class Actions extends Module { always_inherit: true, ui: { - path: 'Chat > Actions > Reasons', + path: 'Chat > Actions > Reasons >> Custom Reasons', component: 'chat-reasons', } }); @@ -80,6 +80,15 @@ export default class Actions extends Module { } }); + this.settings.add('chat.actions.rules-as-reasons', { + default: true, + ui: { + path: 'Chat > Actions > Reasons >> Rules', + component: 'setting-check-box', + title: "Include the current room's rules in the list of reasons." + } + }); + this.settings.add('chat.actions.viewer-card', { // Filter out actions process: (ctx, val) => @@ -184,7 +193,7 @@ export default class Actions extends Module { renderInlineReasons(data, t, tip) { const reasons = this.parent.context.get('chat.actions.reasons'), reason_elements = [], - room = this.parent.getRoom(data.room.id, data.room.login, true), + room = this.parent.context.get('chat.actions.rules-as-reasons') && this.parent.getRoom(data.room.id, data.room.login, true), rules = room && room.rules; if ( ! reasons && ! rules ) { @@ -201,7 +210,7 @@ export default class Actions extends Module { if ( reasons && reasons.length ) { for(const reason of reasons) { - const text = this.replaceVariables(reason.i18n ? this.i18n.t(reason.i18n, reason.text) : reason.text, data); + const text = this.replaceVariables((typeof reason.i18n === 'string') ? this.i18n.t(reason.i18n, reason.text) : reason.text, data); reason_elements.push(
  • import(/* webpackChunkName: 'main-menu' */ './components/edit-timeout.vue'), title: 'Timeout User', - description: '%{options.duration} second%{options.duration|en_plural}', + description: '{options.duration,number} second%{options.duration,en_plural}', reason_text(data) { return this.i18n.t('chat.actions.timeout-reason', - 'Timeout %{user.login} for %{duration} second%{duration|en_plural} for:', + 'Timeout {user.login} for {duration,number} second{duration,en_plural} for:', { user: data.user, duration: data.options.duration @@ -198,7 +198,7 @@ export const timeout = { tooltip(data) { return this.i18n.t( 'chat.actions.timeout', - 'Timeout %{user.login} for %{duration} second%{duration|en_plural}', + 'Timeout {user.login} for {duration,number} second{duration,en_plural}', { user: data.user, duration: data.options.duration @@ -230,7 +230,7 @@ export const unban = { title: 'Unban User', tooltip(data) { - return this.i18n.t('chat.actions.unban', 'Unban %{user.login}', {user: data.user}); + return this.i18n.t('chat.actions.unban', 'Unban {user.login}', {user: data.user}); }, click(event, data) { @@ -257,7 +257,7 @@ export const untimeout = { title: 'Untimeout User', tooltip(data) { - return this.i18n.t('chat.actions.untimeout', 'Untimeout %{user.login}', {user: data.user}); + return this.i18n.t('chat.actions.untimeout', 'Untimeout {user.login}', {user: data.user}); }, click(event, data) { @@ -283,7 +283,7 @@ export const whisper = { title: 'Whisper User', tooltip(data) { - return this.i18n.t('chat.actions.whisper', 'Whisper %{user.login}', data); + return this.i18n.t('chat.actions.whisper', 'Whisper {user.login}', data); }, click(event, data) { @@ -326,7 +326,7 @@ export const gift_sub = { title: 'Gift Subscription', tooltip(data) { - return this.i18n.t('chat.actions.gift_sub', 'Gift a Sub to %{user.login}', data); + return this.i18n.t('chat.actions.gift_sub', 'Gift a Sub to {user.login}', data); }, context() { diff --git a/src/modules/chat/badges.jsx b/src/modules/chat/badges.jsx index c2df36bd..79763b6a 100644 --- a/src/modules/chat/badges.jsx +++ b/src/modules/chat/badges.jsx @@ -284,7 +284,7 @@ export default class Badges extends Module { let title = bd.title || global_badge.title; if ( d.data ) { if ( d.badge === 'subscriber' ) { - title = this.i18n.t('badges.subscriber.months', '%{title} (%{count} Month%{count|en_plural})', { + title = this.i18n.t('badges.subscriber.months', '{title} ({count,number} Month{count,en_plural})', { title, count: d.data }); @@ -645,16 +645,23 @@ export default class Badges extends Module { return b; } + hasTwitchBadges() { + return !! this.twitch_badges + } + updateTwitchBadges(badges) { - if ( ! badges ) + if ( ! Array.isArray(badges) ) this.twitch_badges = badges; else { - const b = {}; - for(const data of badges) { - const sid = data.setID, - bs = b[sid] = b[sid] || {__game: /_\d+$/.test(sid)}; + let b = null; + if ( badges.length ) { + b = {}; + for(const data of badges) { + const sid = data.setID, + bs = b[sid] = b[sid] || {__game: /_\d+$/.test(sid)}; - bs[data.version] = data; + bs[data.version] = data; + } } this.twitch_badges = b; diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index c6dd28db..382996d2 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -1168,7 +1168,7 @@ export default class Chat extends Module { l = parts.length, emotes = {}; - let idx = 0, ret, last_type = null; + let idx = 0, ret, last_type = null, bits = 0; for(let i=0; i < l; i++) { const part = parts[i], @@ -1186,10 +1186,11 @@ export default class Chat extends Module { else if ( content.url ) ret = content.url; - else if ( content.cheerAmount ) + else if ( content.cheerAmount ) { + bits += content.cheerAmount; ret = `${content.alt}${content.cheerAmount}`; - else if ( content.images ) { + } else if ( content.images ) { const url = (content.images.themed ? content.images.dark : content.images.sources), match = url && /\/emoticons\/v1\/(\d+)\/[\d.]+$/.exec(url['1x']), id = match && match[1]; @@ -1219,6 +1220,7 @@ export default class Chat extends Module { if ( ! emotes_only ) msg.message = out.join(''); + msg.bits = bits; msg.ffz_emotes = emotes; return msg; } @@ -1228,8 +1230,15 @@ export default class Chat extends Module { if (!( time instanceof Date )) time = new Date(time); - const fmt = this.context.get('chat.timestamp-format'); - return dayjs(time).locale(this.i18n.locale).format(fmt); + const fmt = this.context.get('chat.timestamp-format'), + d = dayjs(time); + + try { + return d.locale(this.i18n.locale).format(fmt); + } catch(err) { + // If the locale isn't loaded, this can fail. + return d.format(fmt); + } } diff --git a/src/modules/chat/rich_providers.js b/src/modules/chat/rich_providers.js index 72f2f00f..193e1408 100644 --- a/src/modules/chat/rich_providers.js +++ b/src/modules/chat/rich_providers.js @@ -106,25 +106,25 @@ export const Clips = { let desc_1; if ( game_name === 'creative' ) - desc_1 = this.i18n.t('clip.desc.1.creative', '%{user} being Creative', { + desc_1 = this.i18n.t('clip.desc.1.creative', '{user} being Creative', { user }); else if ( game ) - desc_1 = this.i18n.t('clip.desc.1.playing', '%{user} playing %{game}', { + desc_1 = this.i18n.t('clip.desc.1.playing', '{user} playing {game}', { user, game: game_display }); else - desc_1 = this.i18n.t('clip.desc.1', 'Clip of %{user}', {user}); + desc_1 = this.i18n.t('clip.desc.1', 'Clip of {user}', {user}); return { url: token.url, image: clip.thumbnailURL, title: clip.title, desc_1, - desc_2: this.i18n.t('clip.desc.2', 'Clipped by %{curator} — %{views|number} View%{views|en_plural}', { + desc_2: this.i18n.t('clip.desc.2', 'Clipped by {curator} — {views,number} View{views,en_plural}', { curator: clip.curator ? clip.curator.displayName : this.i18n.t('clip.unknown', 'Unknown'), views: clip.viewCount }) @@ -170,25 +170,25 @@ export const Videos = { let desc_1; if ( game_name === 'creative' ) - desc_1 = this.i18n.t('clip.desc.1.creative', '%{user} being Creative', { + desc_1 = this.i18n.t('clip.desc.1.creative', '{user} being Creative', { user }); else if ( game ) - desc_1 = this.i18n.t('clip.desc.1.playing', '%{user} playing %{game}', { + desc_1 = this.i18n.t('clip.desc.1.playing', '{user} playing %{game}', { user, game: game_display }); else - desc_1 = this.i18n.t('video.desc.1', 'Video of %{user}', {user}); + desc_1 = this.i18n.t('video.desc.1', 'Video of {user}', {user}); return { url: token.url, image: video.previewThumbnailURL, title: video.title, desc_1, - desc_2: this.i18n.t('video.desc.2', '%{length} — %{views} Views - %{date}', { + desc_2: this.i18n.t('video.desc.2', '{length,duration} — {views,number} Views - {date}', { length: video.lengthSeconds, views: video.viewCount, date: video.publishedAt diff --git a/src/modules/chat/room.js b/src/modules/chat/room.js index e0330ffa..a963734f 100644 --- a/src/modules/chat/room.js +++ b/src/modules/chat/room.js @@ -380,16 +380,23 @@ export default class Room { // Badge Data // ======================================================================== + hasBadges() { + return !! this.badges + } + updateBadges(badges) { - if ( ! badges ) + if ( ! Array.isArray(badges) ) this.badges = badges; else { - const b = {}; - for(const data of badges) { - const sid = data.setID, - bs = b[sid] = b[sid] || {}; + let b = null; + if ( badges.length ) { + b = {}; + for(const data of badges) { + const sid = data.setID, + bs = b[sid] = b[sid] || {}; - bs[data.version] = data; + bs[data.version] = data; + } } this.badges = b; diff --git a/src/modules/chat/tokenizers.jsx b/src/modules/chat/tokenizers.jsx index 120b58da..73255e88 100644 --- a/src/modules/chat/tokenizers.jsx +++ b/src/modules/chat/tokenizers.jsx @@ -44,7 +44,7 @@ export const Links = { return ''; if ( target.dataset.isMail === 'true' ) - return [this.i18n.t('tooltip.email-link', 'E-Mail %{address}', {address: target.textContent})]; + return [this.i18n.t('tooltip.email-link', 'E-Mail {address}', {address: target.textContent})]; return this.get_link_info(target.dataset.url).then(data => { if ( ! data || (data.v || 1) > TOOLTIP_VERSION ) @@ -58,7 +58,7 @@ export const Links = { content += (content.length ? '
    ' : '') + sanitize(this.i18n.t( 'tooltip.link-destination', - 'Destination: %{url}', + 'Destination: {url}', {url: data.urls[data.urls.length-1][1]} )); @@ -66,7 +66,7 @@ export const Links = { const reasons = Array.from(new Set(data.urls.map(x => x[2]).filter(x => x))).join(', '); content = this.i18n.t( 'tooltip.link-unsafe', - "Caution: This URL is on Google's Safe Browsing List for: %{reasons}", + "Caution: This URL is on Google's Safe Browsing List for: {reasons}", {reasons: sanitize(reasons.toLowerCase())} ) + (content.length ? `
    ${content}` : ''); } @@ -96,7 +96,7 @@ export const Links = { return content; }).catch(error => - sanitize(this.i18n.t('tooltip.error', 'An error occurred. (%{error})', {error})) + sanitize(this.i18n.t('tooltip.error', 'An error occurred. ({error})', {error})) ); }, @@ -736,7 +736,7 @@ export const CheerEmotes = { data-prefix={prefix} data-tier={tier} />), - this.i18n.t('tooltip.bits', '%{count|number} Bits', amount), + this.i18n.t('tooltip.bits', '{count,number} Bits', amount), ]; if ( length > 1 ) { @@ -755,7 +755,7 @@ export const CheerEmotes = { if ( length > 12 ) { out.push(
    ); - out.push(this.i18n.t('tooltip.bits.more', '(and %{count} more)', length-12)); + out.push(this.i18n.t('tooltip.bits.more', '(and {count} more)', length-12)); } } @@ -1008,7 +1008,7 @@ export const AddonEmotes = { source = this.i18n.t('emote.prime', 'Twitch Prime'); else - source = this.i18n.t('tooltip.channel', 'Channel: %{source}', {source}); + source = this.i18n.t('tooltip.channel', 'Channel: {source}', {source}); } } else if ( provider === 'ffz' ) { @@ -1025,7 +1025,7 @@ export const AddonEmotes = { if ( emote.owner ) owner = this.i18n.t( - 'emote.owner', 'By: %{owner}', + 'emote.owner', 'By: {owner}', {owner: emote.owner.display_name}); if ( emote.urls[4] ) @@ -1052,7 +1052,7 @@ export const AddonEmotes = { plain_name = true; name = `:${emoji.names[0]}:${vcode ? `:${vcode.names[0]}:` : ''}`; - source = this.i18n.t('tooltip.emoji', 'Emoji - %{category}', emoji); + source = this.i18n.t('tooltip.emoji', 'Emoji - {category}', emoji); } else return; @@ -1069,7 +1069,7 @@ export const AddonEmotes = { onLoad={tip.update} />) : preview), - plain_name || (hide_source && ! owner) ? name : this.i18n.t('tooltip.emote', 'Emote: %{name}', {name}), + plain_name || (hide_source && ! owner) ? name : this.i18n.t('tooltip.emote', 'Emote: {name}', {name}), ! hide_source && source && this.context.get('tooltip.emote-sources') && (
    {source} diff --git a/src/modules/main_menu/components/action-editor.vue b/src/modules/main_menu/components/action-editor.vue index f5ce8546..9e8079e4 100644 --- a/src/modules/main_menu/components/action-editor.vue +++ b/src/modules/main_menu/components/action-editor.vue @@ -9,7 +9,7 @@

    {{ title }}

    {{ description }}
    - {{ t('setting.actions.visible', 'visible: %{list}', {list: visibility}) }} + {{ t('setting.actions.visible', 'visible: {list}', {list: visibility}) }}