⤴ Rebased from master
This commit is contained in:
@@ -56,18 +56,20 @@ const generateUserToken = (user) => {
|
||||
*/
|
||||
export const isLoggedIn = () => {
|
||||
const users = getUsers();
|
||||
const validTokens = users.map((user) => generateUserToken(user));
|
||||
let userAuthenticated = false;
|
||||
document.cookie.split(';').forEach((cookie) => {
|
||||
let userAuthenticated = document.cookie.split(';').some((cookie) => {
|
||||
if (cookie && cookie.split('=').length > 1) {
|
||||
const cookieKey = cookie.split('=')[0].trim();
|
||||
const cookieValue = cookie.split('=')[1].trim();
|
||||
if (cookieKey === cookieKeys.AUTH_TOKEN) {
|
||||
if (validTokens.includes(cookieValue)) {
|
||||
userAuthenticated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
userAuthenticated = users.some((user) => {
|
||||
if (generateUserToken(user) === cookieValue) {
|
||||
localStorage.setItem(localStorageKeys.USERNAME, user.user);
|
||||
return true;
|
||||
} else return false;
|
||||
});
|
||||
return userAuthenticated;
|
||||
} else return false;
|
||||
} else return false;
|
||||
});
|
||||
return userAuthenticated;
|
||||
};
|
||||
@@ -127,7 +129,7 @@ export const login = (username, pass, timeout) => {
|
||||
const now = new Date();
|
||||
const expiry = new Date(now.setTime(now.getTime() + timeout)).toGMTString();
|
||||
const userObject = { user: username, hash: sha256(pass).toString().toLowerCase() };
|
||||
document.cookie = `authenticationToken=${generateUserToken(userObject)};`
|
||||
document.cookie = `${cookieKeys.AUTH_TOKEN}=${generateUserToken(userObject)};`
|
||||
+ `${timeout > 0 ? `expires=${expiry}` : ''}`;
|
||||
localStorage.setItem(localStorageKeys.USERNAME, username);
|
||||
};
|
||||
@@ -136,7 +138,7 @@ export const login = (username, pass, timeout) => {
|
||||
* Removed the browsers' cookie, causing user to be logged out
|
||||
*/
|
||||
export const logout = () => {
|
||||
document.cookie = 'authenticationToken=null';
|
||||
document.cookie = `${cookieKeys.AUTH_TOKEN}=null`;
|
||||
localStorage.removeItem(localStorageKeys.USERNAME);
|
||||
};
|
||||
|
||||
@@ -152,7 +154,7 @@ export const getCurrentUser = () => {
|
||||
let foundUserObject = false; // Value to return
|
||||
getUsers().forEach((user) => {
|
||||
// If current logged-in user found, then return that user
|
||||
if (user.user === username) foundUserObject = user;
|
||||
if (user.user.toLowerCase() === username.toLowerCase()) foundUserObject = user;
|
||||
});
|
||||
return foundUserObject;
|
||||
};
|
||||
@@ -161,10 +163,10 @@ export const getCurrentUser = () => {
|
||||
* Checks if the user is viewing the dashboard as a guest
|
||||
* Returns true if guest mode enabled, and user not logged in
|
||||
* */
|
||||
export const isLoggedInAsGuest = () => {
|
||||
export const isLoggedInAsGuest = (currentUser) => {
|
||||
const guestEnabled = isGuestAccessEnabled();
|
||||
const notLoggedIn = !isLoggedIn();
|
||||
return guestEnabled && notLoggedIn;
|
||||
const loggedIn = isLoggedIn() && currentUser;
|
||||
return guestEnabled && !loggedIn;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -182,7 +184,7 @@ export const isUserAdmin = () => {
|
||||
const currentUser = localStorage[localStorageKeys.USERNAME];
|
||||
let isAdmin = false;
|
||||
users.forEach((user) => {
|
||||
if (user.user === currentUser) {
|
||||
if (user.user.toLowerCase() === currentUser.toLowerCase()) {
|
||||
if (user.type === 'admin') isAdmin = true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,15 +5,14 @@
|
||||
*/
|
||||
|
||||
// Import helper functions from auth, to get current user, and check if guest
|
||||
import { getCurrentUser, isLoggedInAsGuest } from '@/utils/Auth';
|
||||
import { getCurrentUser } from '@/utils/Auth';
|
||||
import { isVisibleToUser } from '@/utils/IsVisibleToUser';
|
||||
|
||||
/* Putting it all together, the function to export */
|
||||
export const checkItemVisibility = (item) => {
|
||||
const currentUser = getCurrentUser(); // Get current user object
|
||||
const isGuest = isLoggedInAsGuest(); // Check if current user is a guest
|
||||
const displayData = item.displayData || {};
|
||||
return isVisibleToUser(displayData, currentUser, isGuest);
|
||||
return isVisibleToUser(displayData, currentUser);
|
||||
};
|
||||
|
||||
export default checkItemVisibility;
|
||||
|
||||
@@ -5,16 +5,15 @@
|
||||
*/
|
||||
|
||||
// Import helper functions from auth, to get current user, and check if guest
|
||||
import { getCurrentUser, isLoggedInAsGuest } from '@/utils/Auth';
|
||||
import { getCurrentUser } from '@/utils/Auth';
|
||||
import { isVisibleToUser } from '@/utils/IsVisibleToUser';
|
||||
|
||||
/* Putting it all together, the function to export */
|
||||
export const checkSectionVisibility = (sections) => {
|
||||
const currentUser = getCurrentUser(); // Get current user object
|
||||
const isGuest = isLoggedInAsGuest(); // Check if current user is a guest
|
||||
return sections.filter((currentSection) => {
|
||||
const displayData = currentSection.displayData || {};
|
||||
return isVisibleToUser(displayData, currentUser, isGuest);
|
||||
return isVisibleToUser(displayData, currentUser);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -193,6 +193,11 @@
|
||||
"default": "allesedv",
|
||||
"description": "Which service to use to resolve favicons. Set to local to do this locally instead"
|
||||
},
|
||||
"defaultIcon": {
|
||||
"title": "Default Icon",
|
||||
"type": "string",
|
||||
"description": "An icon to apply to any items which don't yet have the icon set"
|
||||
},
|
||||
"layout": {
|
||||
"title": "Default Layout",
|
||||
"type": "string",
|
||||
@@ -476,6 +481,11 @@
|
||||
"type": "string",
|
||||
"description": "The Client ID of the client you created for use with Dashy"
|
||||
},
|
||||
"idpHint": {
|
||||
"title" : "IdP hint",
|
||||
"type": "string",
|
||||
"description": "Set to the 'Alias' of an existing Identity Provider in the specified realm to skip the Keycloak login page and redirect straight to the external IdP for authentication"
|
||||
},
|
||||
"legacySupport": {
|
||||
"title": "Legacy Support",
|
||||
"type": "boolean",
|
||||
@@ -509,6 +519,12 @@
|
||||
"default": false,
|
||||
"description": "If set to true, no users will be able to view or edit the config through the UI"
|
||||
},
|
||||
"disableConfigurationForNonAdmin": {
|
||||
"title": "Disable all UI Config for non admin users.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If set to true, only admin users will be able to view or edit the config through the UI. disableConfiguration must not be set to true."
|
||||
},
|
||||
"allowConfigEdit": {
|
||||
"title": "Allow Config Editing",
|
||||
"type": "boolean",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
// Import helper functions from auth, to get current user, and check if guest
|
||||
import { localStorageKeys } from '@/utils/defaults';
|
||||
import { isLoggedInAsGuest } from '@/utils/Auth';
|
||||
|
||||
/* Helper function, checks if a given testValue is found in the visibility list */
|
||||
const determineVisibility = (visibilityList, testValue) => {
|
||||
@@ -25,7 +26,9 @@ const determineIntersection = (source = [], target = []) => {
|
||||
|
||||
/* Returns false if the displayData of a section/item
|
||||
should not be rendered for the current user/ guest */
|
||||
export const isVisibleToUser = (displayData, currentUser, isGuest) => {
|
||||
export const isVisibleToUser = (displayData, currentUser) => {
|
||||
const isGuest = isLoggedInAsGuest(currentUser); // Check if current user is a guest
|
||||
|
||||
// Checks if user explicitly has access to a certain section
|
||||
const checkVisibility = () => {
|
||||
if (!currentUser) return true;
|
||||
|
||||
@@ -13,25 +13,25 @@ class KeycloakAuth {
|
||||
constructor() {
|
||||
const { auth } = getAppConfig();
|
||||
const {
|
||||
serverUrl, realm, clientId, legacySupport,
|
||||
serverUrl, realm, clientId, idpHint, legacySupport,
|
||||
} = auth.keycloak;
|
||||
const url = legacySupport ? `${serverUrl}/auth` : serverUrl;
|
||||
const initOptions = {
|
||||
url, realm, clientId, onLoad: 'login-required',
|
||||
};
|
||||
const initOptions = { url, realm, clientId };
|
||||
const loginOptions = idpHint ? { idpHint } : {};
|
||||
|
||||
this.loginOptions = loginOptions;
|
||||
this.keycloakClient = Keycloak(initOptions);
|
||||
}
|
||||
|
||||
login() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.keycloakClient.init({ onLoad: 'login-required' })
|
||||
this.keycloakClient.init({ onLoad: 'check-sso' })
|
||||
.then((auth) => {
|
||||
if (auth) {
|
||||
this.storeKeycloakInfo();
|
||||
return resolve();
|
||||
} else {
|
||||
return reject(new Error('Not authenticated'));
|
||||
return this.keycloakClient.login(this.loginOptions);
|
||||
}
|
||||
})
|
||||
.catch((reason) => reject(reason));
|
||||
|
||||
@@ -64,8 +64,8 @@ export const getCountryFlag = (countryCode, dimens) => {
|
||||
|
||||
/* Given a currency code, return path to corresponding countries flag icon */
|
||||
export const getCurrencyFlag = (currency) => {
|
||||
const cdn = 'https://raw.githubusercontent.com/transferwise/currency-flags';
|
||||
return `${cdn}/master/src/flags/${currency.toLowerCase()}.png`;
|
||||
const cdn = 'https://raw.githubusercontent.com/Lissy93/currency-flags';
|
||||
return `${cdn}/master/assets/flags_png_rectangle/${currency.toLowerCase()}.png`;
|
||||
};
|
||||
|
||||
/* Given a Latitude & Longitude object, and optional zoom level, return link to OSM */
|
||||
@@ -141,12 +141,27 @@ export const getTimeDifference = (startTime, endTime) => {
|
||||
const msDifference = new Date(endTime).getTime() - new Date(startTime).getTime();
|
||||
const diff = Math.abs(Math.round(msDifference / 1000));
|
||||
const divide = (time, round) => Math.round(time / round);
|
||||
if (diff < 60) return `${divide(diff, 1)} seconds`;
|
||||
if (diff < 3600) return `${divide(diff, 60)} minutes`;
|
||||
if (diff < 86400) return `${divide(diff, 3600)} hours`;
|
||||
if (diff < 604800) return `${divide(diff, 86400)} days`;
|
||||
if (diff < 31557600) return `${divide(diff, 604800)} weeks`;
|
||||
if (diff >= 31557600) return `${divide(diff, 31557600)} years`;
|
||||
|
||||
const periods = [
|
||||
{ noun: 'second', value: 1 },
|
||||
{ noun: 'minute', value: 60 },
|
||||
{ noun: 'hour', value: 3600 },
|
||||
{ noun: 'day', value: 86400 },
|
||||
{ noun: 'week', value: 604800 },
|
||||
{ noun: 'fortnight', value: 1209600 },
|
||||
{ noun: 'month', value: 2628000 },
|
||||
{ noun: 'year', value: 31557600 },
|
||||
];
|
||||
|
||||
for (let idx = 0; idx < periods.length; idx += 1) {
|
||||
if (diff < (periods[idx + 1]?.value ?? Infinity)) {
|
||||
const period = periods[idx];
|
||||
const value = divide(diff, period.value);
|
||||
const noun = value === 1 ? period.noun : `${period.noun}s`;
|
||||
return `${value} ${noun}`;
|
||||
}
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
};
|
||||
|
||||
@@ -165,6 +180,11 @@ export const getValueFromCss = (colorVar) => {
|
||||
return cssProps.getPropertyValue(`--${colorVar}`).trim();
|
||||
};
|
||||
|
||||
/* Given a temperature in Celsius, returns value in Fahrenheit */
|
||||
export const celsiusToFahrenheit = (celsius) => {
|
||||
return Math.round((celsius * 1.8) + 32);
|
||||
};
|
||||
|
||||
/* Given a temperature in Fahrenheit, returns value in Celsius */
|
||||
export const fahrenheitToCelsius = (fahrenheit) => {
|
||||
return Math.round(((fahrenheit - 32) * 5) / 9);
|
||||
|
||||
@@ -11,7 +11,7 @@ import ErrorHandler from '@/utils/ErrorHandler';
|
||||
const getDomainFromUrl = (url) => {
|
||||
if (!url) return '';
|
||||
const urlPattern = /^(?:https?:\/\/)?(?:w{3}\.)?([a-z\d.-]+)\.(?:[a-z.]{2,10})(?:[/\w.-]*)*/;
|
||||
const domainPattern = url.match(urlPattern);
|
||||
const domainPattern = urlPattern.exec(url);
|
||||
return domainPattern ? domainPattern[1] : '';
|
||||
};
|
||||
|
||||
@@ -24,8 +24,8 @@ const getDomainFromUrl = (url) => {
|
||||
*/
|
||||
const filterHelper = (compareStr, searchStr) => {
|
||||
if (!compareStr) return false;
|
||||
const process = (input) => input && input.toString().toLowerCase().replace(/[^\w\s]/gi, '');
|
||||
return process(compareStr).includes(process(searchStr));
|
||||
const process = (input) => input?.toString().toLowerCase().replace(/[^\w\s]/gi, '');
|
||||
return process(searchStr).split(/\s/).every(word => process(compareStr).includes(word));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -136,7 +136,7 @@ module.exports = {
|
||||
},
|
||||
/* Key names for cookie identifiers */
|
||||
cookieKeys: {
|
||||
AUTH_TOKEN: 'authenticationToken',
|
||||
AUTH_TOKEN: 'dashyAuthToken',
|
||||
},
|
||||
/* Key names for session storage identifiers */
|
||||
sessionStorageKeys: {
|
||||
@@ -206,13 +206,13 @@ module.exports = {
|
||||
/* The URL to CDNs used for external icons. These are only loaded when required */
|
||||
iconCdns: {
|
||||
fa: 'https://kit.fontawesome.com',
|
||||
mdi: 'https://cdn.jsdelivr.net/npm/@mdi/font@5.9.55/css/materialdesignicons.min.css',
|
||||
si: 'https://unpkg.com/simple-icons@v5/icons',
|
||||
mdi: 'https://cdn.jsdelivr.net/npm/@mdi/font@7.0.96/css/materialdesignicons.min.css',
|
||||
si: 'https://unpkg.com/simple-icons@v7/icons',
|
||||
generative: 'https://avatars.dicebear.com/api/identicon/{icon}.svg',
|
||||
generativeFallback: 'https://evatar.io/{icon}',
|
||||
localPath: './item-icons',
|
||||
faviconName: 'favicon.ico',
|
||||
homeLabIcons: 'https://raw.githubusercontent.com/walkxhub/dashboard-icons/master/png/{icon}.png',
|
||||
homeLabIcons: 'https://raw.githubusercontent.com/walkxcode/dashboard-icons/master/png/{icon}.png',
|
||||
homeLabIconsFallback: 'https://raw.githubusercontent.com/NX211/homer-icons/master/png/{icon}.png',
|
||||
},
|
||||
/* API endpoints for widgets that need to fetch external data */
|
||||
@@ -236,6 +236,7 @@ module.exports = {
|
||||
jokes: 'https://v2.jokeapi.dev/joke/',
|
||||
news: 'https://api.currentsapi.services/v1/latest-news',
|
||||
mullvad: 'https://am.i.mullvad.net/json',
|
||||
mvg: 'https://www.mvg.de/api/fib/v2/',
|
||||
publicIp: 'https://ipapi.co/json',
|
||||
publicIp2: 'https://api.ipgeolocation.io/ipgeo',
|
||||
publicIp3: 'http://ip-api.com/json',
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
// Locales - Import translation files here!
|
||||
// Locales - Import translation files here! (sort alphabetically)
|
||||
import en from '@/assets/locales/en.json';
|
||||
import ar from '@/assets/locales/ar.json';
|
||||
import bg from '@/assets/locales/bg.json';
|
||||
import bn from '@/assets/locales/bn.json';
|
||||
import cs from '@/assets/locales/cs.json';
|
||||
import da from '@/assets/locales/da.json';
|
||||
import de from '@/assets/locales/de.json';
|
||||
import el from '@/assets/locales/el.json';
|
||||
import es from '@/assets/locales/es.json';
|
||||
import fr from '@/assets/locales/fr.json';
|
||||
import hi from '@/assets/locales/hi.json';
|
||||
import it from '@/assets/locales/it.json';
|
||||
import ja from '@/assets/locales/ja.json';
|
||||
import ko from '@/assets/locales/ko.json';
|
||||
import nb from '@/assets/locales/nb.json';
|
||||
import nl from '@/assets/locales/nl.json';
|
||||
import pl from '@/assets/locales/pl.json';
|
||||
import fr from '@/assets/locales/fr.json';
|
||||
import sl from '@/assets/locales/sl.json';
|
||||
import es from '@/assets/locales/es.json';
|
||||
import it from '@/assets/locales/it.json';
|
||||
import zhCN from '@/assets/locales/zh-CN.json';
|
||||
import zhTW from '@/assets/locales/zh-TW.json';
|
||||
import ar from '@/assets/locales/ar.json';
|
||||
import hi from '@/assets/locales/hi.json';
|
||||
import ja from '@/assets/locales/ja.json';
|
||||
import pt from '@/assets/locales/pt.json';
|
||||
import ru from '@/assets/locales/ru.json';
|
||||
import nb from '@/assets/locales/nb.json';
|
||||
import pirate from '@/assets/locales/zz-pirate.json';
|
||||
import sk from '@/assets/locales/sk.json';
|
||||
import sl from '@/assets/locales/sl.json';
|
||||
import sv from '@/assets/locales/sv.json';
|
||||
import bg from '@/assets/locales/bg.json';
|
||||
import ko from '@/assets/locales/ko.json';
|
||||
import tr from '@/assets/locales/tr.json';
|
||||
import zhCN from '@/assets/locales/zh-CN.json';
|
||||
import zhTW from '@/assets/locales/zh-TW.json';
|
||||
import pirate from '@/assets/locales/zz-pirate.json';
|
||||
|
||||
// Language data - Next register your language by adding it to this list
|
||||
// Sorted alphabetically by code (except English which is first, and specials at the end)
|
||||
export const languages = [
|
||||
{
|
||||
name: 'English',
|
||||
@@ -28,14 +35,92 @@ export const languages = [
|
||||
locale: en,
|
||||
flag: '🇬🇧',
|
||||
},
|
||||
{ // Arabic
|
||||
name: 'العربية',
|
||||
code: 'ar',
|
||||
locale: ar,
|
||||
flag: '🇦🇪',
|
||||
},
|
||||
{ // Bulgarian
|
||||
name: 'Български',
|
||||
code: 'bg',
|
||||
locale: bg,
|
||||
flag: '🇧🇬',
|
||||
},
|
||||
{ // Bengali
|
||||
name: 'বাংলা',
|
||||
code: 'bn',
|
||||
locale: bn,
|
||||
flag: '🇧🇩',
|
||||
},
|
||||
{ // Czech
|
||||
name: 'Čeština',
|
||||
code: 'cs',
|
||||
locale: cs,
|
||||
flag: '🇨🇿',
|
||||
},
|
||||
{ // Danish
|
||||
name: 'Dansk',
|
||||
code: 'da',
|
||||
locale: da,
|
||||
flag: '🇩🇰',
|
||||
},
|
||||
{ // German
|
||||
name: 'Deutsch',
|
||||
code: 'de',
|
||||
locale: de,
|
||||
flag: '🇩🇪',
|
||||
},
|
||||
{ // Greek
|
||||
name: 'Ελληνικά',
|
||||
code: 'el',
|
||||
locale: el,
|
||||
flag: '🇬🇷',
|
||||
},
|
||||
{ // Spanish
|
||||
name: 'Español',
|
||||
code: 'es',
|
||||
locale: es,
|
||||
flag: '🇪🇸',
|
||||
},
|
||||
{
|
||||
name: 'Dutch',
|
||||
name: 'Français',
|
||||
code: 'fr',
|
||||
locale: fr,
|
||||
flag: '🇲🇫',
|
||||
},
|
||||
{ // Hindi
|
||||
name: 'नहीं',
|
||||
code: 'hi',
|
||||
locale: hi,
|
||||
flag: '🇮🇳',
|
||||
},
|
||||
{ // Italian
|
||||
name: 'Italiano',
|
||||
code: 'it',
|
||||
locale: it,
|
||||
flag: '🇮🇹',
|
||||
},
|
||||
{ // Japanese
|
||||
name: '日本語',
|
||||
code: 'ja',
|
||||
locale: ja,
|
||||
flag: '🇯🇵',
|
||||
},
|
||||
{ // Korean
|
||||
name: '한국어',
|
||||
code: 'ko',
|
||||
locale: ko,
|
||||
flag: '🇰🇷',
|
||||
},
|
||||
{ // Norwegian
|
||||
name: 'Norsk',
|
||||
code: 'nb',
|
||||
locale: nb,
|
||||
flag: '🇳🇴',
|
||||
},
|
||||
{ // Dutch
|
||||
name: 'Nederlands',
|
||||
code: 'nl',
|
||||
locale: nl,
|
||||
flag: '🇳🇱',
|
||||
@@ -46,60 +131,6 @@ export const languages = [
|
||||
locale: pl,
|
||||
flag: '🇵🇱',
|
||||
},
|
||||
{
|
||||
name: 'Français',
|
||||
code: 'fr',
|
||||
locale: fr,
|
||||
flag: '🇲🇫',
|
||||
},
|
||||
{
|
||||
name: 'Slovenščina',
|
||||
code: 'sl',
|
||||
locale: sl,
|
||||
flag: '🇸🇮',
|
||||
},
|
||||
{ // Spanish
|
||||
name: 'Español',
|
||||
code: 'es',
|
||||
locale: es,
|
||||
flag: '🇪🇸',
|
||||
},
|
||||
{ // Italian
|
||||
name: 'Italiano',
|
||||
code: 'it',
|
||||
locale: it,
|
||||
flag: '🇮🇹',
|
||||
},
|
||||
{ // Chinese
|
||||
name: '简体中文',
|
||||
code: 'cn',
|
||||
locale: zhCN,
|
||||
flag: '🇨🇳',
|
||||
},
|
||||
{ // Chinese
|
||||
name: '繁體中文',
|
||||
code: 'zh-TW',
|
||||
locale: zhTW,
|
||||
flag: '🇹🇼',
|
||||
},
|
||||
{ // Arabic
|
||||
name: 'العربية',
|
||||
code: 'ar',
|
||||
locale: ar,
|
||||
flag: '🇦🇪',
|
||||
},
|
||||
{ // Hindi
|
||||
name: 'नहीं',
|
||||
code: 'hi',
|
||||
locale: hi,
|
||||
flag: '🇮🇳',
|
||||
},
|
||||
{ // Japanese
|
||||
name: '日本語',
|
||||
code: 'ja',
|
||||
locale: ja,
|
||||
flag: '🇯🇵',
|
||||
},
|
||||
{ // Portuguese
|
||||
name: 'Português',
|
||||
code: 'pt',
|
||||
@@ -112,17 +143,17 @@ export const languages = [
|
||||
locale: ru,
|
||||
flag: '🇷🇺',
|
||||
},
|
||||
{ // Norwegian
|
||||
name: 'Norsk',
|
||||
code: 'nb',
|
||||
locale: nb,
|
||||
flag: '🇳🇴',
|
||||
{ // Slovak
|
||||
name: 'Slovenčina',
|
||||
code: 'sk',
|
||||
locale: sk,
|
||||
flag: '🇸🇰',
|
||||
},
|
||||
{ // Joke Language - Pirate
|
||||
name: 'Pirate',
|
||||
code: 'pirate',
|
||||
locale: pirate,
|
||||
flag: '🏴☠️',
|
||||
{
|
||||
name: 'Slovenščina',
|
||||
code: 'sl',
|
||||
locale: sl,
|
||||
flag: '🇸🇮',
|
||||
},
|
||||
{ // Swedish
|
||||
name: 'Svenska',
|
||||
@@ -130,17 +161,29 @@ export const languages = [
|
||||
locale: sv,
|
||||
flag: '🇸🇪',
|
||||
},
|
||||
{ // Bulgarian
|
||||
name: 'Български',
|
||||
code: 'bg',
|
||||
locale: bg,
|
||||
flag: '🇧🇬',
|
||||
{ // Turkish
|
||||
name: 'Türkçe',
|
||||
code: 'tr',
|
||||
locale: tr,
|
||||
flag: '🇹🇷',
|
||||
},
|
||||
{ // Korean
|
||||
name: '한국어',
|
||||
code: 'ko',
|
||||
locale: ko,
|
||||
flag: '🇰🇷',
|
||||
{ // Chinese
|
||||
name: '简体中文',
|
||||
code: 'cn',
|
||||
locale: zhCN,
|
||||
flag: '🇨🇳',
|
||||
},
|
||||
{ // Chinese
|
||||
name: '繁體中文',
|
||||
code: 'zh-TW',
|
||||
locale: zhTW,
|
||||
flag: '🇹🇼',
|
||||
},
|
||||
{ // Joke Language - Pirate
|
||||
name: 'Pirate',
|
||||
code: 'pirate',
|
||||
locale: pirate,
|
||||
flag: '🏴☠️',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user