diff --git a/package.json b/package.json
index 78670bdf..a76fc294 100755
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
- "version": "4.12.6",
+ "version": "4.13.0",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"license": "Apache-2.0",
"scripts": {
diff --git a/src/i18n.js b/src/i18n.js
index 27477e56..04459f21 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -75,14 +75,14 @@ export class TranslationManager extends Module {
this._seen = new Set;
- this.availableLocales = ['en']; //, 'de', 'ja'];
+ this.availableLocales = ['en'];
this.localeData = {
- en: { name: 'English' }/*,
- de: { name: 'Deutsch' },
- ja: { name: '日本語' }*/
+ en: { name: 'English' }
}
+ this.loadLocales();
+
this.capturing = false;
this.captured = new Map;
@@ -140,28 +140,59 @@ export class TranslationManager extends Module {
this.settings.add('i18n.locale', {
default: -1,
process: (ctx, val) => {
- if ( val === -1 )
- val = ctx.get('context.session.languageCode');
+ if ( val === -1 || typeof val !== 'string' )
+ val = ctx.get('context.session.languageCode') || 'en';
+ if ( this.availableLocales.includes(val) )
+ return val;
+
+ const idx = val.indexOf('-');
+ if ( idx === -1 )
+ return 'en';
+
+ val = val.slice(0, idx);
return this.availableLocales.includes(val) ? val : 'en'
},
- _ui: {
+ ui: {
path: 'Appearance > Localization >> General',
title: 'Language',
- // description: '',
+ description: `FrankerFaceZ is lovingly translated by volunteers from our community. Thank you. If you're interested in helping to translate FrankerFaceZ, please [join our Discord](https://discord.gg/UrAkGhT) and ask about localization.`,
component: 'setting-select-box',
- data: (profile, val) => [{
- selected: val === -1,
- value: -1,
- i18n_key: 'setting.appearance.localization.general.language.twitch',
- title: "Use Twitch's Language"
- }].concat(this.availableLocales.map(l => ({
- selected: val === l,
- value: l,
- title: this.localeData[l].name
- })))
+ data: (profile, val) => {
+ const out = this.availableLocales.map(l => {
+ const data = this.localeData[l];
+ let title = data?.native_name;
+ if ( ! title )
+ title = data?.name || l;
+
+ if ( data?.coverage != null && data?.coverage < 100 )
+ title = this.t('i18n.locale-coverage', '{name} ({coverage,number,percent} Complete)', {
+ name: title,
+ coverage: data.coverage / 100
+ });
+
+ return {
+ selected: val === l,
+ value: l,
+ title
+ };
+ });
+
+ out.sort((a, b) => {
+ return a.title.localeCompare(b.title)
+ });
+
+ out.unshift({
+ selected: val === -1,
+ value: -1,
+ i18n_key: 'setting.appearance.localization.general.language.twitch',
+ title: "Use Twitch's Language"
+ });
+
+ return out;
+ }
},
changed: val => this.locale = val
@@ -361,122 +392,40 @@ export class TranslationManager extends Module {
}
+ async loadLocales() {
+ const resp = await fetch(`https://api-test.frankerfacez.com/v2/i18n/locales`);
+ if ( ! resp.ok ) {
+ this.log.warn(`Error Populating Locales -- Status: ${resp.status}`);
+ throw new Error(`http error ${resp.status} loading locales`)
+ }
+
+ let data = await resp.json();
+ if ( ! Array.isArray(data) || ! data.length )
+ data = [{
+ id: 'en',
+ name: 'English',
+ coverage: 100,
+ rtl: false
+ }];
+
+ this.localeData = {};
+ this.availableLocales = [];
+
+ for(const locale of data) {
+ const key = locale.id.toLowerCase();
+ this.localeData[key] = locale;
+ this.availableLocales.push(key);
+ }
+
+ this.emit(':locales-loaded');
+ }
+
+
async loadLocale(locale) {
if ( locale === 'en' )
return {};
- /*if ( locale === 'de' )
- return {
- site: {
- menu_button: 'FrankerFaceZ Leitstelle'
- },
-
- player: {
- reset_button: 'Doppelklicken, um den Player zurückzusetzen'
- },
-
- setting: {
- reset: 'Zurücksetzen',
-
- appearance: {
- _: 'Aussehen',
- description: 'Personalisieren Sie das Aussehen von Twitch. Ändern Sie das Farbschema und die Schriften und stimmen Sie das Layout so ab, dass Sie ein optimales Erlebnis erleben.
(Yes, this is Google Translate still.)',
- localization: {
- _: 'Lokalisierung',
-
- general: {
- language: {
- _: 'Sprache',
- twitch: "Verwenden Sie Twitch's Sprache"
- }
- },
-
-
- dates_and_times: {
- _: 'Termine und Zeiten',
- allow_relative_times: {
- _: 'Relative Zeiten zulassen',
- description: 'Wenn dies aktiviert ist, zeigt FrankerFaceZ einige Male in einem relativen Format an.
Beispiel: vor 3 Stunden'
- }
- }
-
-
- },
- layout: 'Layout',
- theme: 'Thema'
- },
-
- profiles: {
- _: 'Profile',
-
- active: 'Dieses Profil ist aktiv.',
- inactive: {
- _: 'Dieses Profil ist nicht aktiv.',
- description: 'Dieses Profil stimmt nicht mit dem aktuellen Kontext überein und ist momentan nicht aktiv, so dass Sie keine Änderungen sehen, die Sie hier bei Twitch vorgenommen haben.'
- },
-
- configure: 'Konfigurieren',
-
- default: {
- _: 'Standard Profil',
- description: 'Einstellungen, die überall auf Twitch angewendet werden.'
- },
-
- moderation: {
- _: 'Mäßigung',
- description: 'Einstellungen, die gelten, wenn Sie ein Moderator des aktuellen Kanals sind.'
- }
- },
-
- add_ons: {
- _: 'Erweiterung'
- },
-
- 'inherited-from': 'Vererbt von: {title}',
- 'overridden-by': 'Überschrieben von: {title}'
- },
-
- 'main-menu': {
- search: 'Sucheinstellungen',
-
- about: {
- _: 'Über',
- news: 'Nachrichten',
- support: 'Unterstützung'
- }
- }
- }
-
- if ( locale === 'ja' )
- return {
- greeting: 'こんにちは',
-
- site: {
- menu_button: 'FrankerFaceZコントロールセンター'
- },
-
- setting: {
- appearance: {
- _: '外観',
- localization: '局地化',
- layout: '設計',
- theme: '題材'
- }
- },
-
- 'main-menu': {
- search: '検索設定',
- version: 'バージョン{version}',
-
- about: {
- _: '約',
- news: '便り',
- support: '対応'
- }
- }
- }*/
-
- const resp = await fetch(`${SERVER}/script/i18n/${locale}.json`);
+ const resp = await fetch(`https://api-test.frankerfacez.com/v2/i18n/locale/${locale}`);
if ( ! resp.ok ) {
if ( resp.status === 404 ) {
this.log.info(`Cannot Load Locale: ${locale}`);
@@ -487,7 +436,8 @@ export class TranslationManager extends Module {
throw new Error(`http error ${resp.status} loading phrases`);
}
- return resp.json();
+ const data = await resp.json();
+ return data?.phrases;
}
async setLocale(new_locale) {
@@ -510,7 +460,8 @@ export class TranslationManager extends Module {
return [];
}
- const phrases = await this.loadLocale(new_locale);
+ const data = this.localeData[new_locale];
+ const phrases = await this.loadLocale(data?.id || new_locale);
if ( this._.locale !== new_locale )
throw new Error('locale has changed since we started loading');
diff --git a/src/sites/twitch-twilight/modules/channel.js b/src/sites/twitch-twilight/modules/channel.js
index e7b36a4d..aaa03024 100644
--- a/src/sites/twitch-twilight/modules/channel.js
+++ b/src/sites/twitch-twilight/modules/channel.js
@@ -53,7 +53,7 @@ export default class Channel extends Module {
this.ChannelPage = this.fine.define(
'channel-page',
- n => (n.updateRoute && n.updateChannel && n.state && has(n.state, 'hostedChannel')) || (n.getHostedChannelLogin && n.handleHostingChange) || (n.onChatHostingChange && n.state && has(n.state, 'hostMode')),
+ n => (n.updateHost && n.updateChannel && n.state && has(n.state, 'hostedChannel')) || (n.getHostedChannelLogin && n.handleHostingChange) || (n.onChatHostingChange && n.state && has(n.state, 'hostMode')),
['user', 'video', 'user-video', 'user-clip', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following']
);
diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss
index 8c5fc3c3..d572edea 100644
--- a/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss
+++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss
@@ -15,5 +15,6 @@
}
.channel-root__scroll-area--theatre-mode:hover .channel-info-bar {
+ background-color: var(--color-background-base);
opacity: 0.75;
}
\ No newline at end of file
diff --git a/src/sites/twitch-twilight/modules/player.jsx b/src/sites/twitch-twilight/modules/player.jsx
index e7f534e8..ba39da04 100644
--- a/src/sites/twitch-twilight/modules/player.jsx
+++ b/src/sites/twitch-twilight/modules/player.jsx
@@ -30,6 +30,8 @@ export default class Player extends Module {
PLAYER_ROUTES
);
+
+ // TODO: Better way to reposition player on demand.
this.PersistentPlayer = this.fine.define(
'twitch-player-persistent',
n => n.renderMiniHoverControls && n.togglePause,
diff --git a/src/utilities/translation-core.js b/src/utilities/translation-core.js
index 18b8b128..b0accd10 100644
--- a/src/utilities/translation-core.js
+++ b/src/utilities/translation-core.js
@@ -488,6 +488,7 @@ function listToString(list) {
const CARDINAL_TO_LANG = {
arabic: ['ar'],
+ czech: ['cs'],
danish: ['da'],
german: ['de', 'el', 'en', 'es', 'fi', 'hu', 'it', 'nl', 'no', 'nb', 'tr', 'sv'],
hebrew: ['he'],
@@ -508,6 +509,13 @@ const CARDINAL_TYPES = {
return n1 >= 11 ? 4 : 5;
},
+ czech: (n,i,v) => {
+ if ( v !== 0 ) return 4;
+ if ( i === 1 ) return 1;
+ if ( i >= 2 && i <= 4 ) return 3;
+ return 5;
+ },
+
danish: (n,i,v,t) => (n === 1 || (t !== 0 && (i === 0 || i === 1))) ? 1 : 5,
french: (n, i) => (i === 0 || i === 1) ? 1 : 5,
german: n => n === 1 ? 1 : 5,