diff --git a/bun.lockb b/bun.lockb index 01b4ce6d..53c134af 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 0820ce52..8f2c0401 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "license": "BSD-3-Clause", "version": "7.1.1", "dependencies": { - "@eartharoid/i18n": "1.2.1", + "@eartharoid/i18n": "2.0.0-alpha.1", + "@eartharoid/vite-plugin-i18n": "^1.0.0-alpha.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@floating-ui/react-dom": "2.1.0", @@ -56,7 +57,8 @@ "stylelint-scss": "^6.3.1", "tailwindcss": "^3.4.3", "vite": "5.2.12", - "vite-plugin-progress": "^0.0.7" + "vite-plugin-progress": "^0.0.7", + "yaml": "^2.4.5" }, "scripts": { "dev": "vite", diff --git a/scripts/updatetranslations.js b/scripts/updatetranslations.js index e46a9079..fe6b32ac 100644 --- a/scripts/updatetranslations.js +++ b/scripts/updatetranslations.js @@ -28,7 +28,7 @@ if (!fs.existsSync(achievementsDir)) { } fs.readdirSync(localesDir).forEach((file) => { - if (file === 'en_GB.json') { + if (file === 'en-GB.json') { return; } @@ -36,7 +36,7 @@ fs.readdirSync(localesDir).forEach((file) => { return; } - const en = require(path.join(localesDir, 'en_GB.json')); + const en = require(path.join(localesDir, 'en-GB.json')); const newdata = merge(en, require(path.join(localesDir, file))); compareAndRemoveKeys(newdata, en); @@ -47,7 +47,7 @@ fs.readdirSync(localesDir).forEach((file) => { }); fs.readdirSync(achievementsDir).forEach((file) => { - if (file === 'en_GB.json') { + if (file === 'en-GB.json') { return; } @@ -55,9 +55,9 @@ fs.readdirSync(achievementsDir).forEach((file) => { return; } - const enGBFilePath = path.join(achievementsDir, 'en_GB.json'); + const enGBFilePath = path.join(achievementsDir, 'en-GB.json'); if (!fs.existsSync(enGBFilePath)) { - console.error(`File 'en_GB.json' does not exist in the directory '${achievementsDir}'`); + console.error(`File 'en-GB.json' does not exist in the directory '${achievementsDir}'`); return; } diff --git a/src/components/Form/Settings/Radio/Radio.jsx b/src/components/Form/Settings/Radio/Radio.jsx index 111885fe..99dfae8b 100644 --- a/src/components/Form/Settings/Radio/Radio.jsx +++ b/src/components/Form/Settings/Radio/Radio.jsx @@ -4,7 +4,6 @@ import { Radio as PureRadio, RadioGroup } from '@headlessui/react'; import { MdCheckCircle } from 'react-icons/md'; import EventBus from 'utils/eventbus'; -import { translations } from 'lib/translations'; function Radio(props) { const [value, setValue] = useState(localStorage.getItem(props.name)); @@ -17,7 +16,7 @@ function Radio(props) { if (props.name === 'language') { // old tab name if (localStorage.getItem('tabName') === variables.getMessage('tabname')) { - localStorage.setItem('tabName', translations[value.replace('-', '_')].tabname); + localStorage.setItem('tabName', translations[value].tabname); } } diff --git a/src/config/default.js b/src/config/default.js index e495ab18..edf8500b 100644 --- a/src/config/default.js +++ b/src/config/default.js @@ -1,6 +1,6 @@ const DefaultOptions = { installed: [], - language: 'en_GB', + language: 'en-GB', toastDisplayTime: 2500, fontstyle: 'normal', fontweight: 400, diff --git a/src/config/variables.js b/src/config/variables.js index b5c2d119..ffe435d7 100644 --- a/src/config/variables.js +++ b/src/config/variables.js @@ -3,7 +3,7 @@ import Stats from 'features/stats/api/stats'; const variables = { language: {}, - languagecode: '', + locale_id: '', stats: Stats, constants, }; diff --git a/src/features/marketplace/views/newItemPage.jsx b/src/features/marketplace/views/newItemPage.jsx index 904701cd..f1d747c6 100644 --- a/src/features/marketplace/views/newItemPage.jsx +++ b/src/features/marketplace/views/newItemPage.jsx @@ -25,7 +25,8 @@ const NewItemPage = () => { const { subTab } = useTab(); const controller = new AbortController(); const [count, setCount] = useState(5); - const [itemData, setItemData] = useState(null); + const [item, setItemData] = useState(null); + const [shareModal, setShareModal] = useState(false); async function getItemData() { let testType = 'quote_packs'; @@ -39,34 +40,10 @@ const NewItemPage = () => { const item = await response.json(); console.log(item); - // Extract all the information you need - const { - onCollection, - type, - display_name, - author, - description, - version, - icon_url, - data, - local, - slug, - quotes, - } = item; + const { data } = item; - // Return an object with all the information return { - onCollection, - type, - display_name, - author, - description, - version, - icon_url, data, - local, - slug, - quotes, }; } @@ -79,7 +56,7 @@ const NewItemPage = () => { fetchData(); }, []); - if (!itemData) { + if (!item) { return null; // or a loading spinner } @@ -94,7 +71,7 @@ const NewItemPage = () => { data, local, slug, - } = itemData; + } = item; const moreInfoItem = (icon, header, text) => (
@@ -134,95 +111,140 @@ const NewItemPage = () => { return null; }; - { - /*async function toggle(pageType, data) { - if (pageType === 'item') { - const toggleType = type === 'all' || type === 'collections' ? data.type : type; - const item = await ( - await fetch(`${variables.constants.API_URL}/marketplace/item/${toggleType}/${data.name}`, { - signal: controller.signal, - }) - ).json(); + const ItemDetails = () => { + return ( +
+ + {variables.getMessage('modals.main.marketplace.product.details')} + +
+ {item?.updated_at && + moreInfoItem( + , + variables.getMessage('modals.main.marketplace.product.updated_at'), + formattedDate, + )} + {item?.quotes && + moreInfoItem( + , + variables.getMessage('modals.main.marketplace.product.no_quotes'), + item.quotes.length, + )} + {item?.photos && + moreInfoItem( + , + variables.getMessage('modals.main.marketplace.product.no_images'), + item.photos.length, + )} + {item?.quotes && item?.language + ? moreInfoItem( + , + variables.getMessage('modals.main.settings.sections.language.title'), + languageNames.of(item.language), + ) + : null} + {/*{moreInfoItem( + , + variables.getMessage('modals.main.settings.sections.background.type.title'), + variables.getMessage( + 'modals.main.marketplace.' + this.getName(this.props.data.data.type), + ) || 'marketplace', + )}*/} +
+
+ ); + }; - if (controller.signal.aborted === true) { - return; - } - - // check if already installed - let addonInstalled = false; - let addonInstalledVersion; - const installed = JSON.parse(localStorage.getItem('installed')); - if (installed.some((item) => item.name === item.data.name)) { - addonInstalled = true; - for (let i = 0; i < installed.length; i++) { - if (installed[i].name === item.data.name) { - addonInstalledVersion = installed[i].version; - break; - } - } - } - - setItem({ - onCollection: data._onCollection, - type: item.data.type, - display_name: item.data.name, - author: item.data.author, - description: item.data.description, - version: item.data.version, - icon: item.data.screenshot_url, - data: item.data, - local: { - installed: addonInstalled, - version: addonInstalledVersion, - }, - slug: data.name, - }); - - setType('item'); - - variables.stats.postEvent('marketplace-item', `${item.display_name} viewed`); - } else if (pageType === 'collection') { - setDone(false); - setItem({}); - - const collection = await ( - await fetch(`${variables.constants.API_URL}/marketplace/collection/${data}`, { - signal: controller.signal, - }) - ).json(); - - setItems(collection.data.items); - setCollection({ - visible: true, - title: collection.data.display_name, - description: collection.data.description, - img: collection.data.img, - }); - - setType('collection'); - } else { - setItem({}); - setCollection({}); - setType('normal'); + const ItemShowcase = () => { + switch (item.type) { + case 'quotes': + return ( + <> + + + + + + + {item.quotes.slice(0, count).map((quote, index) => ( + + + + + ))} + +
{variables.getMessage('modals.main.settings.sections.quote.title')}{variables.getMessage('modals.main.settings.sections.quote.author')}
{quote.quote}{quote.author}
+
+ count === item.quotes.length}> + {count !== item.quotes.length + ? variables.getMessage('modals.main.marketplace.product.show_all') + : variables.getMessage('modals.main.marketplace.product.show_less')} + +
+ + ); + case 'settings': + return ( + <> + + + + + + + {Object.entries(this.props.data.data.settings) + .slice(0, this.state.count) + .map(([key, value]) => ( + + + + + ))} + +
{variables.getMessage('modals.main.marketplace.product.setting')}{variables.getMessage('modals.main.marketplace.product.value')}
{key}{value}
+
+ this.incrementCount('settings')}> + {this.state.count !== this.props.data.data.settings.length + ? variables.getMessage('modals.main.marketplace.product.show_all') + : variables.getMessage('modals.main.marketplace.product.show_less')} + +
+ + ); + default: + return null; } - }*/ + }; + + const locale = localStorage.getItem('language'); + const shortLocale = locale.includes('_') ? locale.split('_')[0] : locale; + let languageNames = new Intl.DisplayNames([shortLocale], { type: 'language' }); + + let dateObj, formattedDate; + if (item.updated_at) { + dateObj = new Date(item.updated_at); + formattedDate = new Intl.DateTimeFormat(shortLocale, { + year: 'numeric', + month: 'long', + day: '2-digit', + }).format(dateObj); } return ( <> - {/* this.setState({ shareModal: false })} + onRequestClose={() => setShareModal(false)} > this.setState({ shareModal: false })} + modalClose={() => setShareModal(false)} /> - */} + {/*
{ {moreInfoItem( , variables.getMessage('modals.main.marketplace.product.created_by'), - itemData.author, + item.author, )} {itemWarning()}
@@ -271,105 +293,15 @@ const NewItemPage = () => { {variables.getMessage('modals.main.marketplace.product.description')} - {itemData.description} - - {itemData.quotes && itemData.quotes.length > 0 ? itemData.quotes[0].quote : 'Loading...'} - {itemData.type === 'quotes' && ( - <> - - - - - - - {itemData.quotes.slice(0, count).map((quote, index) => ( - - - - - ))} - -
{variables.getMessage('modals.main.settings.sections.quote.title')}{variables.getMessage('modals.main.settings.sections.quote.author')}
{quote.quote}{quote.author}
-
- count === itemData.quotes.length}> - {count !== itemData.quotes.length - ? variables.getMessage('modals.main.marketplace.product.show_all') - : variables.getMessage('modals.main.marketplace.product.show_less')} - -
- - )} - {/*{this.props.data.data.settings && ( - <> - - - - - - - {Object.entries(this.props.data.data.settings) - .slice(0, this.state.count) - .map(([key, value]) => ( - - - - - ))} - -
{variables.getMessage('modals.main.marketplace.product.setting')}{variables.getMessage('modals.main.marketplace.product.value')}
{key}{value}
-
- this.incrementCount('settings')}> - {this.state.count !== this.props.data.data.settings.length - ? variables.getMessage('modals.main.marketplace.product.show_all') - : variables.getMessage('modals.main.marketplace.product.show_less')} - -
- - )}*/} -
- - {variables.getMessage('modals.main.marketplace.product.details')} - -
- {/*{this.props.data.data.updated_at && - moreInfoItem( - , - variables.getMessage('modals.main.marketplace.product.updated_at'), - formattedDate, - )}*/} - {itemData?.quotes && - moreInfoItem( - , - variables.getMessage('modals.main.marketplace.product.no_itemData.quotes'), - itemData.quotes.length, - )} - {itemData?.photos && - moreInfoItem( - , - variables.getMessage('modals.main.marketplace.product.no_images'), - itemData.photos.length, - )} - {/*{itemData.itemData.quotes && itemData.language - ? moreInfoItem( - , - variables.getMessage('modals.main.settings.sections.language.title'), - languageNames.of(this.props.data.data.language), - ) - : null} - {moreInfoItem( - , - variables.getMessage('modals.main.settings.sections.background.type.title'), - variables.getMessage( - 'modals.main.marketplace.' + this.getName(this.props.data.data.type), - ) || 'marketplace', - )}*/} -
+ {item.description}
+ +
@@ -377,7 +309,7 @@ const NewItemPage = () => { className="icon" alt="icon" draggable={false} - src={itemData.icon_url} + src={item.icon_url} onError={(e) => { e.target.onerror = null; e.target.src = placeholderIcon; @@ -397,11 +329,11 @@ const NewItemPage = () => { )}

)} - {/*{this.props.data.data.sideload !== true && ( + {item.sideload !== true && (
- )}*/} + )} {/*} {this.props.data.data.in_collections?.length > 0 && (
diff --git a/src/features/misc/views/Marketplace.jsx b/src/features/misc/views/Marketplace.jsx index 6d821665..37859ff0 100644 --- a/src/features/misc/views/Marketplace.jsx +++ b/src/features/misc/views/Marketplace.jsx @@ -75,7 +75,7 @@ function Marketplace(props) { initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - transition={{ duration: 10 }} + transition={{ duration: 0.8 }} > {subTab === '' ? ( diff --git a/src/features/navbar/components/Apps.jsx b/src/features/navbar/components/Apps.jsx index bc26c794..a321eb9b 100644 --- a/src/features/navbar/components/Apps.jsx +++ b/src/features/navbar/components/Apps.jsx @@ -116,7 +116,7 @@ class Apps extends PureComponent { {variables.language.getMessage( - variables.languagecode, + variables.locale_id, 'widgets.navbar.apps.no_apps', )} diff --git a/src/features/quote/Quote.jsx b/src/features/quote/Quote.jsx index ca5f5ba2..c15fd967 100644 --- a/src/features/quote/Quote.jsx +++ b/src/features/quote/Quote.jsx @@ -110,7 +110,7 @@ class Quote extends PureComponent { getAuthorLink(author) { return localStorage.getItem('authorLink') === 'false' || author === 'Unknown' ? null - : `https://${variables.languagecode.split('_')[0]}.wikipedia.org/wiki/${author + : `https://${variables.locale_id.split('_')[0]}.wikipedia.org/wiki/${author .split(' ') .join('_')}`; } @@ -131,7 +131,7 @@ class Quote extends PureComponent { const authorimgdata = await ( await fetch( `https://${ - variables.languagecode.split('_')[0] + variables.locale_id.split('_')[0] }.wikipedia.org/w/api.php?action=query&titles=${author}&origin=*&prop=pageimages&format=json&pithumbsize=100`, ) ).json(); @@ -144,7 +144,7 @@ class Quote extends PureComponent { const authorimglicensedata = await ( await fetch( `https://${ - variables.languagecode.split('_')[0] + variables.locale_id.split('_')[0] }.wikipedia.org/w/api.php?action=query&prop=imageinfo&iiprop=extmetadata&titles=File:${ authorPage.pageimage }&origin=*&format=json`, diff --git a/src/features/stats/api/achievements/index.js b/src/features/stats/api/achievements/index.js index 61c1cb54..5dec97ff 100644 --- a/src/features/stats/api/achievements/index.js +++ b/src/features/stats/api/achievements/index.js @@ -1,48 +1,48 @@ import variables from 'config/variables'; -import de_DE from 'i18n/locales/achievements/de_DE.json'; -import en_GB from 'i18n/locales/achievements/en_GB.json'; -import en_US from 'i18n/locales/achievements/en_US.json'; -import es from 'i18n/locales/achievements/es.json'; -import fr from 'i18n/locales/achievements/fr.json'; -import nl from 'i18n/locales/achievements/nl.json'; -import no from 'i18n/locales/achievements/no.json'; -import ru from 'i18n/locales/achievements/ru.json'; -import zh_CN from 'i18n/locales/achievements/zh_CN.json'; -import id_ID from 'i18n/locales/achievements/id_ID.json'; -import tr_TR from 'i18n/locales/achievements/tr_TR.json'; -import bn from 'i18n/locales/achievements/bn.json'; -import pt_BR from 'i18n/locales/achievements/pt_BR.json'; +// import de_DE from 'i18n/locales/achievements/de_DE.json'; +// import en-GB from 'i18n/locales/achievements/en-GB.json'; +// import en_US from 'i18n/locales/achievements/en_US.json'; +// import es from 'i18n/locales/achievements/es.json'; +// import fr from 'i18n/locales/achievements/fr.json'; +// import nl from 'i18n/locales/achievements/nl.json'; +// import no from 'i18n/locales/achievements/no.json'; +// import ru from 'i18n/locales/achievements/ru.json'; +// import zh_CN from 'i18n/locales/achievements/zh_CN.json'; +// import id_ID from 'i18n/locales/achievements/id_ID.json'; +// import tr_TR from 'i18n/locales/achievements/tr_TR.json'; +// import bn from 'i18n/locales/achievements/bn.json'; +// import pt_BR from 'i18n/locales/achievements/pt_BR.json'; import achievements from '../../achievements.json'; import { checkAchievements, newAchievements } from './condition'; -const translations = { - de_DE, - en_GB, - en_US, - es, - fr, - nl, - no, - ru, - zh_CN, - id_ID, - tr_TR, - bn, - pt_BR, -}; +// const translations = { +// de_DE, +// en-GB, +// en_US, +// es, +// fr, +// nl, +// no, +// ru, +// zh_CN, +// id_ID, +// tr_TR, +// bn, +// pt_BR, +// }; // todo: clean this up and condition.js too function getLocalisedAchievementData(id) { - const localised = translations[variables.languagecode][id] || - translations.en_GB[id] || { name: id, description: '' }; + // const localised = translations[variables.locale_id][id] || + // translations.en-GB[id] || { name: id, description: '' }; - return { - name: localised.name, - description: localised.description, - }; + // return { + // name: localised.name, + // description: localised.description, + // }; } export { achievements, checkAchievements, newAchievements, getLocalisedAchievementData }; diff --git a/src/features/time/Date.jsx b/src/features/time/Date.jsx index 299ef967..c5119143 100644 --- a/src/features/time/Date.jsx +++ b/src/features/time/Date.jsx @@ -94,7 +94,7 @@ const DateWidget = () => { setDate(format); } else { // Long date - const lang = variables.languagecode.split('_')[0]; + const lang = variables.locale_id.split('_')[0]; const datenth = localStorage.getItem('datenth') === 'true' diff --git a/src/features/weather/api/getWeather.js b/src/features/weather/api/getWeather.js index d7c0b255..64741915 100644 --- a/src/features/weather/api/getWeather.js +++ b/src/features/weather/api/getWeather.js @@ -25,7 +25,7 @@ export const getWeather = async (location, done) => { try { const response = await fetch( - variables.constants.API_URL + `/weather?city=${location}&language=${variables.languagecode}`, + variables.constants.API_URL + `/weather?city=${location}&language=${variables.locale_id}`, ); if (!response.ok) { diff --git a/src/i18n/en-GB/_achievements.yml b/src/i18n/en-GB/_achievements.yml new file mode 100644 index 00000000..5276e5e8 --- /dev/null +++ b/src/i18n/en-GB/_achievements.yml @@ -0,0 +1,36 @@ +727: + name: When You See It + description: Opened 727 tabs +1337: + name: MU3T4B + description: Opened 1337 tabs +10tabs: + name: 10/10 IGN + description: Opened 10 tabs +thankyou: + name: Thank You + description: Opened 39 tabs +seasoning: + name: Seasoning + description: Opened 100 tabs +mrworldwide: + name: Mr. Worldwide + description: Opened 305 tabs +tabaholic: + name: Tabaholic + description: Opened 500 tabs +808s: + name: 808s & Tab Breaks + description: Opened 808 tabs +averagelinuxuser: + name: Average Linux User + description: Installed an add-on +fullyriced: + name: Fully Riced + description: Installed 5 add-ons +21addons: + name: They installed 21 add-ons? Whaaat? + description: Installed 21 add-ons +overload: + name: System Overload + description: Installed 50 add-ons diff --git a/src/i18n/en-GB/_addons.yml b/src/i18n/en-GB/_addons.yml new file mode 100644 index 00000000..0d8cbb70 --- /dev/null +++ b/src/i18n/en-GB/_addons.yml @@ -0,0 +1,30 @@ +added: Added +check_updates: Check for updates +no_updates: No updates available +updates_available: Updates available {amount} +empty: + title: Empty + description: No addons are installed +sideload: + title: Sideload + description: Install a Mue addon not on the marketplace from your computer + failed: Failed to sideload addon + errors: + no_name: No name provided + no_author: No author provided + no_type: No type provided + invalid_photos: Invalid photos object + invalid_quotes: Invalid quotes object +sort: + title: Sort + newest: Installed (Newest) + oldest: Installed (Oldest) + a_z: Alphabetical (A-Z) + z_a: Alphabetical (Z-A) +create: + title: Create + moved_title: Create add-on has moved! + moved_description: >- + Following the 7.1 update, the creation of addons has now moved to the + web UI + moved_button: Coming Soon.... diff --git a/src/i18n/en-GB/_marketplace.yml b/src/i18n/en-GB/_marketplace.yml new file mode 100644 index 00000000..37f0605b --- /dev/null +++ b/src/i18n/en-GB/_marketplace.yml @@ -0,0 +1,49 @@ +by: by {author} +all: All +learn_more: Learn More +photo_packs: Photo Packs +quote_packs: Quote Packs +preset_settings: Preset Settings +no_items: No items in this category +collection: Collection +explore_collection: Explore Collection +add_all: Add All To Mue +collections: Collections +cant_find: Can't find what you're looking for? +knowledgebase_one: Visit the +knowledgebase_two: knowledgebase +knowledgebase_three: to create your own. +installing: Installing +product: + overview: Overview + information: Information + last_updated: Last Updated + description: Description + details: Details + more_from_curator: More like this from {name} + show_more: Show More + show_less: Show Less + show_all: Show All + showing: Showing + no_images: No. Images + no_quotes: No. Quotes + version: Version + created_by: Created by + updated_at: Updated on + part_of: Part of + explore: Explore + buttons: + addtomue: Add To Mue + remove: Remove + update_addon: Update Add-on + back: Back + report: Report + not_available_preview: Unavailable on Preview + not_in_language: This add-on is not in your language + third_party_api: This add-on uses a third-party API + sideload_warning: This add-on has been sideloaded. + setting: Setting + value: Value +offline: + title: Looks like you're offline + description: Please connect to the internet diff --git a/src/i18n/en-GB/_settings.yml b/src/i18n/en-GB/_settings.yml new file mode 100644 index 00000000..8c501a5d --- /dev/null +++ b/src/i18n/en-GB/_settings.yml @@ -0,0 +1,391 @@ +enabled: Enabled +open_knowledgebase: Open Knowledgebase +additional_settings: Additional Settings +reminder: + title: NOTICE + message: >- + In order for all of the changes to take place, the page must be + refreshed. +sections: + header: + more_info: More info + report_issue: Report Issue + enabled: Choose whether or not to show this widget + size: Slider to control how large the widget is + time: + title: Time + format: Format + type: Type + type_subtitle: >- + Choose whether to display the time in digital format, analogue + format, or a percentage completion of the day + digital: + title: Digital + subtitle: Change how the digital clock looks + seconds: Seconds + twentyfourhour: 24 Hour + twelvehour: 12 hour + zero: Zero-padded + analogue: + title: Analogue + subtitle: Change how the analogue clock looks + second_hand: Seconds hand + minute_hand: Minutes hand + hour_hand: Hours hand + hour_marks: Hour marks + minute_marks: Minute marks + round_clock: Rounded background + percentage_complete: Percentage complete + vertical_clock: + title: Vertical Clock + change_hour_colour: Change hour text colour + change_minute_colour: Change minute text colour + change_second_colour: Change second text colour + date: + title: Date + week_number: Week number + day_of_week: Day of week + datenth: Date nth + type: + short: Short + long: Long + subtitle: Whether to display the date in long form or short form + type_settings: Display settings and format for the selected date type + short_date: Short date + short_format: Short format + long_format: Long format + short_separator: + title: Short separator + dots: Dots + dash: Dash + gaps: Gaps + slashes: Slashes + quote: + title: Quote + additional: Other settings to customise the style of the quote widget + author_link: Author link + custom: Custom quote + custom_subtitle: Set your own custom quotes + no_quotes: No quotes + author: Author + custom_buttons: Buttons + custom_author: Custom author + author_img: Show author image + add: Add quote + source_subtitle: Choose where to get quotes from + buttons: + title: Buttons + subtitle: Choose which buttons to show on the quote + copy: Copy + tweet: Tweet + favourite: Favourite + greeting: + title: Greeting + events: Events + events_description: Control events on Mue such as birthdays. + enable_events: Show messages on special days + default: Default greeting message + name: Name for greeting + birthday: Birthday + birthday_subtitle: Show a Happy Birthday message when it is your birthday + birthday_age: Birthday age + birthday_date: Birthday date + additional: Settings for the greeting display + custom: Custom events + event_name: Event Name + day: Day + month: Month + no_events: No Events + no_events_description: Add Some Events + add_event: Add Event + background: + title: Background + transition: Fade-in transition + photo_information: Show photo information + show_map: Show location map on photo information if available + categories: Categories + buttons: + title: Buttons + view: Maximise + favourite: Favourite + download: Download + effects: + title: Effects + subtitle: Add effects to the background images + blur: Adjust blur + brightness: Adjust brightness + filters: + title: Background filter + amount: Filter amount + grayscale: Grayscale + sepia: Sepia + invert: Invert + saturate: Saturate + contrast: Contrast + type: + title: Type + api: API + custom_image: Custom image + custom_colour: Custom colour/gradient + random_colour: Random colour + random_gradient: Random gradient + unsplash: + title: Unsplash Collection(s) + subtitle: Select the collection(s) you want to use for your background + id: Collection ID(s) + id_subtitle: Enter a comma separated list of Unsplash IDs + source: + title: Source + subtitle: Select where to get background images from + api: Background API + custom_background: Custom background + custom_colour: Custom background colour + upload: Upload + add_colour: Add colour + add_background: Add background + drop_to_upload: Drop to upload + formats: 'Available formats: {list}' + select: Or Select + add_url: Add URL + disabled: Disabled + loop_video: Loop video + mute_video: Mute video + quality: + title: Quality + original: Original + high: High Quality + normal: Normal Quality + datasaver: Data Saver + custom_title: Custom Images + custom_description: Select images from your local computer + remove: Remove Image + display: Display + display_subtitle: Change how background and photo information are loaded + api: API Settings + api_subtitle: Options for getting an image from an external service (API) + search: + title: Search + additional: Additional options for search widget display and functionality + search_engine: Search Engine + search_engine_subtitle: Choose search engine to use in the search bar + custom: Custom Search URL + autocomplete: Autocomplete + autocomplete_provider: Autocomplete Provider + autocomplete_provider_subtitle: Search engine to use for autocomplete dropdown results + voice_search: Voice search + dropdown: Search Dropdown + focus: Focus on tab open + weather: + title: Weather + location: Location + auto: Auto + widget_type: Widget Type + temp_format: + title: Temperature format + celsius: Celsius + fahrenheit: Fahrenheit + kelvin: Kelvin + extra_info: + title: Extra information + show_location: Show location + show_description: Show description + weather_description: Weather description + cloudiness: Cloudiness + humidity: Humidity + visibility: Visibility + wind_speed: Wind speed + wind_direction: Wind direction + min_temp: Minimum temperature + max_temp: Maximum temperature + atmospheric_pressure: Atmospheric pressure + options: + basic: Basic + standard: Standard + expanded: Expanded + custom: Custom + custom_settings: Custom Settings + quicklinks: + title: Quick Links + additional: Additional settings for quick links display and functions + open_new: Open in new tab + tooltip: Tooltip + text_only: Show text only + add_link: Add Link + no_quicklinks: No quick links + edit: Edit + style: Style + options: + icon: Icon + text_only: Text Only + metro: Metro + styling: Styling + styling_description: Customise Quick Links appearance + message: + title: Message + add: Add message + messages: Messages + text: Text + no_messages: No messages + add_some: Go ahead and add some. + content: Message Content + appearance: + title: Appearance + style: + title: Widget Style + description: >- + Choose between the two styles, legacy (enabled for pre 7.0 users) + and our slick modern styling. + legacy: Legacy + new: New + theme: + title: Theme + description: Change the theme of the Mue widgets and modals + auto: Auto + light: Light + dark: Dark + navbar: + title: Navbar + widgets: Navbar Widgets + notes: Notes + refresh: Refresh + refresh_subtitle: Choose what is refreshed when you click the refresh button + hover: Only display on hover + additional: Modify navbar style and which buttons you want to display + refresh_options: + none: None + page: Page + apps_subtitle: Create a shortcut of your other commonly used websites. + font: + title: Font + description: Change the font used in Mue + custom: Custom font + google: Import from Google Fonts + weight: + title: Font weight + thin: Thin + extra_light: Extra Light + light: Light + normal: Normal + medium: Medium + semi_bold: Semi-Bold + bold: Bold + extra_bold: Extra-Bold + style: + title: Font style + normal: Normal + italic: Italic + oblique: Oblique + accessibility: + title: Accessibility + description: Make Mue comfortable and accessible + animations: Animations + text_shadow: + title: Widget text shadow + new: New + old: Old + none: None + widget_zoom: Widget Zoom + toast_duration: Toast Duration + milliseconds: milliseconds + order: + title: Widget Order + advanced: + title: Advanced + offline_mode: Offline mode + offline_subtitle: When enabled, all requests to online services will be disabled. + data: Data + data_description: >- + Choose whether to export your Mue settings to your computer, import + an existing settings file, or reset your settings to their default + values + data_subtitle: Manage your data on Mue. + reset_modal: + title: WARNING + question: Do you want to reset Mue? + information: >- + This will delete all data. If you wish to keep your data and + preferences, please export them first. + cancel: Cancel + customisation: Customisation + custom_css: Custom CSS + custom_css_subtitle: >- + Make Mue's styling customised to you with Cascading Style Sheets + (CSS). + custom_js: Custom JS + tab_name: Tab name + tab_name_subtitle: Change the name of the tab that appears in your browser + timezone: + title: Time Zone + subtitle: >- + Choose a timezone from the list instead of the automatic default + from your computer + automatic: Automatic + experimental_warning: >- + Please note that the Mue team cannot provide support if you have + experimental mode on. Please disable it first and see if the issue + continues to occur before contacting support. + preview_data_disabled: + title: Data Settings Disabled + description: >- + Data settings are disabled in preview mode. Please exit preview + mode to use this feature. + stats: + title: Stats + sections: + tabs_opened: Tabs opened + backgrounds_favourited: Backgrounds favourited + backgrounds_downloaded: Backgrounds downloaded + quotes_favourited: Quotes favourited + quicklinks_added: Quicklinks added + settings_changed: Settings changed + addons_installed: Add-ons installed + usage: Usage Stats + achievements: Achievements + achievement_unlocked: 'Achievement Unlocked: {name}' + unlocked: '{count} Unlocked' + locked: Locked + clear_modal: + question: Do you want to clear your stats? + information: This will clear all achievements and usage statistics. + experimental: + title: Experimental + warning: >- + These settings have not been fully tested/implemented and may not + work correctly! + developer: Developer + language: + title: Language + quote: Quote language + changelog: + title: Change Log + by: By {author} + about: + title: About + version: + title: Version + checking_update: Checking for update + update_available: Update available + no_update: No update available + offline_mode: Cannot check for update in offline mode + error: + title: Failed to get update information + description: An error occured + contact_us: Contact Us + support_mue: Support Mue + support_subtitle: >- + As Mue is entirely free, we rely on donations to cover the server + bills and fund development + support_donate: Donate + form_button: Form + resources_used: + title: Resources used + bg_images: Offline background images + contributors: Contributors + supporters: Supporters + no_supporters: There are currently no Mue supporters + photographers: Photographers + curators: Add-on creators +buttons: + reset: Reset + import: Import + export: Export diff --git a/src/i18n/en-GB/_welcome.yml b/src/i18n/en-GB/_welcome.yml new file mode 100644 index 00000000..a1c75501 --- /dev/null +++ b/src/i18n/en-GB/_welcome.yml @@ -0,0 +1,75 @@ +tip: Quick Tip +sections: + intro: + title: Welcome to Mue Tab + description: >- + Thank you for installing Mue, we hope you enjoy your time with our + extension. + notices: + discord_title: Join our Discord + discord_description: Talk with the Mue community and developers + discord_join: Join + github_title: Contribute on GitHub + github_description: Report bugs, add features or donate + github_open: Open + language: + title: Choose your language + description: >- + Mue can be displayed the languages listed below. You can also add new + translations on our + theme: + title: Select a theme + description: >- + Mue is available in both light and dark theme, or this can be + automatically set depending on your system theme. + tip: >- + Using the Auto settings will use the theme on your computer. This + setting will impact the modals and some of the widgets displayed on + the screen, such as weather and notes. + style: + title: Choose a style + description: >- + Mue currently offers the choice between the legacy styling and the + newly released modern styling. + legacy: Legacy + modern: Modern + settings: + title: Import Settings + description: Installing Mue on a new device? Feel free to import your old settings! + tip: >- + You can export your old settings by navigating to the Advanced tab in + your old Mue setup. Then you need to click the export button which + will download the JSON file. You can upload this file here to carry + across your settings and preferences from your previous Mue + installation. + privacy: + title: Privacy Options + description: Enable settings to further protect your privacy with Mue. + offline_mode_description: >- + Enabling offline mode will disable all requests to any service. This + will result in online backgrounds, online quotes, marketplace, + weather, quick links, change log and some about tab information to be + disabled. + links: + title: Links + privacy_policy: Privacy Policy + source_code: Source Code + final: + title: Final step + description: Your Mue Tab experience is about to begin. + changes: Changes + changes_description: >- + To change settings later click on the settings icon in the top right + corner of your tab. + imported: Imported {amount} settings +buttons: + next: Next + preview: Preview + previous: Previous + close: Close + finish: Finish +preview: + description: >- + You are currently in preview mode. Settings will be reset on closing + this tab. + continue: Continue setup diff --git a/src/i18n/en-GB/main.yml b/src/i18n/en-GB/main.yml new file mode 100644 index 00000000..6001e836 --- /dev/null +++ b/src/i18n/en-GB/main.yml @@ -0,0 +1,105 @@ +tabname: New Tab +widgets: + greeting: + morning: Good Morning + afternoon: Good Afternoon + evening: Good Evening + christmas: Merry Christmas + newyear: Happy New Year + halloween: Happy Halloween + birthday: Happy Birthday + background: + credit: Photo by + unsplash: on Unsplash + information: Information + download: Download + downloads: Downloads + views: Views + likes: Likes + location: Location + camera: Camera + resolution: Resolution + source: Source + category: Category + exclude: Don't show again + exclude_confirm: >- + Are you sure you don't want to see this image again? + + Note: if you don't like "{category}" images, you can deselect the category + in the background source settings. + confirm: Confirm + search: Search + quicklinks: + new: New Link + edit: Edit Link + name: Name + url: URL + icon: Icon (optional) + add: Add + name_error: Must provide name + url_error: Must provide URL + date: + week: Week + weather: + not_found: Not Found + meters: '{amount} metres' + extra_information: Extra Information + feels_like: Feels like {amount} + quote: + link_tooltip: Open on Wikipedia + share: Share + copy: Copy + favourite: Favourite + unfavourite: Unfavourite + navbar: + tooltips: + refresh: Refresh + notes: + title: Notes + placeholder: Type here + todo: + title: Todo + pin: Pin + add: Add + no_todos: No Todos + apps: + title: Apps + no_apps: No app links found +modals: + main: + title: Options + loading: Loading... + file_upload_error: File is over 2MB + navbar: + settings: Settings + addons: Add-ons + marketplace: Marketplace + error_boundary: + title: Error + message: Failed to load this component of Mue + report_error: Send Error Report + sent: Sent! + refresh: Refresh + update: + title: Update + offline: + title: Offline + description: Cannot get update logs while in offline mode + error: + title: Error + description: Could not connect to the server + share: + copy_link: Copy link + email: Email +toasts: + quote: Quote copied + notes: Notes copied + reset: Reset successfully + installed: Successfully installed + uninstalled: Successfully removed + updated: Successfully updated + error: Something went wrong + imported: Successfully imported + no_storage: Not enough storage + link_copied: Link copied + stats_reset: Stats reset diff --git a/src/i18n/languages.json b/src/i18n/languages.json index 446c3958..7cacc14f 100644 --- a/src/i18n/languages.json +++ b/src/i18n/languages.json @@ -1,7 +1,7 @@ [ { "name": "Bahasa Indonesia", - "value": "id_ID" + "value": "id-ID" }, { "name": "বাংলা", @@ -9,15 +9,15 @@ }, { "name": "Deutsch", - "value": "de_DE" + "value": "de-DE" }, { "name": "English (UK)", - "value": "en_GB" + "value": "en-GB" }, { "name": "English (US)", - "value": "en_US" + "value": "en-US" }, { "name": "Español", @@ -25,7 +25,7 @@ }, { "name": "Español (Latinoamérica)", - "value": "es_419" + "value": "es-419" }, { "name": "Français", @@ -45,14 +45,14 @@ }, { "name": "Türkçe", - "value": "tr_TR" + "value": "tr-TR" }, { "name": "中文 (简体)", - "value": "zh_CN" + "value": "zh-CN" }, { "name": "Portuguese (Brazil)", - "value": "pt_BR" + "value": "pt-BR" } ] diff --git a/src/i18n/locales/de_DE.json b/src/i18n/locales/de-DE.json similarity index 100% rename from src/i18n/locales/de_DE.json rename to src/i18n/locales/de-DE.json diff --git a/src/i18n/locales/en_GB.json b/src/i18n/locales/en-GB.json similarity index 100% rename from src/i18n/locales/en_GB.json rename to src/i18n/locales/en-GB.json diff --git a/src/i18n/locales/en_US.json b/src/i18n/locales/en-US.json similarity index 100% rename from src/i18n/locales/en_US.json rename to src/i18n/locales/en-US.json diff --git a/src/i18n/locales/es_419.json b/src/i18n/locales/es-419.json similarity index 100% rename from src/i18n/locales/es_419.json rename to src/i18n/locales/es-419.json diff --git a/src/i18n/locales/id_ID.json b/src/i18n/locales/id-ID.json similarity index 100% rename from src/i18n/locales/id_ID.json rename to src/i18n/locales/id-ID.json diff --git a/src/i18n/locales/pt_BR.json b/src/i18n/locales/pt-BR.json similarity index 100% rename from src/i18n/locales/pt_BR.json rename to src/i18n/locales/pt-BR.json diff --git a/src/i18n/locales/tr_TR.json b/src/i18n/locales/tr-TR.json similarity index 100% rename from src/i18n/locales/tr_TR.json rename to src/i18n/locales/tr-TR.json diff --git a/src/i18n/locales/zh_CN.json b/src/i18n/locales/zh-CN.json similarity index 100% rename from src/i18n/locales/zh_CN.json rename to src/i18n/locales/zh-CN.json diff --git a/src/index.jsx b/src/index.jsx index 8aec1add..a3ef29c1 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -12,15 +12,15 @@ import './scss/index.scss'; // the toast css is based on default so we need to import it import 'react-toastify/dist/ReactToastify.min.css'; -import { initTranslations } from 'lib/translations'; +import { createTranslator } from 'lib/translations'; -const languagecode = localStorage.getItem('language') || defaults.language; -variables.language = initTranslations(languagecode); -variables.languagecode = languagecode; -document.documentElement.lang = languagecode.replace('_', '-'); +const locale_id = localStorage.getItem('language').replace('_', '-') || defaults.language; +variables.language = createTranslator(locale_id); +variables.locale_id = locale_id; +document.documentElement.lang = locale_id; variables.getMessage = (text, optional) => - variables.language.getMessage(variables.languagecode, text, optional || {}); + variables.language.getMessage(variables.locale_id, text, optional || {}); Sentry.init({ dsn: variables.constants.SENTRY_DSN, diff --git a/src/lib/translations.js b/src/lib/translations.js index 0dc6be88..f12d619e 100644 --- a/src/lib/translations.js +++ b/src/lib/translations.js @@ -1,60 +1,23 @@ -import I18n from '@eartharoid/i18n'; - -import * as de_DE from 'translations/de_DE.json'; -import * as en_GB from 'translations/en_GB.json'; -import * as en_US from 'translations/en_US.json'; -import * as es from 'translations/es.json'; -import * as es_419 from 'translations/es_419.json'; -import * as fr from 'translations/fr.json'; -import * as nl from 'translations/nl.json'; -import * as no from 'translations/no.json'; -import * as ru from 'translations/ru.json'; -import * as zh_CN from 'translations/zh_CN.json'; -import * as id_ID from 'translations/id_ID.json'; -import * as tr_TR from 'translations/tr_TR.json'; -import * as pt_BR from 'translations/pt_BR.json'; -import * as bn from 'translations/bn.json'; +import { I18nLite } from '@eartharoid/i18n'; +import { importJSON } from '@eartharoid/vite-plugin-i18n'; +export const languages = Object.keys(import.meta.glob('i18n/**/*.yml')); /** * Initialise the i18n object. * The i18n object is then returned. - * @param locale _ The locale to use. + * @param locale_id The locale to use. * @returns The i18n object. */ -export function initTranslations(locale) { - const i18n = new I18n(locale, { - de_DE, - en_GB, - en_US, - es, - es_419, - fr, - nl, - no, - ru, - zh_CN, - id_ID, - tr_TR, - pt_BR, - bn, - }); - - return i18n; -} - -export const translations = { - de_DE, - en_GB, - en_US, - es, - es_419, - fr, - nl, - no, - ru, - zh_CN, - id_ID, - tr_TR, - pt_BR, - bn, -}; +export async function createTranslator(locale_id) { + const loaded = importJSON( + await import(`i18n/${locale_id}/_achievements.yml`), + await import(`i18n/${locale_id}/_addons.yml`), + await import(`i18n/${locale_id}/_marketplace.yml`), + await import(`i18n/${locale_id}/_settings.yml`), + await import(`i18n/${locale_id}/_welcome.yml`), + await import(`i18n/${locale_id}/main.yml`), + ); + const i18n = new I18nLite(); + const t = i18n.loadParsed(...loaded).createTranslator(); + return t; +} \ No newline at end of file diff --git a/src/utils/settings/default.js b/src/utils/settings/default.js index 0c4a4108..90774344 100644 --- a/src/utils/settings/default.js +++ b/src/utils/settings/default.js @@ -9,16 +9,13 @@ export function setDefaultSettings(reset) { localStorage.clear(); // Languages - const languageCodes = languages.map(({ value }) => value); - const browserLanguage = - (navigator.languages && - navigator.languages.find((lang) => lang.replace('-', '_') && languageCodes.includes(lang))) || - navigator.language.replace('-', '_'); + const locale_ids = languages.map(({ value }) => value); + const browserLanguage = (navigator.languages && navigator.languages.find((lang) => locale_ids.includes(lang))) || navigator.language; - if (languageCodes.includes(browserLanguage)) { + if (locale_ids.includes(browserLanguage)) { localStorage.setItem('language', browserLanguage); } else { - localStorage.setItem('language', 'en_GB'); + localStorage.setItem('language', 'en-GB'); } localStorage.setItem('tabName', variables.getMessage('tabname')); diff --git a/vite.config.mjs b/vite.config.mjs index 054cbdf8..a03c74b8 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -5,6 +5,8 @@ import fs from 'fs'; import ADMZip from 'adm-zip'; import * as pkg from './package.json'; import progress from 'vite-plugin-progress'; +import { I18nPlugin } from '@eartharoid/vite-plugin-i18n'; +import YAML from 'yaml'; const isProd = process.env.NODE_ENV === 'production'; @@ -98,7 +100,15 @@ export default defineConfig(({ command, mode }) => { define: { __APP_ENV__: JSON.stringify(env.APP_ENV), }, - plugins: [react(), prepareBuilds(), progress()], + plugins: [ + react(), + I18nPlugin({ + include: './src/i18n/**/*.yml', + parser: (file) => YAML.parse(file), + }), + prepareBuilds(), + progress() + ], server: { open: true, hmr: { @@ -134,7 +144,6 @@ export default defineConfig(({ command, mode }) => { features: path.resolve(__dirname, './src/features'), lib: path.resolve(__dirname, './src/lib'), scss: path.resolve(__dirname, './src/scss'), - translations: path.resolve(__dirname, './src/i18n/locales'), utils: path.resolve(__dirname, './src/utils'), }, },