diff --git a/fontello.config.json b/fontello.config.json index 4bfe01e2..28cc0f55 100644 --- a/fontello.config.json +++ b/fontello.config.json @@ -779,6 +779,12 @@ "css": "right-open", "code": 59462, "src": "fontawesome" + }, + { + "uid": "a2a74f5e7b7d9ba054897d8c795a326a", + "css": "list-bullet", + "code": 61642, + "src": "fontawesome" } ] } \ No newline at end of file diff --git a/package.json b/package.json index be8aa81f..d5b2b4e3 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.30.0", + "version": "4.30.1", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", diff --git a/res/font/ffz-fontello.eot b/res/font/ffz-fontello.eot index a3d19d54..b84fe1d0 100644 Binary files a/res/font/ffz-fontello.eot and b/res/font/ffz-fontello.eot differ diff --git a/res/font/ffz-fontello.svg b/res/font/ffz-fontello.svg index f7569219..029fe910 100644 --- a/res/font/ffz-fontello.svg +++ b/res/font/ffz-fontello.svg @@ -158,6 +158,8 @@ + + @@ -209,4 +211,4 @@ - \ No newline at end of file + diff --git a/res/font/ffz-fontello.ttf b/res/font/ffz-fontello.ttf index c5dedefc..b65ee43f 100644 Binary files a/res/font/ffz-fontello.ttf and b/res/font/ffz-fontello.ttf differ diff --git a/res/font/ffz-fontello.woff b/res/font/ffz-fontello.woff index 7029d051..d3da1da1 100644 Binary files a/res/font/ffz-fontello.woff and b/res/font/ffz-fontello.woff differ diff --git a/res/font/ffz-fontello.woff2 b/res/font/ffz-fontello.woff2 index 14edc0ea..91a234aa 100644 Binary files a/res/font/ffz-fontello.woff2 and b/res/font/ffz-fontello.woff2 differ diff --git a/src/addons.js b/src/addons.js index 08b88c95..cb5b8002 100644 --- a/src/addons.js +++ b/src/addons.js @@ -294,6 +294,8 @@ export default class AddonManager extends Module { if ( ! addon ) throw new Error(`Unknown add-on id: ${id}`); + await this.i18n.loadChunk(`addon.${id}`); + let module = this.resolve(`addon.${id}`); if ( module ) { if ( ! module.loaded ) diff --git a/src/i18n.js b/src/i18n.js index cf3af7ff..d5ac48fe 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -6,12 +6,15 @@ import Parser from '@ffz/icu-msgparser'; -import {DEBUG} from 'utilities/constants'; +import {DEBUG, SERVER} from 'utilities/constants'; import {get, pick_random, shallow_copy, deep_copy} from 'utilities/object'; +import { getBuster } from 'utilities/time'; import Module from 'utilities/module'; import NewTransCore from 'utilities/translation-core'; +const fetchJSON = (url, options) => fetch(url, options).then(r => r.ok ? r.json() : null).catch(() => null); + const API_SERVER = 'https://api-test.frankerfacez.com'; const STACK_SPLITTER = /\s*at\s+(.+?)\s+\((.+)\)$/, @@ -78,6 +81,7 @@ export class TranslationManager extends Module { this._seen = new Set; this.availableLocales = ['en']; + this.chunks = ['client']; this.localeData = { en: { name: 'English' } @@ -350,6 +354,25 @@ export class TranslationManager extends Module { this.locale = this.settings.get('i18n.locale'); } + async loadChunk(name) { + if (this.chunks.includes(name)) + return []; + + this.chunks.push(name); + + const locale = this._.locale; + const phrases = await this.loadLocale(locale, name); + + const added = this._.extend(phrases); + if ( added.length ) { + this.log.info(`Loaded Chunk: ${name} -- Phrases: ${added.length}`); + this.emit(':loaded', added); + this.emit(':update'); + } + + return added; + } + broadcast(msg) { if ( this._broadcaster ) this._broadcaster.postMessage(msg); @@ -448,32 +471,13 @@ export class TranslationManager extends Module { this.strings_loading = true; - const loadPage = async page => { - const resp = await fetch(`${API_SERVER}/v2/i18n/strings?page=${page}`); - if ( ! resp.ok ) { - this.log.warn(`Error Loading Strings -- Status: ${resp.status}`); - return { - next: false, - strings: [] - }; - } - - const data = await resp.json(); - return { - next: data?.pages > page, - strings: data?.strings || [] - } - } - - let page = 1; - let next = true; - let strings = []; - - while(next) { - const data = await loadPage(page++); // eslint-disable-line no-await-in-loop - strings = strings.concat(data.strings); - next = data.next; - } + const resp = await fetch(`${SERVER}/script/locale/strings.json?_=${getBuster(30)}`); + let strings; + if (! resp.ok ) { + this.log.warn(`Error Loading Strings -- Status: ${resp.status}`); + strings = []; + } else + strings = await resp.json(); for(const str of strings) { const key = str.id; @@ -629,19 +633,20 @@ export class TranslationManager extends Module { async loadLocales() { - const resp = await fetch(`${API_SERVER}/v2/i18n/locales`); + const resp = await fetch(`${SERVER}/script/locale/locales.json?_=${getBuster(30)}`); + let data; if ( ! resp.ok ) { this.log.warn(`Error Populating Locales -- Status: ${resp.status}`); - throw new Error(`http error ${resp.status} loading locales`) - } + } else + data = await resp.json(); - let data = await resp.json(); if ( ! Array.isArray(data) || ! data.length ) data = [{ id: 'en', name: 'English', coverage: 100, - rtl: false + rtl: false, + hashes: {} }]; this.localeData = {}; @@ -657,11 +662,46 @@ export class TranslationManager extends Module { } - async loadLocale(locale) { + async loadLocale(locale, chunk = null) { if ( locale === 'en' ) return {}; - const resp = await fetch(`${API_SERVER}/v2/i18n/locale/${locale}`); + const hashes = this.localeData[locale]?.hashes; + if (! hashes) { + this.log.info(`Cannot Load Locale: ${locale}`); + return {}; + } + + if (! chunk) + chunk = this.chunks; + else if (! Array.isArray(chunk)) + chunk = [chunk]; + + const id = this.localeData[locale].id; + const promises = []; + + for(const chnk of chunk) { + const hash = hashes[chnk]; + if (! hash) + continue; + + promises.push(fetchJSON(`https://cdn.frankerfacez.com/static/locale/${id}/${chnk}.${hash}.json`)); + } + + const chunks = await Promise.all(promises); + const result = {}; + + for(const chunk of chunks) { + if (! chunk) + continue; + + for(const [key,val] of Object.entries(chunk)) + result[key] = val; + } + + return result; + + /*const resp = await fetch(`${API_SERVER}/v2/i18n/locale/${locale}`); if ( ! resp.ok ) { if ( resp.status === 404 ) { this.log.info(`Cannot Load Locale: ${locale}`); @@ -673,7 +713,7 @@ export class TranslationManager extends Module { } const data = await resp.json(); - return data?.phrases; + return data?.phrases;*/ } async setLocale(new_locale) { @@ -713,7 +753,7 @@ export class TranslationManager extends Module { } async loadDayjsLocale(locale) { - if ( locale === 'en' ) + if ( locale === 'en' || locale === 'en-arrr' ) return; try { diff --git a/src/modules/chat/actions/types.jsx b/src/modules/chat/actions/types.jsx index c36a17a4..4de79288 100644 --- a/src/modules/chat/actions/types.jsx +++ b/src/modules/chat/actions/types.jsx @@ -276,11 +276,11 @@ export const timeout = { editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-timeout.vue'), title: 'Timeout User', - description: '{options.duration,number} second{options.duration,en_plural}', + description: '{options.duration, plural, one {# second} other {# seconds}}', reason_text(data) { return this.i18n.t('chat.actions.timeout-reason', - 'Timeout {user.login} for {duration,number} second{duration,en_plural} for:', + 'Timeout {user.login} for {duration, plural, one {# second} other {# seconds}} for:', { user: data.user, duration: data.options.duration @@ -291,7 +291,7 @@ export const timeout = { tooltip(data) { return this.i18n.t( 'chat.actions.timeout.tooltip', - 'Timeout {user.login} for {duration,number} second{duration,en_plural}', + 'Timeout {user.login} for {duration, plural, one {# second} other {# seconds}}', { user: data.user, duration: data.options.duration diff --git a/src/modules/chat/badges.jsx b/src/modules/chat/badges.jsx index 53397bb5..4d0c5dd2 100644 --- a/src/modules/chat/badges.jsx +++ b/src/modules/chat/badges.jsx @@ -466,18 +466,18 @@ export default class Badges extends Module { if ( d.data ) { if ( d.badge === 'subscriber' ) { if ( tier > 0 ) - title = this.i18n.t('badges.subscriber.tier-months', '{title}\n(Tier {tier}, {months,number} Month{months,en_plural})', { + title = this.i18n.t('badges.subscriber.tier-months', '{title}\n(Tier {tier}, {months, plural, one {# Month} other {# Months}})', { title, tier, months: d.data }); else - title = this.i18n.t('badges.subscriber.months', '{title}\n({count,number} Month{count,en_plural})', { + title = this.i18n.t('badges.subscriber.months', '{title}\n({count, plural, one {# Month} other {# Months}})', { title, count: d.data }); } else if ( d.badge === 'founder' ) { - title = this.i18n.t('badges.founder.months', '{title}\n(Subscribed for {count,number} Month{count,en_plural})', { + title = this.i18n.t('badges.founder.months', '{title}\n(Subscribed for {count, plural, one {# Month} other {# Months}})', { title, count: d.data }); diff --git a/src/modules/chat/rich_providers.js b/src/modules/chat/rich_providers.js index 4a5e5f9e..da1f2590 100644 --- a/src/modules/chat/rich_providers.js +++ b/src/modules/chat/rich_providers.js @@ -251,7 +251,7 @@ export const Clips = { const extra = { type: 'i18n', key: 'clip.desc.2', - phrase: 'Clipped by {curator} — {views,number} View{views,en_plural}', + phrase: 'Clipped by {curator} — {views, plural, one {# View} other {# Views}}', content: { curator, views: clip.viewCount diff --git a/src/modules/main_menu/components/profile-manager.vue b/src/modules/main_menu/components/profile-manager.vue index aa5e624b..cbf9fdc4 100644 --- a/src/modules/main_menu/components/profile-manager.vue +++ b/src/modules/main_menu/components/profile-manager.vue @@ -556,7 +556,7 @@ export default { this.resetImport(); - this.import_message = this.t('setting.backup-restore.imported', 'The profile "{name}" has been successfully imported with {count,number} setting{count,en_plural}.', { + this.import_message = this.t('setting.backup-restore.imported', 'The profile "{name}" has been successfully imported with {count, plural, one {# setting} other {# settings}}.', { name: prof.i18n_key ? this.t(prof.i18n_key, prof.title) : prof.title, count: i }); diff --git a/src/modules/main_menu/components/setting-check-box.vue b/src/modules/main_menu/components/setting-check-box.vue index e007c0b6..d8fb7bcd 100644 --- a/src/modules/main_menu/components/setting-check-box.vue +++ b/src/modules/main_menu/components/setting-check-box.vue @@ -15,7 +15,7 @@ diff --git a/src/modules/main_menu/components/setting-color-box.vue b/src/modules/main_menu/components/setting-color-box.vue index ea54e403..51886f8a 100644 --- a/src/modules/main_menu/components/setting-color-box.vue +++ b/src/modules/main_menu/components/setting-color-box.vue @@ -5,7 +5,7 @@ >
diff --git a/src/modules/main_menu/components/setting-combo-box.vue b/src/modules/main_menu/components/setting-combo-box.vue index c2473df5..c3d7eec8 100644 --- a/src/modules/main_menu/components/setting-combo-box.vue +++ b/src/modules/main_menu/components/setting-combo-box.vue @@ -5,7 +5,7 @@ >
diff --git a/src/modules/main_menu/components/setting-hotkey.vue b/src/modules/main_menu/components/setting-hotkey.vue index 0c0c067b..f7ddd2fa 100644 --- a/src/modules/main_menu/components/setting-hotkey.vue +++ b/src/modules/main_menu/components/setting-hotkey.vue @@ -5,7 +5,7 @@ >
diff --git a/src/modules/main_menu/components/setting-radio-buttons.vue b/src/modules/main_menu/components/setting-radio-buttons.vue index fb8d8829..6203a94d 100644 --- a/src/modules/main_menu/components/setting-radio-buttons.vue +++ b/src/modules/main_menu/components/setting-radio-buttons.vue @@ -1,7 +1,7 @@