diff --git a/biome.jsonc b/biome.jsonc index f89f00e76..f8c3cfe21 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -31,7 +31,8 @@ "ignore": [ "src/renderer/ethers.js", "src/main/util/*.js", - "*genesis-l2.json" + "*genesis-l2.json", + "src/renderer/utils/browserLanguageDetector.js" ] } } diff --git a/package-lock.json b/package-lock.json index 3b4eee347..045b8dd8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nice-node", - "version": "6.4.4-alpha", + "version": "6.4.3-alpha", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nice-node", - "version": "6.4.4-alpha", + "version": "6.4.3-alpha", "license": "MIT", "dependencies": { "@reduxjs/toolkit": "^2.2.7", @@ -84,7 +84,7 @@ "@wdio/mocha-framework": "^9.0.8", "@wdio/spec-reporter": "^9.0.8", "cross-env": "^7.0.3", - "electron": "^34.2.0", + "electron": "^34.3.0", "electron-devtools-installer": "^3.2.0", "electron-extension-installer": "^1.2.0", "electron-mock-ipc": "^0.3.12", @@ -11284,9 +11284,9 @@ } }, "node_modules/electron": { - "version": "34.2.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-34.2.0.tgz", - "integrity": "sha512-SYwBJNeXBTm1q/ErybQMUBZAYqEreBUqBwTrNkw1rV4YatDZk5Aittpcus3PPeC4UoI/tqmJ946uG8AKHTd6CA==", + "version": "34.3.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-34.3.0.tgz", + "integrity": "sha512-I238qRnYTAsuwJ/rS7HGaFNY4NNKAcjX8nlj7mnNmj1TK3z4HvNoD1r7Zud81DYDFx8AITuLd76EPrEnnfF9Bg==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/package.json b/package.json index e26490030..0afbf417e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nice-node", - "version": "6.4.4-alpha", + "version": "6.4.3-alpha", "description": "Run a node at home, the easy way.", "homepage": "https://nicenode.xyz", "productName": "NiceNode", @@ -65,7 +65,7 @@ "@wdio/mocha-framework": "^9.0.8", "@wdio/spec-reporter": "^9.0.8", "cross-env": "^7.0.3", - "electron": "^34.2.0", + "electron": "^34.3.0", "electron-devtools-installer": "^3.2.0", "electron-extension-installer": "^1.2.0", "electron-mock-ipc": "^0.3.12", diff --git a/src/__tests__/node/childProcess.test.ts b/src/__tests__/node/childProcess.test.ts index 391270743..597370b90 100644 --- a/src/__tests__/node/childProcess.test.ts +++ b/src/__tests__/node/childProcess.test.ts @@ -1,7 +1,7 @@ import { type SpawnOptions, spawn } from 'node:child_process'; import * as url from 'node:url'; -import sleep from 'await-sleep'; import { describe, expect, it } from 'vitest'; +import { sleep } from '../../main/utils/sleep.js'; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); diff --git a/src/main/files.ts b/src/main/files.ts index 26b165ea7..87f980cce 100644 --- a/src/main/files.ts +++ b/src/main/files.ts @@ -7,7 +7,7 @@ import { app } from 'electron'; import logger from './logger'; -import du from 'du'; +import calculateDiskUsage from './utils/diskUsage'; logger.info(`App data dir: ${app.getPath('appData')}`); logger.info(`User data dir: ${app.getPath('userData')}`); @@ -124,7 +124,7 @@ export const getSystemDiskSize = async (): Promise => { export const tryCalcDiskSpace = async (dirPath: string) => { let diskUsedInGBs; try { - diskUsedInGBs = (await du(dirPath)) * 1e-9; + diskUsedInGBs = (await calculateDiskUsage(dirPath)) * 1e-9; } catch (err) { console.info( `Cannot calculate disk usage at ${dirPath}. Could be changing files.`, diff --git a/src/main/i18nMain.ts b/src/main/i18nMain.ts index deb05e1a3..7a1a6a608 100644 --- a/src/main/i18nMain.ts +++ b/src/main/i18nMain.ts @@ -1,5 +1,5 @@ import i18n from 'i18next'; -import I18nextCLILanguageDetector from 'i18next-cli-language-detector'; +import I18nextCLILanguageDetector from './utils/i18nextCLILanguageDetector.js'; import { getMenuBuilder } from './main'; import { getSettings, setLanguage } from './state/settings'; diff --git a/src/main/nn-auto-updater/main.ts b/src/main/nn-auto-updater/main.ts index 5bbb83f33..6aa47fc82 100644 --- a/src/main/nn-auto-updater/main.ts +++ b/src/main/nn-auto-updater/main.ts @@ -1,7 +1,13 @@ +/** + * Uses TypedEmitter implementation based on tiny-typed-emitter + * @see https://github.com/binier/tiny-typed-emitter + * @license MIT + */ + import EventEmitter from 'node:events'; import { type AutoUpdater, autoUpdater as _autoUpdater } from 'electron'; -import { TypedEmitter } from 'tiny-typed-emitter'; import { checkForUpdates } from './../updater'; +import { TypedEmitter } from './typed-emitter.js'; import { isLinux } from '../platform'; import { findPackageManager } from './findPackageManager'; diff --git a/src/main/nn-auto-updater/typed-emitter.d.ts b/src/main/nn-auto-updater/typed-emitter.d.ts new file mode 100644 index 000000000..d98c1c129 --- /dev/null +++ b/src/main/nn-auto-updater/typed-emitter.d.ts @@ -0,0 +1,32 @@ +/** + * Based on tiny-typed-emitter + * @see https://github.com/binier/tiny-typed-emitter + * @license MIT + */ + +export type ListenerSignature = { + [E in keyof L]: (...args: any[]) => any; +}; + +export type DefaultListener = { + [k: string]: (...args: any[]) => any; +}; + +export class TypedEmitter = DefaultListener> { + static defaultMaxListeners: number; + addListener(event: U, listener: L[U]): this; + prependListener(event: U, listener: L[U]): this; + prependOnceListener(event: U, listener: L[U]): this; + removeListener(event: U, listener: L[U]): this; + removeAllListeners(event?: keyof L): this; + once(event: U, listener: L[U]): this; + on(event: U, listener: L[U]): this; + off(event: U, listener: L[U]): this; + emit(event: U, ...args: Parameters): boolean; + eventNames(): U[]; + listenerCount(type: keyof L): number; + listeners(type: U): L[U][]; + rawListeners(type: U): L[U][]; + getMaxListeners(): number; + setMaxListeners(n: number): this; +} diff --git a/src/main/nn-auto-updater/typed-emitter.js b/src/main/nn-auto-updater/typed-emitter.js new file mode 100644 index 000000000..933f90d8c --- /dev/null +++ b/src/main/nn-auto-updater/typed-emitter.js @@ -0,0 +1,2 @@ +Object.defineProperty(exports, '__esModule', { value: true }); +exports.TypedEmitter = require('node:events').EventEmitter; diff --git a/src/main/podman/install/install.ts b/src/main/podman/install/install.ts index 9e369fd2c..aa1a996a4 100644 --- a/src/main/podman/install/install.ts +++ b/src/main/podman/install/install.ts @@ -5,7 +5,7 @@ import installOnLinux from './installOnLinux'; import installOnMac from './installOnMac'; import installOnWindows from './installOnWindows'; -export const PODMAN_LATEST_VERSION = '5.4.0'; +export const PODMAN_LATEST_VERSION = '5.2.4'; export const PODMAN_MIN_VERSION = '4.3.0'; const installPodman = async (): Promise => { diff --git a/src/main/podman/uninstall/uninstallOnLinux.ts b/src/main/podman/uninstall/uninstallOnLinux.ts index 6b5e82431..2f3b14b84 100644 --- a/src/main/podman/uninstall/uninstallOnLinux.ts +++ b/src/main/podman/uninstall/uninstallOnLinux.ts @@ -26,16 +26,21 @@ const uninstallOnLinux = async (): Promise => { // Stop and remove all containers await execAwait('podman stop -a', { log: true }); await execAwait('podman rm -af', { log: true }); - + // Remove all pods await execAwait('podman pod rm -af', { log: true }); - + // Remove all images await execAwait('podman rmi -af', { log: true }); - - logger.info('Successfully cleaned up all podman containers, pods and images'); + + logger.info( + 'Successfully cleaned up all podman containers, pods and images', + ); } catch (cleanupErr) { - logger.error('Error during container cleanup, continuing with uninstall:', cleanupErr); + logger.error( + 'Error during container cleanup, continuing with uninstall:', + cleanupErr, + ); } // Remove podman configuration folders @@ -45,7 +50,7 @@ const uninstallOnLinux = async (): Promise => { `${userHome}/.ssh/*podman*`, '/usr/local/podman', ]; - + try { const rmCommand = `rm -rf ${foldersToDelete.join(' ')}`; await execAwait(rmCommand, { diff --git a/src/main/updater.ts b/src/main/updater.ts index 16515ff23..36171d2d2 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -1,11 +1,11 @@ -import sleep from 'await-sleep'; import { type BrowserWindow, app, dialog } from 'electron'; -import { autoUpdateLogger } from './logger'; +import { autoUpdateLogger } from './logger.js'; +import { sleep } from './utils/sleep.js'; -import { reportEvent } from './events'; -import i18nMain from './i18nMain'; -import { setFullQuitForNextQuit } from './main'; -import { autoUpdater } from './nn-auto-updater/main'; +import { reportEvent } from './events.js'; +import i18nMain from './i18nMain.js'; +import { setFullQuitForNextQuit } from './main.js'; +import { autoUpdater } from './nn-auto-updater/main.js'; // import { getSetIsPreReleaseUpdatesEnabled } from './state/settings'; let notifyUserIfNoUpdateAvailable: boolean; @@ -15,7 +15,7 @@ const t = i18nMain.getFixedT(null, 'updater'); const logger = autoUpdateLogger; const initUpdateHandlers = (browserWindow: BrowserWindow) => { - autoUpdater.on('error', (error) => { + autoUpdater.on('error', (error: Error) => { logger.error('autoUpdater:::::::::error', error); }); @@ -42,7 +42,7 @@ const initUpdateHandlers = (browserWindow: BrowserWindow) => { } }); - autoUpdater.on('update-downloaded', (...args) => { + autoUpdater.on('update-downloaded', (...args: unknown[]) => { logger.info('autoUpdater:::::::::update-downloaded args: ', args); logger.info('Calling autoUpdater.quitAndInstall()'); try { diff --git a/src/main/utils/diskUsage.ts b/src/main/utils/diskUsage.ts new file mode 100644 index 000000000..bda1e9bb6 --- /dev/null +++ b/src/main/utils/diskUsage.ts @@ -0,0 +1,64 @@ +/** + * Inspired by node-du (https://github.com/rvagg/node-du) + * + * Original License (MIT): + * Copyright (c) 2012 Rod Vagg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import fs from 'node:fs/promises'; +import path from 'node:path'; + +/** + * Calculates the size of a directory or file recursively + * @param dir Path to directory or file + * @param options Optional settings (disk: boolean to use block size instead of file size) + * @returns Promise Total size in bytes + */ +async function calculateDiskUsage( + dir: string, + options: { disk?: boolean } = {}, +): Promise { + const stat = await fs.lstat(dir); + + // Base size calculation + const size = options.disk ? 512 * stat.blocks : stat.size; + + if (!stat.isDirectory()) { + return size; + } + + try { + const files = await fs.readdir(dir); + const sizes = await Promise.all( + files.map( + (file) => + calculateDiskUsage(path.join(dir, file), options).catch(() => 0), // Handle permission errors or other issues for individual files + ), + ); + + return sizes.reduce((total, s) => total + s, size); + } catch (err) { + // If we can't read the directory, return the current known size + return size; + } +} + +export default calculateDiskUsage; diff --git a/src/main/utils/i18nextCLILanguageDetector.ts b/src/main/utils/i18nextCLILanguageDetector.ts new file mode 100644 index 000000000..bca1873a4 --- /dev/null +++ b/src/main/utils/i18nextCLILanguageDetector.ts @@ -0,0 +1,87 @@ +import type { InitOptions, LanguageDetectorModule, Services } from 'i18next'; + +export class I18nextCLILanguageDetector implements LanguageDetectorModule { + static type = 'languageDetector' as const; + + type = I18nextCLILanguageDetector.type; + services!: Services; + detectorOptions?: object; + i18nextOptions!: InitOptions; + + init( + services: Services, + detectorOptions: object, + i18nextOptions: InitOptions, + ): void { + this.services = services; + this.detectorOptions = detectorOptions; + this.i18nextOptions = i18nextOptions; + } + + detect(): string | string[] | undefined { + const shellLocale = + process.env.LC_ALL ?? + process.env.LC_MESSAGES ?? + process.env.LANG ?? + process.env.LANGUAGE; + + const language = this._getShellLanguage(shellLocale); + if (language != null) { + return language; + } + + if (Array.isArray(this.i18nextOptions.fallbackLng)) { + return [...this.i18nextOptions.fallbackLng]; + } + + if (typeof this.i18nextOptions.fallbackLng === 'string') { + return this.i18nextOptions.fallbackLng; + } + + return undefined; + } + + cacheUserLanguage(): void { + return; + } + + /** + * @see http://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html + */ + private _getShellLanguage(lc?: string): string | string[] | undefined { + if (lc == null) return; + + const languages = lc + .split(':') + .map((language) => + language + // Get `en_US` part from `en_US.UTF-8` + .split('.')[0] + // transforms `en_US` to `en-US` + .replace('_', '-'), + ) + .filter((language) => + this.services.languageUtils.isSupportedCode(language), + ) + .map((language) => + this.services.languageUtils.formatLanguageCode(language), + ); + + // https://unix.stackexchange.com/questions/87745/what-does-lc-all-c-do + if (languages.some((l) => l === 'C')) { + return; + } + + if (languages.length === 1 && languages[0] === '') { + return; + } + + if (languages.length === 1) { + return languages[0]; + } + + return languages; + } +} + +export default I18nextCLILanguageDetector; diff --git a/src/main/utils/sleep.ts b/src/main/utils/sleep.ts new file mode 100644 index 000000000..a4f538762 --- /dev/null +++ b/src/main/utils/sleep.ts @@ -0,0 +1,11 @@ +/** + * Inspired by https://github.com/sorenlouv/await-sleep + * Original code has no explicit license + * + * Creates a promise that resolves after the specified number of milliseconds + * @param ms The number of milliseconds to sleep + * @returns A promise that resolves after the specified delay + */ +export const sleep = (ms: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; diff --git a/src/renderer/i18n.js b/src/renderer/i18n.js index 2a04ea4b8..bb1fb2398 100644 --- a/src/renderer/i18n.js +++ b/src/renderer/i18n.js @@ -1,6 +1,6 @@ import i18n from 'i18next'; -import LanguageDetector from 'i18next-browser-languagedetector'; import { initReactI18next } from 'react-i18next'; +import BrowserLanguageDetector from './utils/browserLanguageDetector'; import enGenericComponents from '../../assets/locales/en/genericComponents.json'; import enSystemRequirements from '../../assets/locales/en/systemRequirements.json'; @@ -49,7 +49,7 @@ i18n // .use(Backend) // detect user language // learn more: https://github.com/i18next/i18next-browser-languageDetector - .use(LanguageDetector) + .use(BrowserLanguageDetector) // pass the i18n instance to react-i18next. .use(initReactI18next) // init i18next diff --git a/src/renderer/utils/browserLanguageDetector.js b/src/renderer/utils/browserLanguageDetector.js new file mode 100644 index 000000000..06b90717e --- /dev/null +++ b/src/renderer/utils/browserLanguageDetector.js @@ -0,0 +1,431 @@ +/** + * i18next browser language detector + * Original source: https://github.com/i18next/i18next-browser-languageDetector + * License: MIT (https://github.com/i18next/i18next-browser-languageDetector/blob/master/LICENSE) + */ + +((global, factory) => { + typeof exports === 'object' && typeof module !== 'undefined' + ? (module.exports = factory()) + : typeof define === 'function' && define.amd + ? define(factory) + : ((global = + typeof globalThis !== 'undefined' ? globalThis : global || self), + (global.i18nextBrowserLanguageDetector = factory())); +})(this, () => { + const { slice, forEach } = []; + function defaults(obj) { + forEach.call(slice.call(arguments, 1), (source) => { + if (source) { + for (const prop in source) { + if (obj[prop] === undefined) obj[prop] = source[prop]; + } + } + }); + return obj; + } + + // eslint-disable-next-line no-control-regex + const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; + const serializeCookie = (name, val) => { + const options = + arguments.length > 2 && arguments[2] !== undefined + ? arguments[2] + : { + path: '/', + }; + const opt = options; + const value = encodeURIComponent(val); + let str = `${name}=${value}`; + if (opt.maxAge > 0) { + const maxAge = opt.maxAge - 0; + if (Number.isNaN(maxAge)) throw new Error('maxAge should be a Number'); + str += `; Max-Age=${Math.floor(maxAge)}`; + } + if (opt.domain) { + if (!fieldContentRegExp.test(opt.domain)) { + throw new TypeError('option domain is invalid'); + } + str += `; Domain=${opt.domain}`; + } + if (opt.path) { + if (!fieldContentRegExp.test(opt.path)) { + throw new TypeError('option path is invalid'); + } + str += `; Path=${opt.path}`; + } + if (opt.expires) { + if (typeof opt.expires.toUTCString !== 'function') { + throw new TypeError('option expires is invalid'); + } + str += `; Expires=${opt.expires.toUTCString()}`; + } + if (opt.httpOnly) str += '; HttpOnly'; + if (opt.secure) str += '; Secure'; + if (opt.sameSite) { + const sameSite = + typeof opt.sameSite === 'string' + ? opt.sameSite.toLowerCase() + : opt.sameSite; + switch (sameSite) { + case true: + str += '; SameSite=Strict'; + break; + case 'lax': + str += '; SameSite=Lax'; + break; + case 'strict': + str += '; SameSite=Strict'; + break; + case 'none': + str += '; SameSite=None'; + break; + default: + throw new TypeError('option sameSite is invalid'); + } + } + return str; + }; + const cookie = { + create(name, value, minutes, domain) { + const cookieOptions = + arguments.length > 4 && arguments[4] !== undefined + ? arguments[4] + : { + path: '/', + sameSite: 'strict', + }; + if (minutes) { + cookieOptions.expires = new Date(); + cookieOptions.expires.setTime( + cookieOptions.expires.getTime() + minutes * 60 * 1000, + ); + } + if (domain) cookieOptions.domain = domain; + document.cookie = serializeCookie( + name, + encodeURIComponent(value), + cookieOptions, + ); + }, + read(name) { + const nameEQ = `${name}=`; + const ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) === ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) === 0) + return c.substring(nameEQ.length, c.length); + } + return null; + }, + remove(name) { + this.create(name, '', -1); + }, + }; + var cookie$1 = { + name: 'cookie', + lookup(_ref) { + const { lookupCookie } = _ref; + if (lookupCookie && typeof document !== 'undefined') { + return cookie.read(lookupCookie) || undefined; + } + return undefined; + }, + cacheUserLanguage(lng, _ref2) { + const { lookupCookie, cookieMinutes, cookieDomain, cookieOptions } = + _ref2; + if (lookupCookie && typeof document !== 'undefined') { + cookie.create( + lookupCookie, + lng, + cookieMinutes, + cookieDomain, + cookieOptions, + ); + } + }, + }; + + var querystring = { + name: 'querystring', + lookup(_ref) { + const { lookupQuerystring } = _ref; + let found; + if (typeof window !== 'undefined') { + let { search } = window.location; + if ( + !window.location.search && + window.location.hash?.indexOf('?') > -1 + ) { + search = window.location.hash.substring( + window.location.hash.indexOf('?'), + ); + } + const query = search.substring(1); + const params = query.split('&'); + for (let i = 0; i < params.length; i++) { + const pos = params[i].indexOf('='); + if (pos > 0) { + const key = params[i].substring(0, pos); + if (key === lookupQuerystring) { + found = params[i].substring(pos + 1); + } + } + } + } + return found; + }, + }; + + let hasLocalStorageSupport = null; + const localStorageAvailable = () => { + if (hasLocalStorageSupport !== null) return hasLocalStorageSupport; + try { + hasLocalStorageSupport = + typeof window !== 'undefined' && window.localStorage !== null; + if (!hasLocalStorageSupport) { + return false; + } + const testKey = 'i18next.translate.boo'; + window.localStorage.setItem(testKey, 'foo'); + window.localStorage.removeItem(testKey); + } catch (e) { + hasLocalStorageSupport = false; + } + return hasLocalStorageSupport; + }; + var localStorage = { + name: 'localStorage', + lookup(_ref) { + const { lookupLocalStorage } = _ref; + if (lookupLocalStorage && localStorageAvailable()) { + return window.localStorage.getItem(lookupLocalStorage) || undefined; + } + return undefined; + }, + cacheUserLanguage(lng, _ref2) { + const { lookupLocalStorage } = _ref2; + if (lookupLocalStorage && localStorageAvailable()) { + window.localStorage.setItem(lookupLocalStorage, lng); + } + }, + }; + + let hasSessionStorageSupport = null; + const sessionStorageAvailable = () => { + if (hasSessionStorageSupport !== null) return hasSessionStorageSupport; + try { + hasSessionStorageSupport = + typeof window !== 'undefined' && window.sessionStorage !== null; + if (!hasSessionStorageSupport) { + return false; + } + const testKey = 'i18next.translate.boo'; + window.sessionStorage.setItem(testKey, 'foo'); + window.sessionStorage.removeItem(testKey); + } catch (e) { + hasSessionStorageSupport = false; + } + return hasSessionStorageSupport; + }; + var sessionStorage = { + name: 'sessionStorage', + lookup(_ref) { + const { lookupSessionStorage } = _ref; + if (lookupSessionStorage && sessionStorageAvailable()) { + return window.sessionStorage.getItem(lookupSessionStorage) || undefined; + } + return undefined; + }, + cacheUserLanguage(lng, _ref2) { + const { lookupSessionStorage } = _ref2; + if (lookupSessionStorage && sessionStorageAvailable()) { + window.sessionStorage.setItem(lookupSessionStorage, lng); + } + }, + }; + + var navigator$1 = { + name: 'navigator', + lookup(options) { + const found = []; + if (typeof navigator !== 'undefined') { + const { languages, userLanguage, language } = navigator; + if (languages) { + for (let i = 0; i < languages.length; i++) { + found.push(languages[i]); + } + } + if (userLanguage) { + found.push(userLanguage); + } + if (language) { + found.push(language); + } + } + return found.length > 0 ? found : undefined; + }, + }; + + var htmlTag = { + name: 'htmlTag', + lookup(_ref) { + const { htmlTag } = _ref; + let found; + const internalHtmlTag = + htmlTag || + (typeof document !== 'undefined' ? document.documentElement : null); + if ( + internalHtmlTag && + typeof internalHtmlTag.getAttribute === 'function' + ) { + found = internalHtmlTag.getAttribute('lang'); + } + return found; + }, + }; + + var path = { + name: 'path', + lookup(_ref) { + const { lookupFromPathIndex } = _ref; + if (typeof window === 'undefined') return undefined; + const language = window.location.pathname.match(/\/([a-zA-Z-]*)/g); + if (!Array.isArray(language)) return undefined; + const index = + typeof lookupFromPathIndex === 'number' ? lookupFromPathIndex : 0; + return language[index]?.replace('/', ''); + }, + }; + + var subdomain = { + name: 'subdomain', + lookup(_ref) { + const { lookupFromSubdomainIndex } = _ref; + const internalLookupFromSubdomainIndex = + typeof lookupFromSubdomainIndex === 'number' + ? lookupFromSubdomainIndex + 1 + : 1; + const language = + typeof window !== 'undefined' && + window.location?.hostname?.match( + /^(\w{2,5})\.(([a-z0-9-]{1,63}\.[a-z]{2,6})|localhost)/i, + ); + + if (!language) return undefined; + return language[internalLookupFromSubdomainIndex]; + }, + }; + + let canCookies = false; + try { + document.cookie; + canCookies = true; + } catch (e) {} + const order = [ + 'querystring', + 'cookie', + 'localStorage', + 'sessionStorage', + 'navigator', + 'htmlTag', + ]; + if (!canCookies) order.splice(1, 1); + const getDefaults = () => ({ + order, + lookupQuerystring: 'lng', + lookupCookie: 'i18next', + lookupLocalStorage: 'i18nextLng', + lookupSessionStorage: 'i18nextLng', + caches: ['localStorage'], + excludeCacheFor: ['cimode'], + convertDetectedLanguage: (l) => l, + }); + class Browser { + constructor(services) { + const options = + arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + this.type = 'languageDetector'; + this.detectors = {}; + this.init(services, options); + } + init() { + const services = + arguments.length > 0 && arguments[0] !== undefined + ? arguments[0] + : { + languageUtils: {}, + }; + const options = + arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + const i18nOptions = + arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + this.services = services; + this.options = defaults(options, this.options || {}, getDefaults()); + if ( + typeof this.options.convertDetectedLanguage === 'string' && + this.options.convertDetectedLanguage.indexOf('15897') > -1 + ) { + this.options.convertDetectedLanguage = (l) => l.replace('-', '_'); + } + + if (this.options.lookupFromUrlIndex) + this.options.lookupFromPathIndex = this.options.lookupFromUrlIndex; + this.i18nOptions = i18nOptions; + this.addDetector(cookie$1); + this.addDetector(querystring); + this.addDetector(localStorage); + this.addDetector(sessionStorage); + this.addDetector(navigator$1); + this.addDetector(htmlTag); + this.addDetector(path); + this.addDetector(subdomain); + } + addDetector(detector) { + this.detectors[detector.name] = detector; + return this; + } + detect() { + const detectionOrder = + arguments.length > 0 && arguments[0] !== undefined + ? arguments[0] + : this.options.order; + let detected = []; + detectionOrder.forEach((detectorName) => { + if (this.detectors[detectorName]) { + let lookup = this.detectors[detectorName].lookup(this.options); + if (lookup && typeof lookup === 'string') lookup = [lookup]; + if (lookup) detected = detected.concat(lookup); + } + }); + detected = detected.map((d) => this.options.convertDetectedLanguage(d)); + if ( + this.services && + this.services.languageUtils && + this.services.languageUtils.getBestMatchFromCodes + ) + return detected; + return detected.length > 0 ? detected[0] : null; + } + cacheUserLanguage(lng) { + const caches = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : this.options.caches; + if (!caches) return; + if ( + this.options.excludeCacheFor && + this.options.excludeCacheFor.indexOf(lng) > -1 + ) + return; + caches.forEach((cacheName) => { + if (this.detectors[cacheName]) + this.detectors[cacheName].cacheUserLanguage(lng, this.options); + }); + } + } + Browser.type = 'languageDetector'; + + return Browser; +});