Merge branch 'master' into lang-pt-pt

This commit is contained in:
Diogo Correia 2020-10-01 09:14:41 +01:00
commit 8abebe0416
79 changed files with 578 additions and 432 deletions

View File

@ -37,7 +37,7 @@ export default function DropDown({
return ( return (
<div ref={ref} className={classNames(styles.dropdown, className)} onClick={handleShowMenu}> <div ref={ref} className={classNames(styles.dropdown, className)} onClick={handleShowMenu}>
<div className={styles.value}> <div className={styles.value}>
{options.find(e => e.value === value)?.label || value} <div className={styles.text}>{options.find(e => e.value === value)?.label || value}</div>
<Icon icon={<Chevron />} className={styles.icon} size="small" /> <Icon icon={<Chevron />} className={styles.icon} size="small" />
</div> </div>
{showMenu && ( {showMenu && (

View File

@ -19,6 +19,10 @@
min-width: 160px; min-width: 160px;
} }
.text {
flex: 1;
}
.icon { .icon {
padding-left: 20px; padding-left: 20px;
} }

View File

@ -0,0 +1,47 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import useVersion from 'hooks/useVersion';
import styles from './UpdateNotice.module.css';
import ButtonLayout from '../layout/ButtonLayout';
import Button from './Button';
import useForceUpdate from '../../hooks/useForceUpdate';
export default function UpdateNotice() {
const forceUpdte = useForceUpdate();
const { hasUpdate, latest, updateCheck } = useVersion();
function handleViewClick() {
location.href = 'https://github.com/mikecao/umami/releases';
updateCheck();
forceUpdte();
}
function handleDismissClick() {
updateCheck();
forceUpdte();
}
if (!hasUpdate) {
return null;
}
return (
<div className={styles.notice}>
<div className={styles.message}>
<FormattedMessage
id="message.new-version-available"
defaultMessage="A new version of umami {version} is available!"
values={{ version: `v${latest}` }}
/>
</div>
<ButtonLayout>
<Button size="xsmall" variant="action" onClick={handleViewClick}>
<FormattedMessage id="button.view-details" defaultMessage="View details" />
</Button>
<Button size="xsmall" onClick={handleDismissClick}>
<FormattedMessage id="button.dismiss" defaultMessage="Dismiss" />
</Button>
</ButtonLayout>
</div>
);
}

View File

@ -0,0 +1,13 @@
.notice {
display: flex;
justify-content: center;
align-items: center;
padding-top: 10px;
font-size: var(--font-size-small);
font-weight: 600;
}
.message {
text-align: center;
margin-right: 20px;
}

View File

@ -6,6 +6,8 @@ import tinycolor from 'tinycolor2';
import useTheme from 'hooks/useTheme'; import useTheme from 'hooks/useTheme';
import { THEME_COLORS } from 'lib/constants'; import { THEME_COLORS } from 'lib/constants';
import styles from './WorldMap.module.css'; import styles from './WorldMap.module.css';
import useCountryNames from 'hooks/useCountryNames';
import useLocale from 'hooks/useLocale';
const geoUrl = '/world-110m.json'; const geoUrl = '/world-110m.json';
@ -21,6 +23,8 @@ export default function WorldMap({ data, className }) {
}), }),
[theme], [theme],
); );
const [locale] = useLocale();
const countryNames = useCountryNames(locale);
function getFillColor(code) { function getFillColor(code) {
if (code === 'AQ') return; if (code === 'AQ') return;
@ -39,10 +43,10 @@ export default function WorldMap({ data, className }) {
return code === 'AQ' ? 0 : 1; return code === 'AQ' ? 0 : 1;
} }
function handleHover({ ISO_A2: code, NAME: name }) { function handleHover(code) {
if (code === 'AQ') return; if (code === 'AQ') return;
const country = data?.find(({ x }) => x === code); const country = data?.find(({ x }) => x === code);
setTooltip(`${name}: ${country?.y || 0} visitors`); setTooltip(`${countryNames[code]}: ${country?.y || 0} visitors`);
} }
return ( return (
@ -70,7 +74,7 @@ export default function WorldMap({ data, className }) {
hover: { outline: 'none', fill: colors.hoverColor }, hover: { outline: 'none', fill: colors.hoverColor },
pressed: { outline: 'none' }, pressed: { outline: 'none' },
}} }}
onMouseOver={() => handleHover(geo.properties)} onMouseOver={() => handleHover(code)}
onMouseOut={() => setTooltip(null)} onMouseOut={() => setTooltip(null)}
/> />
); );

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Formik, Form, Field } from 'formik'; import { Formik, Form, Field } from 'formik';
import { useRouter } from 'next/router';
import { post } from 'lib/web'; import { post } from 'lib/web';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
import FormLayout, { import FormLayout, {
@ -29,18 +30,17 @@ const validate = ({ user_id, username, password }) => {
}; };
export default function AccountEditForm({ values, onSave, onClose }) { export default function AccountEditForm({ values, onSave, onClose }) {
const { basePath } = useRouter();
const [message, setMessage] = useState(); const [message, setMessage] = useState();
const handleSubmit = async values => { const handleSubmit = async values => {
const response = await post(`/api/account`, values); const { ok, data } = await post(`${basePath}/api/account`, values);
if (typeof response !== 'string') { if (ok) {
onSave(); onSave();
} else { } else {
setMessage( setMessage(
response || ( data || <FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />
),
); );
} }
}; };

View File

@ -1,5 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { useRouter } from 'next/router';
import { Formik, Form, Field } from 'formik'; import { Formik, Form, Field } from 'formik';
import { post } from 'lib/web'; import { post } from 'lib/web';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
@ -37,18 +38,17 @@ const validate = ({ current_password, new_password, confirm_password }) => {
}; };
export default function ChangePasswordForm({ values, onSave, onClose }) { export default function ChangePasswordForm({ values, onSave, onClose }) {
const { basePath } = useRouter();
const [message, setMessage] = useState(); const [message, setMessage] = useState();
const handleSubmit = async values => { const handleSubmit = async values => {
const response = await post(`/api/account/password`, values); const { ok, data } = await post(`${basePath}/api/account/password`, values);
if (typeof response !== 'string') { if (ok) {
onSave(); onSave();
} else { } else {
setMessage( setMessage(
response || ( data || <FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />
),
); );
} }
}; };

View File

@ -1,4 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { useRouter } from 'next/router';
import { Formik, Form, Field } from 'formik'; import { Formik, Form, Field } from 'formik';
import { del } from 'lib/web'; import { del } from 'lib/web';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
@ -8,7 +10,6 @@ import FormLayout, {
FormMessage, FormMessage,
FormRow, FormRow,
} from 'components/layout/FormLayout'; } from 'components/layout/FormLayout';
import { FormattedMessage } from 'react-intl';
const CONFIRMATION_WORD = 'DELETE'; const CONFIRMATION_WORD = 'DELETE';
@ -27,15 +28,18 @@ const validate = ({ confirmation }) => {
}; };
export default function DeleteForm({ values, onSave, onClose }) { export default function DeleteForm({ values, onSave, onClose }) {
const { basePath } = useRouter();
const [message, setMessage] = useState(); const [message, setMessage] = useState();
const handleSubmit = async ({ type, id }) => { const handleSubmit = async ({ type, id }) => {
const response = await del(`/api/${type}/${id}`); const { ok, data } = await del(`${basePath}/api/${type}/${id}`);
if (typeof response !== 'string') { if (ok) {
onSave(); onSave();
} else { } else {
setMessage(<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />); setMessage(
data || <FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
);
} }
}; };

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Formik, Form, Field } from 'formik'; import { Formik, Form, Field } from 'formik';
import Router from 'next/router'; import { useRouter } from 'next/router';
import { post } from 'lib/web'; import { post } from 'lib/web';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
import FormLayout, { import FormLayout, {
@ -28,22 +28,26 @@ const validate = ({ username, password }) => {
}; };
export default function LoginForm() { export default function LoginForm() {
const router = useRouter();
const [message, setMessage] = useState(); const [message, setMessage] = useState();
const handleSubmit = async ({ username, password }) => { const handleSubmit = async ({ username, password }) => {
const response = await post('/api/auth/login', { username, password }); const { ok, status, data } = await post(`${router.basePath}/api/auth/login`, {
username,
password,
});
if (typeof response !== 'string') { if (ok) {
await Router.push('/'); return router.push('/');
} else { } else {
setMessage( setMessage(
response.startsWith('401') ? ( status === 401 ? (
<FormattedMessage <FormattedMessage
id="message.incorrect-username-password" id="message.incorrect-username-password"
defaultMessage="Incorrect username/password." defaultMessage="Incorrect username/password."
/> />
) : ( ) : (
response data
), ),
); );
} }

View File

@ -11,6 +11,7 @@ import FormLayout, {
} from 'components/layout/FormLayout'; } from 'components/layout/FormLayout';
import Checkbox from 'components/common/Checkbox'; import Checkbox from 'components/common/Checkbox';
import { DOMAIN_REGEX } from 'lib/constants'; import { DOMAIN_REGEX } from 'lib/constants';
import { useRouter } from 'next/router';
const initialValues = { const initialValues = {
name: '', name: '',
@ -34,15 +35,18 @@ const validate = ({ name, domain }) => {
}; };
export default function WebsiteEditForm({ values, onSave, onClose }) { export default function WebsiteEditForm({ values, onSave, onClose }) {
const { basePath } = useRouter();
const [message, setMessage] = useState(); const [message, setMessage] = useState();
const handleSubmit = async values => { const handleSubmit = async values => {
const response = await post(`/api/website`, values); const { ok, data } = await post(`${basePath}/api/website`, values);
if (typeof response !== 'string') { if (ok) {
onSave(); onSave();
} else { } else {
setMessage(<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />); setMessage(
data || <FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
);
} }
}; };

View File

@ -6,6 +6,7 @@ import Link from 'components/common/Link';
import Icon from 'components/common/Icon'; import Icon from 'components/common/Icon';
import LanguageButton from 'components/settings/LanguageButton'; import LanguageButton from 'components/settings/LanguageButton';
import ThemeButton from 'components/settings/ThemeButton'; import ThemeButton from 'components/settings/ThemeButton';
import UpdateNotice from 'components/common/UpdateNotice';
import UserButton from 'components/settings/UserButton'; import UserButton from 'components/settings/UserButton';
import Logo from 'assets/logo.svg'; import Logo from 'assets/logo.svg';
import styles from './Header.module.css'; import styles from './Header.module.css';
@ -15,6 +16,7 @@ export default function Header() {
return ( return (
<header className="container"> <header className="container">
{user?.is_admin && <UpdateNotice />}
<div className={classNames(styles.header, 'row align-items-center')}> <div className={classNames(styles.header, 'row align-items-center')}>
<div className="col-12 col-md-12 col-lg-3"> <div className="col-12 col-md-12 col-lg-3">
<div className={styles.title}> <div className={styles.title}>

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import styles from './Page.module.css'; import styles from './Page.module.css';
export default function Page({ children }) { export default function Page({ className, children }) {
return <div className={styles.page}>{children}</div>; return <div className={classNames(styles.page, className)}>{children}</div>;
} }

View File

@ -4,4 +4,5 @@
align-items: center; align-items: center;
align-content: center; align-content: center;
min-height: 80px; min-height: 80px;
align-self: stretch;
} }

View File

@ -1,9 +1,18 @@
import React from 'react'; import React from 'react';
import MetricsTable from './MetricsTable'; import MetricsTable from './MetricsTable';
import { countryFilter, percentFilter } from 'lib/filters'; import { percentFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import useCountryNames from 'hooks/useCountryNames';
import useLocale from 'hooks/useLocale';
export default function CountriesTable({ websiteId, token, limit, onDataLoad = () => {} }) { export default function CountriesTable({ websiteId, token, limit, onDataLoad = () => {} }) {
const [locale] = useLocale();
const countryNames = useCountryNames(locale);
function renderLabel({ x }) {
return <div className={locale}>{countryNames[x]}</div>;
}
return ( return (
<MetricsTable <MetricsTable
title={<FormattedMessage id="metrics.countries" defaultMessage="Countries" />} title={<FormattedMessage id="metrics.countries" defaultMessage="Countries" />}
@ -12,8 +21,8 @@ export default function CountriesTable({ websiteId, token, limit, onDataLoad = (
websiteId={websiteId} websiteId={websiteId}
token={token} token={token}
limit={limit} limit={limit}
dataFilter={countryFilter}
onDataLoad={data => onDataLoad(percentFilter(data))} onDataLoad={data => onDataLoad(percentFilter(data))}
renderLabel={renderLabel}
/> />
); );
} }

View File

@ -59,7 +59,7 @@ export default function WebsiteChart({
} }
return ( return (
<> <div className={styles.container}>
<WebsiteHeader websiteId={websiteId} token={token} title={title} showLink={showLink} /> <WebsiteHeader websiteId={websiteId} token={token} title={title} showLink={showLink} />
<div className={classNames(styles.header, 'row')}> <div className={classNames(styles.header, 'row')}>
<StickyHeader <StickyHeader
@ -92,7 +92,7 @@ export default function WebsiteChart({
/> />
</div> </div>
</div> </div>
</> </div>
); );
} }

View File

@ -1,6 +1,7 @@
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-self: stretch;
} }
.title { .title {

View File

@ -2,9 +2,9 @@ import React, { useState } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import MenuLayout from 'components/layout/MenuLayout'; import MenuLayout from 'components/layout/MenuLayout';
import WebsiteSettings from './WebsiteSettings'; import WebsiteSettings from '../settings/WebsiteSettings';
import AccountSettings from './AccountSettings'; import AccountSettings from '../settings/AccountSettings';
import ProfileSettings from './ProfileSettings'; import ProfileSettings from '../settings/ProfileSettings';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
@ -26,7 +26,7 @@ export default function Settings() {
{ {
label: <FormattedMessage id="label.accounts" defaultMessage="Accounts" />, label: <FormattedMessage id="label.accounts" defaultMessage="Accounts" />,
value: ACCOUNTS, value: ACCOUNTS,
hidden: !user.is_admin, hidden: !user?.is_admin,
}, },
{ {
label: <FormattedMessage id="label.profile" defaultMessage="Profile" />, label: <FormattedMessage id="label.profile" defaultMessage="Profile" />,

View File

@ -0,0 +1,5 @@
.test {
border: 1px solid var(--gray200);
border-radius: 5px;
padding: 0 20px 20px 20px;
}

View File

@ -0,0 +1,94 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import classNames from 'classnames';
import Head from 'next/head';
import Link from 'next/link';
import Page from '../layout/Page';
import PageHeader from '../layout/PageHeader';
import useFetch from '../../hooks/useFetch';
import DropDown from '../common/DropDown';
import styles from './Test.module.css';
import WebsiteChart from '../metrics/WebsiteChart';
import EventsChart from '../metrics/EventsChart';
import Button from '../common/Button';
import EmptyPlaceholder from '../common/EmptyPlaceholder';
export default function TestConsole() {
const user = useSelector(state => state.user);
const [website, setWebsite] = useState();
const { data } = useFetch('/api/websites');
if (!data || !user?.is_admin) {
return null;
}
const options = data.map(({ name, website_id }) => ({ label: name, value: website_id }));
const selectedValue = options.find(({ value }) => value === website?.website_id)?.value;
function handleSelect(value) {
setWebsite(data.find(({ website_id }) => website_id === value));
}
function handleClick() {
window.umami('event (default)');
window.umami.trackView('/page-view', 'https://www.google.com');
window.umami.trackEvent('event (custom)', 'custom-type');
}
return (
<Page>
<Head>
{typeof window !== 'undefined' && website && (
<script async defer data-website-id={website.website_uuid} src="/umami.js" />
)}
</Head>
<PageHeader>
<div>Test Console</div>
<DropDown
value={selectedValue || 'Select website'}
options={options}
onChange={handleSelect}
/>
</PageHeader>
{!selectedValue && <EmptyPlaceholder msg="I hope you know what you're doing here" />}
{selectedValue && (
<>
<div className={classNames(styles.test, 'row')}>
<div className="col-4">
<PageHeader>Page links</PageHeader>
<div>
<Link href={`?page=1`}>
<a>page one</a>
</Link>
</div>
<div>
<Link href={`?page=2`}>
<a>page two</a>
</Link>
</div>
</div>
<div className="col-4">
<PageHeader>CSS events</PageHeader>
<Button id="primary-button" className="umami--click--primary-button" variant="action">
Send event
</Button>
</div>
<div className="col-4">
<PageHeader>Javascript events</PageHeader>
<Button id="manual-button" variant="action" onClick={handleClick}>
Run script
</Button>
</div>
</div>
<div className="row">
<div className="col-12">
<WebsiteChart websiteId={website.website_id} title={website.name} showLink />
<PageHeader>Events</PageHeader>
<EventsChart websiteId={website.website_id} />
</div>
</div>
</>
)}
</Page>
);
}

View File

@ -9,14 +9,14 @@ import Link from 'components/common/Link';
import Loading from 'components/common/Loading'; import Loading from 'components/common/Loading';
import Arrow from 'assets/arrow-right.svg'; import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteDetails.module.css'; import styles from './WebsiteDetails.module.css';
import PagesTable from './metrics/PagesTable'; import PagesTable from '../metrics/PagesTable';
import ReferrersTable from './metrics/ReferrersTable'; import ReferrersTable from '../metrics/ReferrersTable';
import BrowsersTable from './metrics/BrowsersTable'; import BrowsersTable from '../metrics/BrowsersTable';
import OSTable from './metrics/OSTable'; import OSTable from '../metrics/OSTable';
import DevicesTable from './metrics/DevicesTable'; import DevicesTable from '../metrics/DevicesTable';
import CountriesTable from './metrics/CountriesTable'; import CountriesTable from '../metrics/CountriesTable';
import EventsTable from './metrics/EventsTable'; import EventsTable from '../metrics/EventsTable';
import EventsChart from './metrics/EventsChart'; import EventsChart from '../metrics/EventsChart';
import useFetch from 'hooks/useFetch'; import useFetch from 'hooks/useFetch';
import usePageQuery from 'hooks/usePageQuery'; import usePageQuery from 'hooks/usePageQuery';
@ -42,9 +42,9 @@ export default function WebsiteDetails({ websiteId, token }) {
} = usePageQuery(); } = usePageQuery();
const BackButton = () => ( const BackButton = () => (
<div key="back-button" className={styles.backButton}>
<Link <Link
key="back-button" key="back-button"
className={styles.backButton}
href={router.pathname} href={router.pathname}
as={resolve({ view: undefined })} as={resolve({ view: undefined })}
icon={<Arrow />} icon={<Arrow />}
@ -52,6 +52,7 @@ export default function WebsiteDetails({ websiteId, token }) {
> >
<FormattedMessage id="button.back" defaultMessage="Back" /> <FormattedMessage id="button.back" defaultMessage="Back" />
</Link> </Link>
</div>
); );
const menuOptions = [ const menuOptions = [

View File

@ -16,6 +16,9 @@
} }
.backButton { .backButton {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 16px; margin-bottom: 16px;
} }

View File

@ -2,6 +2,7 @@
padding-bottom: 30px; padding-bottom: 30px;
border-bottom: 1px solid var(--gray300); border-bottom: 1px solid var(--gray300);
margin-bottom: 30px; margin-bottom: 30px;
align-self: stretch;
} }
.website:last-child { .website:last-child {

View File

@ -10,6 +10,7 @@ import TimezoneSetting from 'components/settings/TimezoneSetting';
import Dots from 'assets/ellipsis-h.svg'; import Dots from 'assets/ellipsis-h.svg';
import styles from './ProfileSettings.module.css'; import styles from './ProfileSettings.module.css';
import DateRangeSetting from './DateRangeSetting'; import DateRangeSetting from './DateRangeSetting';
import useEscapeKey from 'hooks/useEscapeKey';
export default function ProfileSettings() { export default function ProfileSettings() {
const user = useSelector(state => state.user); const user = useSelector(state => state.user);
@ -22,6 +23,10 @@ export default function ProfileSettings() {
setMessage(<FormattedMessage id="message.save-success" defaultMessage="Saved successfully." />); setMessage(<FormattedMessage id="message.save-success" defaultMessage="Saved successfully." />);
} }
useEscapeKey(() => {
setChangePassword(false);
});
return ( return (
<> <>
<PageHeader> <PageHeader>

34
hooks/useCountryNames.js Normal file
View File

@ -0,0 +1,34 @@
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { get } from 'lib/web';
import enUS from 'public/country/en-US.json';
const countryNames = {
'en-US': enUS,
};
export default function useCountryNames(locale) {
const [list, setList] = useState(countryNames[locale] || enUS);
const { basePath } = useRouter();
async function loadData(locale) {
const { ok, data } = await get(`${basePath}/country/${locale}.json`);
if (ok) {
countryNames[locale] = data;
setList(countryNames[locale]);
} else {
setList(enUS);
}
}
useEffect(() => {
if (!countryNames[locale]) {
loadData(locale);
} else {
setList(countryNames[locale]);
}
}, [locale]);
return list;
}

19
hooks/useEscapeKey.js Normal file
View File

@ -0,0 +1,19 @@
import { useEffect, useCallback } from 'react';
export default function useEscapeKey(handler) {
const escFunction = useCallback(event => {
if (event.keyCode === 27) {
handler(event);
}
}, []);
useEffect(() => {
document.addEventListener('keydown', escFunction, false);
return () => {
document.removeEventListener('keydown', escFunction, false);
};
}, [escFunction]);
return null;
}

View File

@ -2,12 +2,15 @@ import { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { get } from 'lib/web'; import { get } from 'lib/web';
import { updateQuery } from 'redux/actions/queries'; import { updateQuery } from 'redux/actions/queries';
import { useRouter } from 'next/router';
export default function useFetch(url, params = {}, options = {}) { export default function useFetch(url, params = {}, options = {}) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [data, setData] = useState(); const [data, setData] = useState();
const [status, setStatus] = useState();
const [error, setError] = useState(); const [error, setError] = useState();
const [loading, setLoadiing] = useState(false); const [loading, setLoadiing] = useState(false);
const { basePath } = useRouter();
const keys = Object.keys(params) const keys = Object.keys(params)
.sort() .sort()
.map(key => params[key]); .map(key => params[key]);
@ -18,11 +21,12 @@ export default function useFetch(url, params = {}, options = {}) {
setLoadiing(true); setLoadiing(true);
setError(null); setError(null);
const time = performance.now(); const time = performance.now();
const data = await get(url, params); const { data, status } = await get(`${basePath}${url}`, params);
dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() })); dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() }));
setData(data); setData(data);
setStatus(status);
onDataLoad(data); onDataLoad(data);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@ -46,5 +50,5 @@ export default function useFetch(url, params = {}, options = {}) {
} }
}, [url, ...keys, ...update]); }, [url, ...keys, ...update]);
return { data, error, loading, loadData }; return { data, status, error, loading };
} }

View File

@ -1,6 +1,8 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { updateUser } from 'redux/actions/user'; import { updateUser } from 'redux/actions/user';
import { useRouter } from 'next/router';
import { get } from '../lib/web';
export async function fetchUser() { export async function fetchUser() {
const res = await fetch('/api/auth/verify'); const res = await fetch('/api/auth/verify');
@ -13,29 +15,33 @@ export async function fetchUser() {
} }
export default function useRequireLogin() { export default function useRequireLogin() {
const router = useRouter();
const dispatch = useDispatch(); const dispatch = useDispatch();
const storeUser = useSelector(state => state.user); const storeUser = useSelector(state => state.user);
const [loading, setLoading] = useState(!storeUser); const [loading, setLoading] = useState(!storeUser);
const [user, setUser] = useState(storeUser); const [user, setUser] = useState(storeUser);
async function loadUser() {
setLoading(true);
const { ok, data } = await get(`${router.basePath}/api/auth/verify`);
if (!ok) {
return router.push('/login');
}
await dispatch(updateUser(data));
setUser(user);
setLoading(false);
}
useEffect(() => { useEffect(() => {
if (!loading && user) { if (!loading && user) {
return; return;
} }
setLoading(true); loadUser();
fetchUser().then(async user => {
if (!user) {
window.location.href = '/login';
return;
}
await dispatch(updateUser(user));
setUser(user);
setLoading(false);
});
}, []); }, []);
return { user, loading }; return { user, loading };

View File

@ -4,7 +4,7 @@ import { getItem, setItem } from 'lib/web';
import { THEME_CONFIG } from 'lib/constants'; import { THEME_CONFIG } from 'lib/constants';
import { useEffect } from 'react'; import { useEffect } from 'react';
export default function useLocale() { export default function useTheme() {
const theme = useSelector(state => state.app.theme || getItem(THEME_CONFIG) || 'light'); const theme = useSelector(state => state.app.theme || getItem(THEME_CONFIG) || 'light');
const dispatch = useDispatch(); const dispatch = useDispatch();

View File

@ -1,13 +1,14 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { getTimezone } from 'lib/date'; import { getTimezone } from 'lib/date';
import { getItem, setItem } from 'lib/web'; import { getItem, setItem } from 'lib/web';
import { TIMEZONE_CONFIG } from 'lib/constants';
export default function useTimezone() { export default function useTimezone() {
const [timezone, setTimezone] = useState(getItem('umami.timezone') || getTimezone()); const [timezone, setTimezone] = useState(getItem(TIMEZONE_CONFIG) || getTimezone());
const saveTimezone = useCallback( const saveTimezone = useCallback(
value => { value => {
setItem('umami.timezone', value); setItem(TIMEZONE_CONFIG, value);
setTimezone(value); setTimezone(value);
}, },
[setTimezone], [setTimezone],

27
hooks/useVersion.js Normal file
View File

@ -0,0 +1,27 @@
import { useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import semver from 'semver';
import { getItem, setItem } from 'lib/web';
import { checkVersion } from 'redux/actions/app';
import { VERSION_CHECK } from 'lib/constants';
export default function useVersion() {
const dispatch = useDispatch();
const versions = useSelector(state => state.app.versions);
const lastCheck = getItem(VERSION_CHECK);
const { current, latest } = versions;
const hasUpdate = latest && semver.gt(latest, current) && lastCheck?.version !== latest;
const updateCheck = useCallback(() => {
setItem(VERSION_CHECK, { version: latest, time: Date.now() });
}, [versions]);
useEffect(() => {
if (!versions.latest) {
dispatch(checkVersion());
}
}, [versions]);
return { ...versions, hasUpdate, updateCheck };
}

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopier til udklipsholder", "button.copy-to-clipboard": "Kopier til udklipsholder",
"button.date-range": "Datointerval", "button.date-range": "Datointerval",
"button.delete": "Slet", "button.delete": "Slet",
"button.dismiss": "Dismiss",
"button.edit": "Rediger", "button.edit": "Rediger",
"button.login": "Log ind", "button.login": "Log ind",
"button.more": "Mere", "button.more": "Mere",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Få sporingskode", "message.get-tracking-code": "Få sporingskode",
"message.go-to-settings": "Gå til betjeningspanel", "message.go-to-settings": "Gå til betjeningspanel",
"message.incorrect-username-password": "Ugyldigt brugernavn/adgangskode.", "message.incorrect-username-password": "Ugyldigt brugernavn/adgangskode.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Ingen data tilgængelig.", "message.no-data-available": "Ingen data tilgængelig.",
"message.no-websites-configured": "Du har ikke konfigureret nogen websteder.", "message.no-websites-configured": "Du har ikke konfigureret nogen websteder.",
"message.page-not-found": "Side ikke fundet.", "message.page-not-found": "Side ikke fundet.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "In die Zwischenablage kopieren", "button.copy-to-clipboard": "In die Zwischenablage kopieren",
"button.date-range": "Datumsbereich", "button.date-range": "Datumsbereich",
"button.delete": "Löschen", "button.delete": "Löschen",
"button.dismiss": "Dismiss",
"button.edit": "Bearbeiten", "button.edit": "Bearbeiten",
"button.login": "Anmelden", "button.login": "Anmelden",
"button.more": "Mehr", "button.more": "Mehr",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Erstelle Tracking Kennung", "message.get-tracking-code": "Erstelle Tracking Kennung",
"message.go-to-settings": "Zu den Einstellungen", "message.go-to-settings": "Zu den Einstellungen",
"message.incorrect-username-password": "Falsches Passwort oder Benutzername.", "message.incorrect-username-password": "Falsches Passwort oder Benutzername.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Keine Daten vorhanden.", "message.no-data-available": "Keine Daten vorhanden.",
"message.no-websites-configured": "Es ist keine Webseite vorhanden.", "message.no-websites-configured": "Es ist keine Webseite vorhanden.",
"message.page-not-found": "Seite nicht gefunden.", "message.page-not-found": "Seite nicht gefunden.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Αντιγραφή στο πρόχειρο", "button.copy-to-clipboard": "Αντιγραφή στο πρόχειρο",
"button.date-range": "Εύρος ημερομηνιών", "button.date-range": "Εύρος ημερομηνιών",
"button.delete": "Διαγραφή", "button.delete": "Διαγραφή",
"button.dismiss": "Dismiss",
"button.edit": "Επεξεργασία", "button.edit": "Επεξεργασία",
"button.login": "Είσοδος", "button.login": "Είσοδος",
"button.more": "Περισσότερα", "button.more": "Περισσότερα",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Λήψη κώδικα παρακολούθησης", "message.get-tracking-code": "Λήψη κώδικα παρακολούθησης",
"message.go-to-settings": "Μεταβείτε στις ρυθμίσεις", "message.go-to-settings": "Μεταβείτε στις ρυθμίσεις",
"message.incorrect-username-password": "Εσφαλμένο όνομα χρήστη / κωδικός πρόσβασης.", "message.incorrect-username-password": "Εσφαλμένο όνομα χρήστη / κωδικός πρόσβασης.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Δεν υπάρχουν διαθέσιμα δεδομένα.", "message.no-data-available": "Δεν υπάρχουν διαθέσιμα δεδομένα.",
"message.no-websites-configured": "Δεν έχετε ρυθμίσει κανένα ιστότοπο.", "message.no-websites-configured": "Δεν έχετε ρυθμίσει κανένα ιστότοπο.",
"message.page-not-found": "Η σελίδα δεν βρέθηκε.", "message.page-not-found": "Η σελίδα δεν βρέθηκε.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Copy to clipboard", "button.copy-to-clipboard": "Copy to clipboard",
"button.date-range": "Date range", "button.date-range": "Date range",
"button.delete": "Delete", "button.delete": "Delete",
"button.dismiss": "Dismiss",
"button.edit": "Edit", "button.edit": "Edit",
"button.login": "Login", "button.login": "Login",
"button.more": "More", "button.more": "More",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Get tracking code", "message.get-tracking-code": "Get tracking code",
"message.go-to-settings": "Go to settings", "message.go-to-settings": "Go to settings",
"message.incorrect-username-password": "Incorrect username/password.", "message.incorrect-username-password": "Incorrect username/password.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "No data available.", "message.no-data-available": "No data available.",
"message.no-websites-configured": "You don't have any websites configured.", "message.no-websites-configured": "You don't have any websites configured.",
"message.page-not-found": "Page not found.", "message.page-not-found": "Page not found.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Copiar al portapapeles", "button.copy-to-clipboard": "Copiar al portapapeles",
"button.date-range": "Date range", "button.date-range": "Date range",
"button.delete": "Eliminar", "button.delete": "Eliminar",
"button.dismiss": "Dismiss",
"button.edit": "Editar", "button.edit": "Editar",
"button.login": "Iniciar sesión", "button.login": "Iniciar sesión",
"button.more": "Más", "button.more": "Más",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Obtener código de rastreo", "message.get-tracking-code": "Obtener código de rastreo",
"message.go-to-settings": "Ir a la configuración", "message.go-to-settings": "Ir a la configuración",
"message.incorrect-username-password": "Nombre de usuario o contraseña incorrectos.", "message.incorrect-username-password": "Nombre de usuario o contraseña incorrectos.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Sin información disponible.", "message.no-data-available": "Sin información disponible.",
"message.no-websites-configured": "No tienes ningún sitio configurado.", "message.no-websites-configured": "No tienes ningún sitio configurado.",
"message.page-not-found": "Page not found", "message.page-not-found": "Page not found",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopier til clipboard", "button.copy-to-clipboard": "Kopier til clipboard",
"button.date-range": "Vel dato", "button.date-range": "Vel dato",
"button.delete": "Sletta", "button.delete": "Sletta",
"button.dismiss": "Dismiss",
"button.edit": "Ger broyting", "button.edit": "Ger broyting",
"button.login": "Rita inn", "button.login": "Rita inn",
"button.more": "Meira", "button.more": "Meira",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Fá sporings kotu", "message.get-tracking-code": "Fá sporings kotu",
"message.go-to-settings": "Far til stillingar", "message.go-to-settings": "Far til stillingar",
"message.incorrect-username-password": "Skeivt brúkaranavn/loyniorð.", "message.incorrect-username-password": "Skeivt brúkaranavn/loyniorð.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Einki data tøk.", "message.no-data-available": "Einki data tøk.",
"message.no-websites-configured": "Tú hevur ongar heimasíður stillaða til.", "message.no-websites-configured": "Tú hevur ongar heimasíður stillaða til.",
"message.page-not-found": "Síðan bleiv ikki funnin.", "message.page-not-found": "Síðan bleiv ikki funnin.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Copier dans le presse papier", "button.copy-to-clipboard": "Copier dans le presse papier",
"button.date-range": "Date range", "button.date-range": "Date range",
"button.delete": "Supprimer", "button.delete": "Supprimer",
"button.dismiss": "Dismiss",
"button.edit": "Modifier", "button.edit": "Modifier",
"button.login": "Connexion", "button.login": "Connexion",
"button.more": "Plus", "button.more": "Plus",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Obtenez le code de suivi", "message.get-tracking-code": "Obtenez le code de suivi",
"message.go-to-settings": "Aller aux paramètres", "message.go-to-settings": "Aller aux paramètres",
"message.incorrect-username-password": "nom d'utilisateurs/mot de passe incorrect.", "message.incorrect-username-password": "nom d'utilisateurs/mot de passe incorrect.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Pas de données disponibles.", "message.no-data-available": "Pas de données disponibles.",
"message.no-websites-configured": "Vous n'avez configuré aucun site Web.", "message.no-websites-configured": "Vous n'avez configuré aucun site Web.",
"message.page-not-found": "Page non trouvée.", "message.page-not-found": "Page non trouvée.",

View File

@ -5,8 +5,9 @@
"button.cancel": "キャンセル", "button.cancel": "キャンセル",
"button.change-password": "パスワード変更", "button.change-password": "パスワード変更",
"button.copy-to-clipboard": "クリップボードにコピー", "button.copy-to-clipboard": "クリップボードにコピー",
"button.date-range": "期間", "button.date-range": "日付範囲",
"button.delete": "削除", "button.delete": "削除",
"button.dismiss": "無視する",
"button.edit": "編集", "button.edit": "編集",
"button.login": "ログイン", "button.login": "ログイン",
"button.more": "さらに表示", "button.more": "さらに表示",
@ -14,7 +15,7 @@
"button.reset": "リセット", "button.reset": "リセット",
"button.save": "保存", "button.save": "保存",
"button.single-day": "一日のみ", "button.single-day": "一日のみ",
"button.view-details": "詳細表示", "button.view-details": "詳細を見る",
"label.accounts": "アカウント", "label.accounts": "アカウント",
"label.administrator": "管理者", "label.administrator": "管理者",
"label.confirm-password": "パスワード(確認)", "label.confirm-password": "パスワード(確認)",
@ -54,6 +55,7 @@
"message.get-tracking-code": "トラッキングコードを取得", "message.get-tracking-code": "トラッキングコードを取得",
"message.go-to-settings": "設定する", "message.go-to-settings": "設定する",
"message.incorrect-username-password": "ユーザー名/パスワードが正しくありません。", "message.incorrect-username-password": "ユーザー名/パスワードが正しくありません。",
"message.new-version-available": "新しいバージョン({version})が利用可能です!",
"message.no-data-available": "データがありません。", "message.no-data-available": "データがありません。",
"message.no-websites-configured": "Webサイトが設定されていません。", "message.no-websites-configured": "Webサイトが設定されていません。",
"message.page-not-found": "ページが見つかりません。", "message.page-not-found": "ページが見つかりません。",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Хуулах", "button.copy-to-clipboard": "Хуулах",
"button.date-range": "Хугацааны мужид", "button.date-range": "Хугацааны мужид",
"button.delete": "Устгах", "button.delete": "Устгах",
"button.dismiss": "Dismiss",
"button.edit": "Засах", "button.edit": "Засах",
"button.login": "Нэвтрэх", "button.login": "Нэвтрэх",
"button.more": "Цааш", "button.more": "Цааш",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Мөрдөх код авах", "message.get-tracking-code": "Мөрдөх код авах",
"message.go-to-settings": "Тохиргоо руу очих", "message.go-to-settings": "Тохиргоо руу очих",
"message.incorrect-username-password": "Буруу хэрэглэгчийн нэр/нууц үг.", "message.incorrect-username-password": "Буруу хэрэглэгчийн нэр/нууц үг.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Өгөгдөл алга.", "message.no-data-available": "Өгөгдөл алга.",
"message.no-websites-configured": "Та ямар нэгэн веб тохируулаагүй байна.", "message.no-websites-configured": "Та ямар нэгэн веб тохируулаагүй байна.",
"message.page-not-found": "Хуудас олдсонгүй.", "message.page-not-found": "Хуудас олдсонгүй.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopiëer naar klembord", "button.copy-to-clipboard": "Kopiëer naar klembord",
"button.date-range": "Datumbereik", "button.date-range": "Datumbereik",
"button.delete": "Verwijderen", "button.delete": "Verwijderen",
"button.dismiss": "Dismiss",
"button.edit": "Bewerken", "button.edit": "Bewerken",
"button.login": "Inloggen", "button.login": "Inloggen",
"button.more": "Toon meer", "button.more": "Toon meer",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Tracking code", "message.get-tracking-code": "Tracking code",
"message.go-to-settings": "Naar instellingen", "message.go-to-settings": "Naar instellingen",
"message.incorrect-username-password": "Incorrecte gebruikersnaam/wachtwoord.", "message.incorrect-username-password": "Incorrecte gebruikersnaam/wachtwoord.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Geen gegevens beschikbaar.", "message.no-data-available": "Geen gegevens beschikbaar.",
"message.no-websites-configured": "Je hebt geen websites ingesteld.", "message.no-websites-configured": "Je hebt geen websites ingesteld.",
"message.page-not-found": "Pagina niet gevonden.", "message.page-not-found": "Pagina niet gevonden.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Скопировать в буфер обмена", "button.copy-to-clipboard": "Скопировать в буфер обмена",
"button.date-range": "Диапазон дат", "button.date-range": "Диапазон дат",
"button.delete": "Удалить", "button.delete": "Удалить",
"button.dismiss": "Dismiss",
"button.edit": "Редактировать", "button.edit": "Редактировать",
"button.login": "Войти", "button.login": "Войти",
"button.more": "Больше", "button.more": "Больше",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Получить код отслеживания", "message.get-tracking-code": "Получить код отслеживания",
"message.go-to-settings": "Перейти к настройкам", "message.go-to-settings": "Перейти к настройкам",
"message.incorrect-username-password": "Неверное имя пользователя/пароль.", "message.incorrect-username-password": "Неверное имя пользователя/пароль.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Нет данных.", "message.no-data-available": "Нет данных.",
"message.no-websites-configured": "У вас нет настроенных сайтов.", "message.no-websites-configured": "У вас нет настроенных сайтов.",
"message.page-not-found": "Страница не найдена.", "message.page-not-found": "Страница не найдена.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopiera till urklipp", "button.copy-to-clipboard": "Kopiera till urklipp",
"button.date-range": "Datumomfång", "button.date-range": "Datumomfång",
"button.delete": "Radera", "button.delete": "Radera",
"button.dismiss": "Dismiss",
"button.edit": "Redigera", "button.edit": "Redigera",
"button.login": "Logga in", "button.login": "Logga in",
"button.more": "Mer", "button.more": "Mer",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Visa spårningskod", "message.get-tracking-code": "Visa spårningskod",
"message.go-to-settings": "Gå till inställningar", "message.go-to-settings": "Gå till inställningar",
"message.incorrect-username-password": "Felaktikt användarnamn/lösenord.", "message.incorrect-username-password": "Felaktikt användarnamn/lösenord.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Ingen data tillgänglig.", "message.no-data-available": "Ingen data tillgänglig.",
"message.no-websites-configured": "Du har inga webbsajter.", "message.no-websites-configured": "Du har inga webbsajter.",
"message.page-not-found": "Sidan kan inte hittas.", "message.page-not-found": "Sidan kan inte hittas.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Panoya kopyala", "button.copy-to-clipboard": "Panoya kopyala",
"button.date-range": "Tarih aralığı", "button.date-range": "Tarih aralığı",
"button.delete": "Sil", "button.delete": "Sil",
"button.dismiss": "Dismiss",
"button.edit": "Düzenle", "button.edit": "Düzenle",
"button.login": "Giriş Yap", "button.login": "Giriş Yap",
"button.more": "Detaylı göster", "button.more": "Detaylı göster",
@ -54,6 +55,7 @@
"message.get-tracking-code": "İzleme kodunu al", "message.get-tracking-code": "İzleme kodunu al",
"message.go-to-settings": "Ayarlara git", "message.go-to-settings": "Ayarlara git",
"message.incorrect-username-password": "Hatalı kullanıcı adı ya da parola.", "message.incorrect-username-password": "Hatalı kullanıcı adı ya da parola.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Henüz hiç veri yok.", "message.no-data-available": "Henüz hiç veri yok.",
"message.no-websites-configured": "Henüz hiç web sitesi tanımlamadınız", "message.no-websites-configured": "Henüz hiç web sitesi tanımlamadınız",
"message.page-not-found": "Sayfa bulunamadı.", "message.page-not-found": "Sayfa bulunamadı.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "复制", "button.copy-to-clipboard": "复制",
"button.date-range": "多日", "button.date-range": "多日",
"button.delete": "删除", "button.delete": "删除",
"button.dismiss": "Dismiss",
"button.edit": "编辑", "button.edit": "编辑",
"button.login": "登录", "button.login": "登录",
"button.more": "更多", "button.more": "更多",
@ -54,6 +55,7 @@
"message.get-tracking-code": "获得跟踪代码", "message.get-tracking-code": "获得跟踪代码",
"message.go-to-settings": "去设置", "message.go-to-settings": "去设置",
"message.incorrect-username-password": "用户名密码不正确.", "message.incorrect-username-password": "用户名密码不正确.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "无可用数据.", "message.no-data-available": "无可用数据.",
"message.no-websites-configured": "你还没有设置任何网站.", "message.no-websites-configured": "你还没有设置任何网站.",
"message.page-not-found": "网页未找到.", "message.page-not-found": "网页未找到.",

View File

@ -3,6 +3,7 @@ export const LOCALE_CONFIG = 'umami.locale';
export const TIMEZONE_CONFIG = 'umami.timezone'; export const TIMEZONE_CONFIG = 'umami.timezone';
export const DATE_RANGE_CONFIG = 'umami.date-range'; export const DATE_RANGE_CONFIG = 'umami.date-range';
export const THEME_CONFIG = 'umami.theme'; export const THEME_CONFIG = 'umami.theme';
export const VERSION_CHECK = 'umami.version-check';
export const THEME_COLORS = { export const THEME_COLORS = {
light: { light: {
@ -131,251 +132,3 @@ export const BROWSERS = {
'ios-webview': 'iOS (webview)', 'ios-webview': 'iOS (webview)',
searchbot: 'Searchbot', searchbot: 'Searchbot',
}; };
export const ISO_COUNTRIES = {
AF: 'Afghanistan',
AX: 'Aland Islands',
AL: 'Albania',
DZ: 'Algeria',
AS: 'American Samoa',
AD: 'Andorra',
AO: 'Angola',
AI: 'Anguilla',
AQ: 'Antarctica',
AG: 'Antigua And Barbuda',
AR: 'Argentina',
AM: 'Armenia',
AW: 'Aruba',
AU: 'Australia',
AT: 'Austria',
AZ: 'Azerbaijan',
BS: 'Bahamas',
BH: 'Bahrain',
BD: 'Bangladesh',
BB: 'Barbados',
BY: 'Belarus',
BE: 'Belgium',
BZ: 'Belize',
BJ: 'Benin',
BM: 'Bermuda',
BT: 'Bhutan',
BO: 'Bolivia',
BA: 'Bosnia And Herzegovina',
BW: 'Botswana',
BV: 'Bouvet Island',
BR: 'Brazil',
IO: 'British Indian Ocean Territory',
BN: 'Brunei Darussalam',
BG: 'Bulgaria',
BF: 'Burkina Faso',
BI: 'Burundi',
KH: 'Cambodia',
CM: 'Cameroon',
CA: 'Canada',
CV: 'Cape Verde',
KY: 'Cayman Islands',
CF: 'Central African Republic',
TD: 'Chad',
CL: 'Chile',
CN: 'China',
CX: 'Christmas Island',
CC: 'Cocos (Keeling) Islands',
CO: 'Colombia',
KM: 'Comoros',
CG: 'Congo',
CD: 'Congo, Democratic Republic',
CK: 'Cook Islands',
CR: 'Costa Rica',
CI: "Cote D'Ivoire",
HR: 'Croatia',
CU: 'Cuba',
CY: 'Cyprus',
CZ: 'Czech Republic',
DK: 'Denmark',
DJ: 'Djibouti',
DM: 'Dominica',
DO: 'Dominican Republic',
EC: 'Ecuador',
EG: 'Egypt',
SV: 'El Salvador',
GQ: 'Equatorial Guinea',
ER: 'Eritrea',
EE: 'Estonia',
ET: 'Ethiopia',
FK: 'Falkland Islands (Malvinas)',
FO: 'Faroe Islands',
FJ: 'Fiji',
FI: 'Finland',
FR: 'France',
GF: 'French Guiana',
PF: 'French Polynesia',
TF: 'French Southern Territories',
GA: 'Gabon',
GM: 'Gambia',
GE: 'Georgia',
DE: 'Germany',
GH: 'Ghana',
GI: 'Gibraltar',
GR: 'Greece',
GL: 'Greenland',
GD: 'Grenada',
GP: 'Guadeloupe',
GU: 'Guam',
GT: 'Guatemala',
GG: 'Guernsey',
GN: 'Guinea',
GW: 'Guinea-Bissau',
GY: 'Guyana',
HT: 'Haiti',
HM: 'Heard Island & Mcdonald Islands',
VA: 'Holy See (Vatican City State)',
HN: 'Honduras',
HK: 'Hong Kong',
HU: 'Hungary',
IS: 'Iceland',
IN: 'India',
ID: 'Indonesia',
IR: 'Iran, Islamic Republic Of',
IQ: 'Iraq',
IE: 'Ireland',
IM: 'Isle Of Man',
IL: 'Israel',
IT: 'Italy',
JM: 'Jamaica',
JP: 'Japan',
JE: 'Jersey',
JO: 'Jordan',
KZ: 'Kazakhstan',
KE: 'Kenya',
KI: 'Kiribati',
KR: 'Korea',
KW: 'Kuwait',
KG: 'Kyrgyzstan',
LA: "Lao People's Democratic Republic",
LV: 'Latvia',
LB: 'Lebanon',
LS: 'Lesotho',
LR: 'Liberia',
LY: 'Libyan Arab Jamahiriya',
LI: 'Liechtenstein',
LT: 'Lithuania',
LU: 'Luxembourg',
MO: 'Macao',
MK: 'Macedonia',
MG: 'Madagascar',
MW: 'Malawi',
MY: 'Malaysia',
MV: 'Maldives',
ML: 'Mali',
MT: 'Malta',
MH: 'Marshall Islands',
MQ: 'Martinique',
MR: 'Mauritania',
MU: 'Mauritius',
YT: 'Mayotte',
MX: 'Mexico',
FM: 'Micronesia, Federated States Of',
MD: 'Moldova',
MC: 'Monaco',
MN: 'Mongolia',
ME: 'Montenegro',
MS: 'Montserrat',
MA: 'Morocco',
MZ: 'Mozambique',
MM: 'Myanmar',
NA: 'Namibia',
NR: 'Nauru',
NP: 'Nepal',
NL: 'Netherlands',
AN: 'Netherlands Antilles',
NC: 'New Caledonia',
NZ: 'New Zealand',
NI: 'Nicaragua',
NE: 'Niger',
NG: 'Nigeria',
NU: 'Niue',
NF: 'Norfolk Island',
MP: 'Northern Mariana Islands',
NO: 'Norway',
OM: 'Oman',
PK: 'Pakistan',
PW: 'Palau',
PS: 'Palestinian Territory, Occupied',
PA: 'Panama',
PG: 'Papua New Guinea',
PY: 'Paraguay',
PE: 'Peru',
PH: 'Philippines',
PN: 'Pitcairn',
PL: 'Poland',
PT: 'Portugal',
PR: 'Puerto Rico',
QA: 'Qatar',
RE: 'Reunion',
RO: 'Romania',
RU: 'Russia',
RW: 'Rwanda',
BL: 'Saint Barthelemy',
SH: 'Saint Helena',
KN: 'Saint Kitts And Nevis',
LC: 'Saint Lucia',
MF: 'Saint Martin',
PM: 'Saint Pierre And Miquelon',
VC: 'Saint Vincent And Grenadines',
WS: 'Samoa',
SM: 'San Marino',
ST: 'Sao Tome And Principe',
SA: 'Saudi Arabia',
SN: 'Senegal',
RS: 'Serbia',
SC: 'Seychelles',
SL: 'Sierra Leone',
SG: 'Singapore',
SK: 'Slovakia',
SI: 'Slovenia',
SB: 'Solomon Islands',
SO: 'Somalia',
ZA: 'South Africa',
GS: 'South Georgia And Sandwich Isl.',
ES: 'Spain',
LK: 'Sri Lanka',
SD: 'Sudan',
SR: 'Suriname',
SJ: 'Svalbard And Jan Mayen',
SZ: 'Swaziland',
SE: 'Sweden',
CH: 'Switzerland',
SY: 'Syrian Arab Republic',
TW: 'Taiwan',
TJ: 'Tajikistan',
TZ: 'Tanzania',
TH: 'Thailand',
TL: 'Timor-Leste',
TG: 'Togo',
TK: 'Tokelau',
TO: 'Tonga',
TT: 'Trinidad And Tobago',
TN: 'Tunisia',
TR: 'Turkey',
TM: 'Turkmenistan',
TC: 'Turks And Caicos Islands',
TV: 'Tuvalu',
UG: 'Uganda',
UA: 'Ukraine',
AE: 'United Arab Emirates',
GB: 'United Kingdom',
US: 'United States',
UM: 'United States Outlying Islands',
UY: 'Uruguay',
UZ: 'Uzbekistan',
VU: 'Vanuatu',
VE: 'Venezuela',
VN: 'Viet Nam',
VG: 'Virgin Islands, British',
VI: 'Virgin Islands, U.S.',
WF: 'Wallis And Futuna',
EH: 'Western Sahara',
YE: 'Yemen',
ZM: 'Zambia',
ZW: 'Zimbabwe',
};

View File

@ -1,5 +1,5 @@
import firstBy from 'thenby'; import firstBy from 'thenby';
import { BROWSERS, ISO_COUNTRIES } from './constants'; import { BROWSERS } from './constants';
import { removeTrailingSlash, removeWWW, getDomainName } from './url'; import { removeTrailingSlash, removeWWW, getDomainName } from './url';
export const urlFilter = (data, { raw }) => { export const urlFilter = (data, { raw }) => {
@ -125,9 +125,6 @@ export const osFilter = data => data.filter(({ x }) => x);
export const deviceFilter = data => data.filter(({ x }) => x); export const deviceFilter = data => data.filter(({ x }) => x);
export const countryFilter = data =>
data.map(({ x, y }) => ({ x: ISO_COUNTRIES[x] || x, y })).filter(({ x }) => x);
export const percentFilter = data => { export const percentFilter = data => {
const total = data.reduce((n, { y }) => n + y, 0); const total = data.reduce((n, { y }) => n + y, 0);
return data.map(({ x, y, ...props }) => ({ x, y, z: total ? (y / total) * 100 : 0, ...props })); return data.map(({ x, y, ...props }) => ({ x, y, z: total ? (y / total) * 100 : 0, ...props }));

View File

@ -16,8 +16,12 @@ export function getDatabase() {
} }
export async function runQuery(query) { export async function runQuery(query) {
return query.catch(e => { return query
.catch(e => {
throw e; throw e;
})
.finally(async () => {
await prisma.$disconnect();
}); });
} }
@ -285,8 +289,9 @@ export async function createAccount(data) {
); );
} }
export function getMetrics(website_id, start_at, end_at, url) { export function getMetrics(website_id, start_at, end_at, filters = {}) {
const params = [website_id, start_at, end_at]; const params = [website_id, start_at, end_at];
const { url } = filters;
let urlFilter = ''; let urlFilter = '';
if (url) { if (url) {
@ -348,8 +353,10 @@ export function getPageviews(
); );
} }
export function getSessionMetrics(website_id, start_at, end_at, field, url) { export function getSessionMetrics(website_id, start_at, end_at, field, filters = {}) {
const params = [website_id, start_at, end_at]; const params = [website_id, start_at, end_at];
const { url } = filters;
let urlFilter = ''; let urlFilter = '';
if (url) { if (url) {
@ -375,13 +382,15 @@ export function getSessionMetrics(website_id, start_at, end_at, field, url) {
); );
} }
export function getPageviewMetrics(website_id, start_at, end_at, field, table, domain, url) { export function getPageviewMetrics(website_id, start_at, end_at, field, table, filters = {}) {
const params = [website_id, start_at, end_at]; const params = [website_id, start_at, end_at];
const { domain, url } = filters;
let domainFilter = ''; let domainFilter = '';
let urlFilter = ''; let urlFilter = '';
if (domain) { if (domain) {
domainFilter = `and referrer not like $${params.length + 1}`; domainFilter = `and referrer not like $${params.length + 1} and referrer not like '/%'`;
params.push(`%${domain}%`); params.push(`%${domain}%`);
} }
@ -420,8 +429,17 @@ export function getActiveVisitors(website_id) {
); );
} }
export function getEvents(website_id, start_at, end_at, timezone = 'utc', unit = 'day', url) { export function getEvents(
website_id,
start_at,
end_at,
timezone = 'utc',
unit = 'day',
filters = {},
) {
const params = [website_id, start_at, end_at]; const params = [website_id, start_at, end_at];
const { url } = filters;
let urlFilter = ''; let urlFilter = '';
if (url) { if (url) {

View File

@ -1,6 +1,6 @@
import { getQueryString } from './url'; import { getQueryString } from './url';
export const apiRequest = (method, url, body) => export const apiRequest = (method, url, body, headers) =>
fetch(url, { fetch(url, {
method, method,
cache: 'no-cache', cache: 'no-cache',
@ -8,18 +8,15 @@ export const apiRequest = (method, url, body) =>
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...headers,
}, },
body, body,
}).then(res => { }).then(res => {
if (res.ok) { if (res.ok) {
return res.json(); return res.json().then(data => ({ ok: res.ok, status: res.status, data }));
} }
if (['post', 'put', 'delete'].includes(method)) { return res.text().then(data => ({ ok: res.ok, status: res.status, res: res, data }));
return res.text();
}
return null;
}); });
export const get = (url, params) => apiRequest('get', `${url}${getQueryString(params)}`); export const get = (url, params) => apiRequest('get', `${url}${getQueryString(params)}`);

View File

@ -1,6 +1,6 @@
{ {
"name": "umami", "name": "umami",
"version": "0.58.0", "version": "0.67.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ", "description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao <mike@mikecao.com>", "author": "Mike Cao <mike@mikecao.com>",
"license": "MIT", "license": "MIT",
@ -13,6 +13,7 @@
"dev": "next dev", "dev": "next dev",
"build": "npm-run-all build-tracker build-lang build-geo build-db build-app", "build": "npm-run-all build-tracker build-lang build-geo build-db build-app",
"start": "next start", "start": "next start",
"start-env": "node scripts/start-env.js",
"build-app": "next build", "build-app": "next build",
"build-tracker": "rollup -c rollup.tracker.config.js", "build-tracker": "rollup -c rollup.tracker.config.js",
"build-db": "npm-run-all copy-db-schema build-db-client", "build-db": "npm-run-all copy-db-schema build-db-client",
@ -29,7 +30,8 @@
"extract-lang": "formatjs extract {pages,components}/**/*.js --out-file build/messages.json", "extract-lang": "formatjs extract {pages,components}/**/*.js --out-file build/messages.json",
"merge-lang": "node scripts/merge-lang.js", "merge-lang": "node scripts/merge-lang.js",
"format-lang": "node scripts/format-lang.js", "format-lang": "node scripts/format-lang.js",
"compile-lang": "formatjs compile-folder --ast build lang-compiled" "compile-lang": "formatjs compile-folder --ast build lang-compiled",
"check-lang": "node scripts/check-lang.js"
}, },
"lint-staged": { "lint-staged": {
"**/*.js": [ "**/*.js": [
@ -50,7 +52,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@prisma/client": "2.7.1", "@prisma/client": "2.8.0",
"@reduxjs/toolkit": "^1.4.0", "@reduxjs/toolkit": "^1.4.0",
"bcrypt": "^5.0.0", "bcrypt": "^5.0.0",
"chalk": "^4.1.0", "chalk": "^4.1.0",
@ -61,12 +63,12 @@
"date-fns": "^2.16.1", "date-fns": "^2.16.1",
"date-fns-tz": "^1.0.10", "date-fns-tz": "^1.0.10",
"detect-browser": "^5.1.1", "detect-browser": "^5.1.1",
"formik": "^2.1.5", "formik": "^2.1.6",
"immer": "^7.0.9", "immer": "^7.0.9",
"is-localhost-ip": "^1.4.0", "is-localhost-ip": "^1.4.0",
"isbot-fast": "^1.2.0", "isbot-fast": "^1.2.0",
"jose": "^2.0.2", "jose": "^2.0.2",
"maxmind": "^4.1.4", "maxmind": "^4.2.0",
"moment-timezone": "^0.5.31", "moment-timezone": "^0.5.31",
"next": "^9.5.3", "next": "^9.5.3",
"react": "^16.13.1", "react": "^16.13.1",
@ -80,6 +82,7 @@
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"request-ip": "^2.1.3", "request-ip": "^2.1.3",
"semver": "^7.3.2",
"thenby": "^1.3.4", "thenby": "^1.3.4",
"timezone-support": "^2.0.2", "timezone-support": "^2.0.2",
"tinycolor2": "^1.4.2", "tinycolor2": "^1.4.2",
@ -87,7 +90,7 @@
}, },
"devDependencies": { "devDependencies": {
"@formatjs/cli": "^2.12.0", "@formatjs/cli": "^2.12.0",
"@prisma/cli": "2.7.1", "@prisma/cli": "2.8.0",
"@rollup/plugin-buble": "^0.21.3", "@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-node-resolve": "^9.0.0", "@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-replace": "^2.3.3", "@rollup/plugin-replace": "^2.3.3",
@ -113,7 +116,7 @@
"rollup": "^2.28.2", "rollup": "^2.28.2",
"rollup-plugin-hashbang": "^2.2.2", "rollup-plugin-hashbang": "^2.2.2",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"stylelint": "^13.7.1", "stylelint": "^13.7.2",
"stylelint-config-css-modules": "^2.2.0", "stylelint-config-css-modules": "^2.2.0",
"stylelint-config-prettier": "^8.0.1", "stylelint-config-prettier": "^8.0.1",
"stylelint-config-recommended": "^3.0.0", "stylelint-config-recommended": "^3.0.0",

View File

@ -1,6 +1,6 @@
import { serialize } from 'cookie'; import { serialize } from 'cookie';
import { AUTH_COOKIE_NAME } from 'lib/constants'; import { AUTH_COOKIE_NAME } from 'lib/constants';
import { redirect } from 'lib/response'; import { ok } from 'lib/response';
export default async (req, res) => { export default async (req, res) => {
const cookie = serialize(AUTH_COOKIE_NAME, '', { const cookie = serialize(AUTH_COOKIE_NAME, '', {
@ -11,5 +11,5 @@ export default async (req, res) => {
res.setHeader('Set-Cookie', [cookie]); res.setHeader('Set-Cookie', [cookie]);
return redirect(res, '/login'); return ok(res);
}; };

View File

@ -21,7 +21,7 @@ export default async (req, res) => {
const startDate = new Date(+start_at); const startDate = new Date(+start_at);
const endDate = new Date(+end_at); const endDate = new Date(+end_at);
const events = await getEvents(websiteId, startDate, endDate, tz, unit, url); const events = await getEvents(websiteId, startDate, endDate, tz, unit, { url });
return ok(res, events); return ok(res, events);
} }

View File

@ -14,7 +14,7 @@ export default async (req, res) => {
const startDate = new Date(+start_at); const startDate = new Date(+start_at);
const endDate = new Date(+end_at); const endDate = new Date(+end_at);
const metrics = await getMetrics(websiteId, startDate, endDate, url); const metrics = await getMetrics(websiteId, startDate, endDate, { url });
const stats = Object.keys(metrics[0]).reduce((obj, key) => { const stats = Object.keys(metrics[0]).reduce((obj, key) => {
obj[key] = Number(metrics[0][key]) || 0; obj[key] = Number(metrics[0][key]) || 0;

View File

@ -42,7 +42,7 @@ export default async (req, res) => {
const endDate = new Date(+end_at); const endDate = new Date(+end_at);
if (sessionColumns.includes(type)) { if (sessionColumns.includes(type)) {
const data = await getSessionMetrics(websiteId, startDate, endDate, type, url); const data = await getSessionMetrics(websiteId, startDate, endDate, type, { url });
return ok(res, data); return ok(res, data);
} }
@ -54,8 +54,10 @@ export default async (req, res) => {
endDate, endDate,
getColumn(type), getColumn(type),
getTable(type), getTable(type),
{
domain, domain,
type !== 'url' ? url : undefined, url: type !== 'url' && url,
},
); );
return ok(res, data); return ok(res, data);

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import WebsiteList from 'components/WebsiteList'; import WebsiteList from 'components/pages/WebsiteList';
import useRequireLogin from 'hooks/useRequireLogin'; import useRequireLogin from 'hooks/useRequireLogin';
export default function DashboardPage() { export default function DashboardPage() {

View File

@ -1,8 +1,12 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRouter } from 'next/router';
export default function LogoutPage() { export default function LogoutPage() {
const router = useRouter();
const { basePath } = router;
useEffect(() => { useEffect(() => {
fetch('/api/auth/logout').then(() => (window.location.href = '/login')); fetch(`${basePath}/api/auth/logout`).then(() => router.push('/login'));
}, []); }, []);
return null; return null;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import Settings from 'components/settings/Settings'; import Settings from 'components/pages/Settings';
import useRequireLogin from 'hooks/useRequireLogin'; import useRequireLogin from 'hooks/useRequireLogin';
export default function SettingsPage() { export default function SettingsPage() {

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import WebsiteDetails from 'components/WebsiteDetails'; import WebsiteDetails from 'components/pages/WebsiteDetails';
import useFetch from 'hooks/useFetch'; import useFetch from 'hooks/useFetch';
export default function SharePage() { export default function SharePage() {

View File

@ -1,56 +1,18 @@
import Head from 'next/head'; import React from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import TestConsole from 'components/pages/TestConsole';
import useRequireLogin from 'hooks/useRequireLogin';
export default function Test() { export default function TestPage() {
const router = useRouter(); const { loading } = useRequireLogin();
const { id } = router.query;
if (!id) { if (loading) {
return <h1>No id query specified.</h1>; return null;
}
function handleClick() {
window.umami('Custom event');
window.umami.pageView('/fake', 'https://www.google.com');
window.umami.pageEvent('pageEvent', 'custom-type');
} }
return ( return (
<>
<Head>
{typeof window !== 'undefined' && (
<script async defer data-website-id={id} src="/umami.js" />
)}
</Head>
<Layout> <Layout>
<p> <TestConsole />
Here you can test if your umami installation works. Open the network tab in your browser
developer console and watch for requests to the url <b>collect</b>. The links below should
trigger page views. Clicking on the button should trigger an event.
</p>
<h2>Page links</h2>
<Link href={`?id=${id}&q=1`}>
<a>Page One</a>
</Link>
<br />
<Link href={`?id=${id}&q=2`}>
<a>Page Two</a>
</Link>
<h2>Events</h2>
<button
id="primary-button"
className="otherClass umami--click--primary-button align-self-start"
type="button"
>
Button
</button>
<h2>Manual trigger</h2>
<button id="manual-button" type="button" onClick={handleClick}>
Button
</button>
</Layout> </Layout>
</>
); );
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import WebsiteDetails from 'components/WebsiteDetails'; import WebsiteDetails from 'components/pages/WebsiteDetails';
import useRequireLogin from 'hooks/useRequireLogin'; import useRequireLogin from 'hooks/useRequireLogin';
export default function DetailsPage() { export default function DetailsPage() {

View File

@ -0,0 +1 @@
{"AF":"Afghanistan","AL":"Albanien","DZ":"Algeriet","AS":"Amerikansk Samoa","UM":"Amerikanske overs\u00f8iske \u00f8er","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktis","AG":"Antigua og Barbuda","AR":"Argentina","AM":"Armenien","AW":"Aruba","AZ":"Aserbajdsjan","AU":"Australien","BS":"Bahamas","BH":"Bahrain","BD":"Bangladesh","BB":"Barbados","BE":"Belgien","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivia","BA":"Bosnien-Hercegovina","BW":"Botswana","BV":"Bouvet\u00f8en","BR":"Brasilien","BN":"Brunei","BG":"Bulgarien","BF":"Burkina Faso","BI":"Burundi","KH":"Cambodja","CM":"Cameroun","CA":"Canada","KY":"Cayman\u00f8erne","CL":"Chile","CC":"Cocos\u00f8erne","CO":"Colombia","KM":"Comorerne","CG":"Congo-Brazzaville","CD":"Congo-Kinshasa","CK":"Cook\u00f8erne","CR":"Costa Rica","CU":"Cuba","CW":"Cura\u00e7ao","CY":"Cypern","DK":"Danmark","VI":"De Amerikanske Jomfru\u00f8er","VG":"De Britiske Jomfru\u00f8er","AE":"De Forenede Arabiske Emirater","TF":"De Franske Besiddelser i Det Sydlige Indiske Ocean og Antarktis","PS":"De pal\u00e6stinensiske omr\u00e5der","BQ":"De tidligere Nederlandske Antiller","CF":"Den Centralafrikanske Republik","DO":"Den Dominikanske Republik","IO":"Det Britiske Territorium i Det Indiske Ocean","DJ":"Djibouti","DM":"Dominica","EC":"Ecuador","EG":"Egypten","SV":"El Salvador","CI":"Elfenbenskysten","ER":"Eritrea","EE":"Estland","SZ":"Eswatini","ET":"Etiopien","FK":"Falklands\u00f8erne","FJ":"Fiji","PH":"Filippinerne","FI":"Finland","FR":"Frankrig","GF":"Fransk Guyana","PF":"Fransk Polynesien","FO":"F\u00e6r\u00f8erne","GA":"Gabon","GM":"Gambia","GE":"Georgien","GH":"Ghana","GI":"Gibraltar","GD":"Grenada","GR":"Gr\u00e6kenland","GL":"Gr\u00f8nland","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GW":"Guinea-Bissau","GY":"Guyana","HT":"Haiti","HM":"Heard Island og McDonald Islands","NL":"Holland","HN":"Honduras","BY":"Hviderusland","IN":"Indien","ID":"Indonesien","IQ":"Irak","IR":"Iran","IE":"Irland","IS":"Island","IM":"Isle of Man","IL":"Israel","IT":"Italien","JM":"Jamaica","JP":"Japan","JE":"Jersey","JO":"Jordan","CX":"Jule\u00f8en","CV":"Kap Verde","KZ":"Kasakhstan","KE":"Kenya","CN":"Kina","KG":"Kirgisistan","KI":"Kiribati","HR":"Kroatien","KW":"Kuwait","LA":"Laos","LS":"Lesotho","LV":"Letland","LB":"Libanon","LR":"Liberia","LY":"Libyen","LI":"Liechtenstein","LT":"Litauen","LU":"Luxembourg","MG":"Madagaskar","MW":"Malawi","MY":"Malaysia","MV":"Maldiverne","ML":"Mali","MT":"Malta","MA":"Marokko","MH":"Marshall\u00f8erne","MQ":"Martinique","MR":"Mauretanien","MU":"Mauritius","YT":"Mayotte","MX":"Mexico","FM":"Mikronesien","MD":"Moldova","MC":"Monaco","MN":"Mongoliet","ME":"Montenegro","MS":"Montserrat","MZ":"Mozambique","MM":"Myanmar (Burma)","NA":"Namibia","NR":"Nauru","NP":"Nepal","NZ":"New Zealand","NI":"Nicaragua","NE":"Niger","NG":"Nigeria","NU":"Niue","KP":"Nordkorea","MK":"Nordmakedonien","MP":"Nordmarianerne","NF":"Norfolk Island","NO":"Norge","NC":"Ny Kaledonien","OM":"Oman","PK":"Pakistan","PW":"Palau","PA":"Panama","PG":"Papua Ny Guinea","PY":"Paraguay","PE":"Peru","PN":"Pitcairn","PL":"Polen","PT":"Portugal","PR":"Puerto Rico","QA":"Qatar","RE":"R\u00e9union","RO":"Rum\u00e6nien","RU":"Rusland","RW":"Rwanda","BL":"Saint Barth\u00e9lemy","KN":"Saint Kitts og Nevis","LC":"Saint Lucia","MF":"Saint Martin","PM":"Saint Pierre og Miquelon","VC":"Saint Vincent og Grenadinerne","SB":"Salomon\u00f8erne","WS":"Samoa","SM":"San Marino","ST":"S\u00e3o Tom\u00e9 og Pr\u00edncipe","HK":"SAR Hongkong","MO":"SAR Macao","SA":"Saudi-Arabien","CH":"Schweiz","SN":"Senegal","RS":"Serbien","SC":"Seychellerne","SL":"Sierra Leone","SG":"Singapore","SX":"Sint Maarten","SK":"Slovakiet","SI":"Slovenien","SO":"Somalia","GS":"South Georgia og De Sydlige Sandwich\u00f8er","ES":"Spanien","LK":"Sri Lanka","SH":"St. Helena","GB":"Storbritannien","SD":"Sudan","SR":"Surinam","SJ":"Svalbard og Jan Mayen","SE":"Sverige","ZA":"Sydafrika","KR":"Sydkorea","SS":"Sydsudan","SY":"Syrien","TJ":"Tadsjikistan","TW":"Taiwan","TZ":"Tanzania","TD":"Tchad","TH":"Thailand","TL":"Timor-Leste","CZ":"Tjekkiet","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad og Tobago","TN":"Tunesien","TM":"Turkmenistan","TC":"Turks- og Caicos\u00f8erne","TV":"Tuvalu","TR":"Tyrkiet","DE":"Tyskland","UG":"Uganda","UA":"Ukraine","HU":"Ungarn","UY":"Uruguay","US":"USA","UZ":"Usbekistan","VU":"Vanuatu","VA":"Vatikanstaten","VE":"Venezuela","EH":"Vestsahara","VN":"Vietnam","WF":"Wallis og Futuna","YE":"Yemen","ZM":"Zambia","ZW":"Zimbabwe","GQ":"\u00c6kvatorialguinea","AT":"\u00d8strig","AX":"\u00c5land"}

View File

@ -0,0 +1 @@
{"AF":"Afghanistan","EG":"\u00c4gypten","AX":"\u00c5landinseln","AL":"Albanien","DZ":"Algerien","AS":"Amerikanisch-Samoa","VI":"Amerikanische Jungferninseln","UM":"Amerikanische \u00dcberseeinseln","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktis","AG":"Antigua und Barbuda","GQ":"\u00c4quatorialguinea","AR":"Argentinien","AM":"Armenien","AW":"Aruba","AZ":"Aserbaidschan","ET":"\u00c4thiopien","AU":"Australien","BS":"Bahamas","BH":"Bahrain","BD":"Bangladesch","BB":"Barbados","BY":"Belarus","BE":"Belgien","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivien","BQ":"Bonaire, Sint Eustatius und Saba","BA":"Bosnien und Herzegowina","BW":"Botsuana","BV":"Bouvetinsel","BR":"Brasilien","VG":"Britische Jungferninseln","IO":"Britisches Territorium im Indischen Ozean","BN":"Brunei Darussalam","BG":"Bulgarien","BF":"Burkina Faso","BI":"Burundi","CV":"Cabo Verde","CL":"Chile","CN":"China","CK":"Cookinseln","CR":"Costa Rica","CI":"C\u00f4te d\u2019Ivoire","CW":"Cura\u00e7ao","DK":"D\u00e4nemark","DE":"Deutschland","DM":"Dominica","DO":"Dominikanische Republik","DJ":"Dschibuti","EC":"Ecuador","SV":"El Salvador","ER":"Eritrea","EE":"Estland","SZ":"Eswatini","FK":"Falklandinseln","FO":"F\u00e4r\u00f6er","FJ":"Fidschi","FI":"Finnland","FR":"Frankreich","GF":"Franz\u00f6sisch-Guayana","PF":"Franz\u00f6sisch-Polynesien","TF":"Franz\u00f6sische S\u00fcd- und Antarktisgebiete","GA":"Gabun","GM":"Gambia","GE":"Georgien","GH":"Ghana","GI":"Gibraltar","GD":"Grenada","GR":"Griechenland","GL":"Gr\u00f6nland","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GW":"Guinea-Bissau","GY":"Guyana","HT":"Haiti","HM":"Heard und McDonaldinseln","HN":"Honduras","IN":"Indien","ID":"Indonesien","IQ":"Irak","IR":"Iran","IE":"Irland","IS":"Island","IM":"Isle of Man","IL":"Israel","IT":"Italien","JM":"Jamaika","JP":"Japan","YE":"Jemen","JE":"Jersey","JO":"Jordanien","KY":"Kaimaninseln","KH":"Kambodscha","CM":"Kamerun","CA":"Kanada","KZ":"Kasachstan","QA":"Katar","KE":"Kenia","KG":"Kirgisistan","KI":"Kiribati","CC":"Kokosinseln","CO":"Kolumbien","KM":"Komoren","CG":"Kongo-Brazzaville","CD":"Kongo-Kinshasa","HR":"Kroatien","CU":"Kuba","KW":"Kuwait","LA":"Laos","LS":"Lesotho","LV":"Lettland","LB":"Libanon","LR":"Liberia","LY":"Libyen","LI":"Liechtenstein","LT":"Litauen","LU":"Luxemburg","MG":"Madagaskar","MW":"Malawi","MY":"Malaysia","MV":"Malediven","ML":"Mali","MT":"Malta","MA":"Marokko","MH":"Marshallinseln","MQ":"Martinique","MR":"Mauretanien","MU":"Mauritius","YT":"Mayotte","MX":"Mexiko","FM":"Mikronesien","MC":"Monaco","MN":"Mongolei","ME":"Montenegro","MS":"Montserrat","MZ":"Mosambik","MM":"Myanmar","NA":"Namibia","NR":"Nauru","NP":"Nepal","NC":"Neukaledonien","NZ":"Neuseeland","NI":"Nicaragua","NL":"Niederlande","NE":"Niger","NG":"Nigeria","NU":"Niue","KP":"Nordkorea","MP":"N\u00f6rdliche Marianen","MK":"Nordmazedonien","NF":"Norfolkinsel","NO":"Norwegen","OM":"Oman","AT":"\u00d6sterreich","PK":"Pakistan","PS":"Pal\u00e4stinensische Autonomiegebiete","PW":"Palau","PA":"Panama","PG":"Papua-Neuguinea","PY":"Paraguay","PE":"Peru","PH":"Philippinen","PN":"Pitcairninseln","PL":"Polen","PT":"Portugal","PR":"Puerto Rico","MD":"Republik Moldau","RE":"R\u00e9union","RW":"Ruanda","RO":"Rum\u00e4nien","RU":"Russland","SB":"Salomonen","ZM":"Sambia","WS":"Samoa","SM":"San Marino","ST":"S\u00e3o Tom\u00e9 und Pr\u00edncipe","SA":"Saudi-Arabien","SE":"Schweden","CH":"Schweiz","SN":"Senegal","RS":"Serbien","SC":"Seychellen","SL":"Sierra Leone","ZW":"Simbabwe","SG":"Singapur","SX":"Sint Maarten","SK":"Slowakei","SI":"Slowenien","SO":"Somalia","HK":"Sonderverwaltungsregion Hongkong","MO":"Sonderverwaltungsregion Macau","ES":"Spanien","SJ":"Spitzbergen und Jan Mayen","LK":"Sri Lanka","BL":"St. Barth\u00e9lemy","SH":"St. Helena","KN":"St. Kitts und Nevis","LC":"St. Lucia","MF":"St. Martin","PM":"St. Pierre und Miquelon","VC":"St. Vincent und die Grenadinen","ZA":"S\u00fcdafrika","SD":"Sudan","GS":"S\u00fcdgeorgien und die S\u00fcdlichen Sandwichinseln","KR":"S\u00fcdkorea","SS":"S\u00fcdsudan","SR":"Suriname","SY":"Syrien","TJ":"Tadschikistan","TW":"Taiwan","TZ":"Tansania","TH":"Thailand","TL":"Timor-Leste","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad und Tobago","TD":"Tschad","CZ":"Tschechien","TN":"Tunesien","TR":"T\u00fcrkei","TM":"Turkmenistan","TC":"Turks- und Caicosinseln","TV":"Tuvalu","UG":"Uganda","UA":"Ukraine","HU":"Ungarn","UY":"Uruguay","UZ":"Usbekistan","VU":"Vanuatu","VA":"Vatikanstadt","VE":"Venezuela","AE":"Vereinigte Arabische Emirate","US":"Vereinigte Staaten","GB":"Vereinigtes K\u00f6nigreich","VN":"Vietnam","WF":"Wallis und Futuna","CX":"Weihnachtsinsel","EH":"Westsahara","CF":"Zentralafrikanische Republik","CY":"Zypern"}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"AF":"Afghanistan","AX":"\u00c5land Islands","AL":"Albania","DZ":"Algeria","AS":"American Samoa","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarctica","AG":"Antigua & Barbuda","AR":"Argentina","AM":"Armenia","AW":"Aruba","AU":"Australia","AT":"Austria","AZ":"Azerbaijan","BS":"Bahamas","BH":"Bahrain","BD":"Bangladesh","BB":"Barbados","BY":"Belarus","BE":"Belgium","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivia","BA":"Bosnia & Herzegovina","BW":"Botswana","BV":"Bouvet Island","BR":"Brazil","IO":"British Indian Ocean Territory","VG":"British Virgin Islands","BN":"Brunei","BG":"Bulgaria","BF":"Burkina Faso","BI":"Burundi","KH":"Cambodia","CM":"Cameroon","CA":"Canada","CV":"Cape Verde","BQ":"Caribbean Netherlands","KY":"Cayman Islands","CF":"Central African Republic","TD":"Chad","CL":"Chile","CN":"China","CX":"Christmas Island","CC":"Cocos (Keeling) Islands","CO":"Colombia","KM":"Comoros","CG":"Congo - Brazzaville","CD":"Congo - Kinshasa","CK":"Cook Islands","CR":"Costa Rica","CI":"C\u00f4te d\u2019Ivoire","HR":"Croatia","CU":"Cuba","CW":"Cura\u00e7ao","CY":"Cyprus","CZ":"Czechia","DK":"Denmark","DJ":"Djibouti","DM":"Dominica","DO":"Dominican Republic","EC":"Ecuador","EG":"Egypt","SV":"El Salvador","GQ":"Equatorial Guinea","ER":"Eritrea","EE":"Estonia","SZ":"Eswatini","ET":"Ethiopia","FK":"Falkland Islands","FO":"Faroe Islands","FJ":"Fiji","FI":"Finland","FR":"France","GF":"French Guiana","PF":"French Polynesia","TF":"French Southern Territories","GA":"Gabon","GM":"Gambia","GE":"Georgia","DE":"Germany","GH":"Ghana","GI":"Gibraltar","GR":"Greece","GL":"Greenland","GD":"Grenada","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GW":"Guinea-Bissau","GY":"Guyana","HT":"Haiti","HM":"Heard & McDonald Islands","HN":"Honduras","HK":"Hong Kong SAR China","HU":"Hungary","IS":"Iceland","IN":"India","ID":"Indonesia","IR":"Iran","IQ":"Iraq","IE":"Ireland","IM":"Isle of Man","IL":"Israel","IT":"Italy","JM":"Jamaica","JP":"Japan","JE":"Jersey","JO":"Jordan","KZ":"Kazakhstan","KE":"Kenya","KI":"Kiribati","KW":"Kuwait","KG":"Kyrgyzstan","LA":"Laos","LV":"Latvia","LB":"Lebanon","LS":"Lesotho","LR":"Liberia","LY":"Libya","LI":"Liechtenstein","LT":"Lithuania","LU":"Luxembourg","MO":"Macao SAR China","MG":"Madagascar","MW":"Malawi","MY":"Malaysia","MV":"Maldives","ML":"Mali","MT":"Malta","MH":"Marshall Islands","MQ":"Martinique","MR":"Mauritania","MU":"Mauritius","YT":"Mayotte","MX":"Mexico","FM":"Micronesia","MD":"Moldova","MC":"Monaco","MN":"Mongolia","ME":"Montenegro","MS":"Montserrat","MA":"Morocco","MZ":"Mozambique","MM":"Myanmar (Burma)","NA":"Namibia","NR":"Nauru","NP":"Nepal","NL":"Netherlands","NC":"New Caledonia","NZ":"New Zealand","NI":"Nicaragua","NE":"Niger","NG":"Nigeria","NU":"Niue","NF":"Norfolk Island","KP":"North Korea","MK":"North Macedonia","MP":"Northern Mariana Islands","NO":"Norway","OM":"Oman","PK":"Pakistan","PW":"Palau","PS":"Palestinian Territories","PA":"Panama","PG":"Papua New Guinea","PY":"Paraguay","PE":"Peru","PH":"Philippines","PN":"Pitcairn Islands","PL":"Poland","PT":"Portugal","PR":"Puerto Rico","QA":"Qatar","RE":"R\u00e9union","RO":"Romania","RU":"Russia","RW":"Rwanda","WS":"Samoa","SM":"San Marino","ST":"S\u00e3o Tom\u00e9 & Pr\u00edncipe","SA":"Saudi Arabia","SN":"Senegal","RS":"Serbia","SC":"Seychelles","SL":"Sierra Leone","SG":"Singapore","SX":"Sint Maarten","SK":"Slovakia","SI":"Slovenia","SB":"Solomon Islands","SO":"Somalia","ZA":"South Africa","GS":"South Georgia & South Sandwich Islands","KR":"South Korea","SS":"South Sudan","ES":"Spain","LK":"Sri Lanka","BL":"St. Barth\u00e9lemy","SH":"St. Helena","KN":"St. Kitts & Nevis","LC":"St. Lucia","MF":"St. Martin","PM":"St. Pierre & Miquelon","VC":"St. Vincent & Grenadines","SD":"Sudan","SR":"Suriname","SJ":"Svalbard & Jan Mayen","SE":"Sweden","CH":"Switzerland","SY":"Syria","TW":"Taiwan","TJ":"Tajikistan","TZ":"Tanzania","TH":"Thailand","TL":"Timor-Leste","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad & Tobago","TN":"Tunisia","TR":"Turkey","TM":"Turkmenistan","TC":"Turks & Caicos Islands","TV":"Tuvalu","UM":"U.S. Outlying Islands","VI":"U.S. Virgin Islands","UG":"Uganda","UA":"Ukraine","AE":"United Arab Emirates","GB":"United Kingdom","US":"United States","UY":"Uruguay","UZ":"Uzbekistan","VU":"Vanuatu","VA":"Vatican City","VE":"Venezuela","VN":"Vietnam","WF":"Wallis & Futuna","EH":"Western Sahara","YE":"Yemen","ZM":"Zambia","ZW":"Zimbabwe"}

View File

@ -0,0 +1 @@
{"AF":"Afganist\u00e1n","AL":"Albania","DE":"Alemania","AD":"Andorra","AO":"Angola","AI":"Anguila","AQ":"Ant\u00e1rtida","AG":"Antigua y Barbuda","SA":"Arabia Saudita","DZ":"Argelia","AR":"Argentina","AM":"Armenia","AW":"Aruba","AU":"Australia","AT":"Austria","AZ":"Azerbaiy\u00e1n","BS":"Bahamas","BD":"Banglad\u00e9s","BB":"Barbados","BH":"Bar\u00e9in","BE":"B\u00e9lgica","BZ":"Belice","BJ":"Ben\u00edn","BM":"Bermudas","BY":"Bielorrusia","BO":"Bolivia","BA":"Bosnia y Herzegovina","BW":"Botsuana","BR":"Brasil","BN":"Brun\u00e9i","BG":"Bulgaria","BF":"Burkina Faso","BI":"Burundi","BT":"But\u00e1n","CV":"Cabo Verde","KH":"Camboya","CM":"Camer\u00fan","CA":"Canad\u00e1","BQ":"Caribe neerland\u00e9s","QA":"Catar","TD":"Chad","CZ":"Chequia","CL":"Chile","CN":"China","CY":"Chipre","VA":"Ciudad del Vaticano","CO":"Colombia","KM":"Comoras","CG":"Congo","KP":"Corea del Norte","KR":"Corea del Sur","CR":"Costa Rica","CI":"C\u00f4te d\u2019Ivoire","HR":"Croacia","CU":"Cuba","CW":"Curazao","DK":"Dinamarca","DM":"Dominica","EC":"Ecuador","EG":"Egipto","SV":"El Salvador","AE":"Emiratos \u00c1rabes Unidos","ER":"Eritrea","SK":"Eslovaquia","SI":"Eslovenia","ES":"Espa\u00f1a","US":"Estados Unidos","EE":"Estonia","SZ":"Eswatini","ET":"Etiop\u00eda","PH":"Filipinas","FI":"Finlandia","FJ":"Fiyi","FR":"Francia","GA":"Gab\u00f3n","GM":"Gambia","GE":"Georgia","GH":"Ghana","GI":"Gibraltar","GD":"Granada","GR":"Grecia","GL":"Groenlandia","GP":"Guadalupe","GU":"Guam","GT":"Guatemala","GF":"Guayana Francesa","GG":"Guernsey","GN":"Guinea","GQ":"Guinea Ecuatorial","GW":"Guinea-Bis\u00e1u","GY":"Guyana","HT":"Hait\u00ed","HN":"Honduras","HU":"Hungr\u00eda","IN":"India","ID":"Indonesia","IQ":"Irak","IR":"Ir\u00e1n","IE":"Irlanda","BV":"Isla Bouvet","IM":"Isla de Man","CX":"Isla de Navidad","NF":"Isla Norfolk","IS":"Islandia","AX":"Islas \u00c5land","KY":"Islas Caim\u00e1n","CC":"Islas Cocos","CK":"Islas Cook","FO":"Islas Feroe","GS":"Islas Georgia del Sur y Sandwich del Sur","HM":"Islas Heard y McDonald","FK":"Islas Malvinas","MP":"Islas Marianas del Norte","MH":"Islas Marshall","UM":"Islas menores alejadas de EE. UU.","PN":"Islas Pitcairn","SB":"Islas Salom\u00f3n","TC":"Islas Turcas y Caicos","VG":"Islas V\u00edrgenes Brit\u00e1nicas","VI":"Islas V\u00edrgenes de EE. UU.","IL":"Israel","IT":"Italia","JM":"Jamaica","JP":"Jap\u00f3n","JE":"Jersey","JO":"Jordania","KZ":"Kazajist\u00e1n","KE":"Kenia","KG":"Kirguist\u00e1n","KI":"Kiribati","KW":"Kuwait","LA":"Laos","LS":"Lesoto","LV":"Letonia","LB":"L\u00edbano","LR":"Liberia","LY":"Libia","LI":"Liechtenstein","LT":"Lituania","LU":"Luxemburgo","MK":"Macedonia del Norte","MG":"Madagascar","MY":"Malasia","MW":"Malaui","MV":"Maldivas","ML":"Mali","MT":"Malta","MA":"Marruecos","MQ":"Martinica","MU":"Mauricio","MR":"Mauritania","YT":"Mayotte","MX":"M\u00e9xico","FM":"Micronesia","MD":"Moldavia","MC":"M\u00f3naco","MN":"Mongolia","ME":"Montenegro","MS":"Montserrat","MZ":"Mozambique","MM":"Myanmar (Birmania)","NA":"Namibia","NR":"Nauru","NP":"Nepal","NI":"Nicaragua","NE":"N\u00edger","NG":"Nigeria","NU":"Niue","NO":"Noruega","NC":"Nueva Caledonia","NZ":"Nueva Zelanda","OM":"Om\u00e1n","NL":"Pa\u00edses Bajos","PK":"Pakist\u00e1n","PW":"Palaos","PA":"Panam\u00e1","PG":"Pap\u00faa Nueva Guinea","PY":"Paraguay","PE":"Per\u00fa","PF":"Polinesia Francesa","PL":"Polonia","PT":"Portugal","PR":"Puerto Rico","HK":"RAE de Hong Kong (China)","MO":"RAE de Macao (China)","GB":"Reino Unido","CF":"Rep\u00fablica Centroafricana","CD":"Rep\u00fablica Democr\u00e1tica del Congo","DO":"Rep\u00fablica Dominicana","RE":"Reuni\u00f3n","RW":"Ruanda","RO":"Rumania","RU":"Rusia","EH":"S\u00e1hara Occidental","WS":"Samoa","AS":"Samoa Americana","BL":"San Bartolom\u00e9","KN":"San Crist\u00f3bal y Nieves","SM":"San Marino","MF":"San Mart\u00edn","PM":"San Pedro y Miquel\u00f3n","VC":"San Vicente y las Granadinas","SH":"Santa Elena","LC":"Santa Luc\u00eda","ST":"Santo Tom\u00e9 y Pr\u00edncipe","SN":"Senegal","RS":"Serbia","SC":"Seychelles","SL":"Sierra Leona","SG":"Singapur","SX":"Sint Maarten","SY":"Siria","SO":"Somalia","LK":"Sri Lanka","ZA":"Sud\u00e1frica","SD":"Sud\u00e1n","SS":"Sud\u00e1n del Sur","SE":"Suecia","CH":"Suiza","SR":"Surinam","SJ":"Svalbard y Jan Mayen","TH":"Tailandia","TW":"Taiw\u00e1n","TZ":"Tanzania","TJ":"Tayikist\u00e1n","IO":"Territorio Brit\u00e1nico del Oc\u00e9ano \u00cdndico","TF":"Territorios Australes Franceses","PS":"Territorios Palestinos","TL":"Timor-Leste","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad y Tobago","TN":"T\u00fanez","TM":"Turkmenist\u00e1n","TR":"Turqu\u00eda","TV":"Tuvalu","UA":"Ucrania","UG":"Uganda","UY":"Uruguay","UZ":"Uzbekist\u00e1n","VU":"Vanuatu","VE":"Venezuela","VN":"Vietnam","WF":"Wallis y Futuna","YE":"Yemen","DJ":"Yibuti","ZM":"Zambia","ZW":"Zimbabue"}

View File

@ -0,0 +1 @@
{"AF":"Afganistan","AX":"\u00c1land","AL":"Albania","DZ":"Algeria","AS":"Amerikanska Samoa","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktis","AG":"Antigua & Barbuda","AR":"Argentina","AM":"Armenia","AW":"Aruba","AZ":"Aserbadjan","AU":"Avstralia","BS":"Bahamaoyggjar","BD":"Bangladesj","BB":"Barbados","BH":"Barein","BE":"Belgia","BZ":"Belis","BJ":"Benin","BM":"Bermuda","BO":"Bolivia","BA":"Bosnia-Hersegovina","BW":"Botsvana","BV":"Bouvetoyggj","BR":"Brasil","BN":"Brunei","BG":"Bulgaria","BF":"Burkina Faso","BI":"Burundi","BT":"Butan","KY":"Caymanoyggjar","CK":"Cooksoyggjar","CW":"Cura\u00e7ao","DK":"Danmark","DJ":"Djibuti","DM":"Dominika","DO":"Dominikal\u00fd\u00f0veldi\u00f0","EG":"Egyptaland","EC":"Ekvador","GQ":"Ekvatorguinea","SV":"El Salvador","ER":"Eritrea","EE":"Estland","SZ":"Esvatini","ET":"Etiopia","AT":"Eysturr\u00edki","TL":"Eysturtimor","FK":"Falklandsoyggjar","FJ":"Fiji","CI":"F\u00edlabeinsstrondin","PH":"Filipsoyggjar","FI":"Finnland","FR":"Frakland","GF":"Franska Gujana","PF":"Franska Polynesia","TF":"Fronsku sunnaru landa\u00f8ki","FO":"F\u00f8royar","GA":"Gabon","GM":"Gambia","GH":"Gana","GE":"Georgia","GI":"Gibraltar","GD":"Grenada","GR":"Grikkaland","CV":"Gr\u00f8nh\u00f8vdaoyggjar","GL":"Gr\u00f8nland","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GW":"Guinea-Bissau","GY":"Gujana","HT":"Haiti","HM":"Heard og McDonaldoyggjar","HN":"Honduras","HK":"Hong Kong SAR Kina","BY":"Hv\u00edtarussland","IN":"India","ID":"Indonesia","IQ":"Irak","IR":"Iran","IE":"\u00cdrland","IS":"\u00cdsland","IM":"Isle of Man","IL":"\u00cdsrael","IT":"Italia","JM":"Jamaika","JP":"Japan","YE":"Jemen","JE":"Jersey","CX":"J\u00f3laoyggjin","JO":"Jordan","KH":"Kambodja","CM":"Kamerun","CA":"Kanada","KZ":"Kasakstan","QA":"Katar","CZ":"Kekkia","KE":"Kenja","CL":"Kili","CN":"Kina","KG":"Kirgisia","KI":"Kiribati","TD":"Kjad","CC":"Kokosoyggjar","CO":"Kolombia","KM":"Komoroyggjar","CG":"Kongo","CD":"Kongo, Dem. L\u00fd\u00f0veldi\u00f0","CR":"Kosta Rika","HR":"Kroatia","CU":"Kuba","KW":"Kuvait","CY":"K\u00fdpros","LA":"Laos","LS":"Lesoto","LV":"Lettland","LB":"Libanon","LR":"Liberia","LY":"Libya","LI":"Liktinstein","LT":"Litava","LU":"Luksemborg","MG":"Madagaskar","MO":"Makao SAR Kina","MY":"Malaisia","MW":"Malavi","MV":"Maldivoyggjar","ML":"Mali","MT":"Malta","MA":"Marokko","MH":"Marshalloyggjar","MQ":"Martinique","YT":"Mayotte","MX":"Meksiko","CF":"Mi\u00f0afrikal\u00fd\u00f0veldi\u00f0","FM":"Mikronesiasamveldi\u00f0","MD":"Moldova","MC":"Monako","MN":"Mongolia","ME":"Montenegro","MS":"Montserrat","MR":"M\u00f3ritania","MU":"M\u00f3ritius","MZ":"Mosambik","MM":"Myanmar (Burma)","NA":"Namibia","NR":"Nauru","NP":"Nepal","NL":"Ni\u00f0urlond","BQ":"Ni\u00f0urlonds Karibia","NE":"Niger","NG":"Nigeria","NI":"Nikaragua","NU":"Niue","MP":"Nor\u00f0aru Mariuoyggjar","KP":"Nor\u00f0urkorea","NO":"Noreg","NF":"Norfolksoyggj","MK":"North Macedonia","NC":"N\u00fdkaled\u00f3nia","NZ":"N\u00fds\u00e6land","OM":"Oman","PK":"Pakistan","PW":"Palau","PS":"Palestinskt land\u00f8ki","PA":"Panama","PG":"Papua N\u00fdguinea","PY":"Paraguai","PE":"Peru","PN":"Pitcairnoyggjar","PL":"P\u00f3lland","PT":"Portugal","PR":"Puerto Riko","RE":"R\u00e9union","RW":"Ruanda","RO":"Rumenia","RU":"Russland","PM":"Saint Pierre & Miquelon","SB":"Salomonoyggjar","US":"Sambandsr\u00edki Amerika","UM":"Sambandsr\u00edki Amerikas fjarskotnu oyggjar","VI":"Sambandsr\u00edki Amerikas Jomfr\u00faoyggjar","ZM":"Sambia","AE":"Sameindu Emirr\u00edkini","WS":"Samoa","SM":"San Marino","ST":"Sao Tome & Prinsipi","SA":"Saudiarabia","SN":"Senegal","RS":"Serbia","SC":"Seyskelloyggjar","SL":"Sierra Leona","ZW":"Simbabvi","SG":"Singapor","SX":"Sint Maarten","SK":"Slovakia","SI":"Slovenia","SO":"Somalia","ES":"Spania","LK":"Sri Lanka","MF":"St-Martin","BL":"St. Barth\u00e9lemy","SH":"St. Helena","KN":"St. Kitts & Nevis","LC":"St. Lusia","VC":"St. Vinsent & Grenadinoyggjar","IO":"St\u00f3ra Bretlands Indiahavoyggjar","VG":"St\u00f3ra Bretlands Jomfr\u00faoyggjar","GB":"St\u00f3rabretland","SD":"Sudan","ZA":"Su\u00f0urafrika","GS":"Su\u00f0urgeorgia og Su\u00f0ursandwichoyggjar","KR":"Su\u00f0urkorea","SS":"Su\u00f0ursudan","SR":"Surinam","SJ":"Svalbard & Jan Mayen","CH":"Sveis","SE":"Sv\u00f8r\u00edki","SY":"S\u00fdria","TJ":"Tadsjikistan","TH":"Tailand","TW":"Taivan","TZ":"Tansania","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad & Tobago","TN":"Tunesia","TR":"Turkaland","TM":"Turkmenistan","TC":"Turks- og Caicosoyggjar","TV":"Tuvalu","DE":"T\u00fdskland","UG":"Uganda","UA":"Ukraina","HU":"Ungarn","UY":"Uruguai","UZ":"Usbekistan","VU":"Vanuatu","VA":"Vatikanb\u00fdur","VE":"Venesuela","EH":"Vestursahara","VN":"Vjetnam","WF":"Wallis- og Futunaoyggjar"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"AF":"Afghanistan","AX":"\u00c5land","AL":"Albani\u00eb","DZ":"Algerije","AS":"Amerikaans-Samoa","VI":"Amerikaanse Maagdeneilanden","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarctica","AG":"Antigua en Barbuda","AR":"Argentini\u00eb","AM":"Armeni\u00eb","AW":"Aruba","AU":"Australi\u00eb","AZ":"Azerbeidzjan","BS":"Bahama\u2019s","BH":"Bahrein","BD":"Bangladesh","BB":"Barbados","BY":"Belarus","BE":"Belgi\u00eb","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivia","BA":"Bosni\u00eb en Herzegovina","BW":"Botswana","BV":"Bouveteiland","BR":"Brazili\u00eb","IO":"Brits Indische Oceaanterritorium","VG":"Britse Maagdeneilanden","BN":"Brunei","BG":"Bulgarije","BF":"Burkina Faso","BI":"Burundi","KH":"Cambodja","CA":"Canada","BQ":"Caribisch Nederland","CF":"Centraal-Afrikaanse Republiek","CL":"Chili","CN":"China","CX":"Christmaseiland","CC":"Cocoseilanden","CO":"Colombia","KM":"Comoren","CG":"Congo-Brazzaville","CD":"Congo-Kinshasa","CK":"Cookeilanden","CR":"Costa Rica","CU":"Cuba","CW":"Cura\u00e7ao","CY":"Cyprus","DK":"Denemarken","DJ":"Djibouti","DM":"Dominica","DO":"Dominicaanse Republiek","DE":"Duitsland","EC":"Ecuador","EG":"Egypte","SV":"El Salvador","GQ":"Equatoriaal-Guinea","ER":"Eritrea","EE":"Estland","SZ":"eSwatini","ET":"Ethiopi\u00eb","FO":"Faer\u00f6er","FK":"Falklandeilanden","FJ":"Fiji","PH":"Filipijnen","FI":"Finland","FR":"Frankrijk","GF":"Frans-Guyana","PF":"Frans-Polynesi\u00eb","TF":"Franse Gebieden in de zuidelijke Indische Oceaan","GA":"Gabon","GM":"Gambia","GE":"Georgi\u00eb","GH":"Ghana","GI":"Gibraltar","GD":"Grenada","GR":"Griekenland","GL":"Groenland","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinee","GW":"Guinee-Bissau","GY":"Guyana","HT":"Ha\u00efti","HM":"Heard en McDonaldeilanden","HN":"Honduras","HU":"Hongarije","HK":"Hongkong SAR van China","IE":"Ierland","IS":"IJsland","IN":"India","ID":"Indonesi\u00eb","IQ":"Irak","IR":"Iran","IM":"Isle of Man","IL":"Isra\u00ebl","IT":"Itali\u00eb","CI":"Ivoorkust","JM":"Jamaica","JP":"Japan","YE":"Jemen","JE":"Jersey","JO":"Jordani\u00eb","KY":"Kaaimaneilanden","CV":"Kaapverdi\u00eb","CM":"Kameroen","KZ":"Kazachstan","KE":"Kenia","KG":"Kirgizi\u00eb","KI":"Kiribati","UM":"Kleine afgelegen eilanden van de Verenigde Staten","KW":"Koeweit","HR":"Kroati\u00eb","LA":"Laos","LS":"Lesotho","LV":"Letland","LB":"Libanon","LR":"Liberia","LY":"Libi\u00eb","LI":"Liechtenstein","LT":"Litouwen","LU":"Luxemburg","MO":"Macau SAR van China","MG":"Madagaskar","MW":"Malawi","MV":"Maldiven","MY":"Maleisi\u00eb","ML":"Mali","MT":"Malta","MA":"Marokko","MH":"Marshalleilanden","MQ":"Martinique","MR":"Mauritani\u00eb","MU":"Mauritius","YT":"Mayotte","MX":"Mexico","FM":"Micronesia","MD":"Moldavi\u00eb","MC":"Monaco","MN":"Mongoli\u00eb","ME":"Montenegro","MS":"Montserrat","MZ":"Mozambique","MM":"Myanmar (Birma)","NA":"Namibi\u00eb","NR":"Nauru","NL":"Nederland","NP":"Nepal","NI":"Nicaragua","NC":"Nieuw-Caledoni\u00eb","NZ":"Nieuw-Zeeland","NE":"Niger","NG":"Nigeria","NU":"Niue","KP":"Noord-Korea","MK":"Noord-Macedoni\u00eb","MP":"Noordelijke Marianen","NO":"Noorwegen","NF":"Norfolk","UG":"Oeganda","UA":"Oekra\u00efne","UZ":"Oezbekistan","OM":"Oman","TL":"Oost-Timor","AT":"Oostenrijk","PK":"Pakistan","PW":"Palau","PS":"Palestijnse gebieden","PA":"Panama","PG":"Papoea-Nieuw-Guinea","PY":"Paraguay","PE":"Peru","PN":"Pitcairneilanden","PL":"Polen","PT":"Portugal","PR":"Puerto Rico","QA":"Qatar","RE":"R\u00e9union","RO":"Roemeni\u00eb","RU":"Rusland","RW":"Rwanda","KN":"Saint Kitts en Nevis","LC":"Saint Lucia","VC":"Saint Vincent en de Grenadines","BL":"Saint-Barth\u00e9lemy","MF":"Saint-Martin","PM":"Saint-Pierre en Miquelon","SB":"Salomonseilanden","WS":"Samoa","SM":"San Marino","ST":"Sao Tom\u00e9 en Principe","SA":"Saoedi-Arabi\u00eb","SN":"Senegal","RS":"Servi\u00eb","SC":"Seychellen","SL":"Sierra Leone","SG":"Singapore","SH":"Sint-Helena","SX":"Sint-Maarten","SI":"Sloveni\u00eb","SK":"Slowakije","SD":"Soedan","SO":"Somali\u00eb","ES":"Spanje","SJ":"Spitsbergen en Jan Mayen","LK":"Sri Lanka","SR":"Suriname","SY":"Syri\u00eb","TJ":"Tadzjikistan","TW":"Taiwan","TZ":"Tanzania","TH":"Thailand","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad en Tobago","TD":"Tsjaad","CZ":"Tsjechi\u00eb","TN":"Tunesi\u00eb","TR":"Turkije","TM":"Turkmenistan","TC":"Turks- en Caicoseilanden","TV":"Tuvalu","UY":"Uruguay","VU":"Vanuatu","VA":"Vaticaanstad","VE":"Venezuela","GB":"Verenigd Koninkrijk","AE":"Verenigde Arabische Emiraten","US":"Verenigde Staten","VN":"Vietnam","WF":"Wallis en Futuna","EH":"Westelijke Sahara","ZM":"Zambia","ZW":"Zimbabwe","ZA":"Zuid-Afrika","GS":"Zuid-Georgia en Zuidelijke Sandwicheilanden","KR":"Zuid-Korea","SS":"Zuid-Soedan","SE":"Zweden","CH":"Zwitserland"}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"AF":"Afghanistan","AL":"Albanien","DZ":"Algeriet","VI":"Amerikanska Jungfru\u00f6arna","AS":"Amerikanska Samoa","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktis","AG":"Antigua och Barbuda","AR":"Argentina","AM":"Armenien","AW":"Aruba","AU":"Australien","AZ":"Azerbajdzjan","BS":"Bahamas","BH":"Bahrain","BD":"Bangladesh","BB":"Barbados","BE":"Belgien","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivia","BA":"Bosnien och Hercegovina","BW":"Botswana","BV":"Bouvet\u00f6n","BR":"Brasilien","VG":"Brittiska Jungfru\u00f6arna","IO":"Brittiska territoriet i Indiska oceanen","BN":"Brunei","BG":"Bulgarien","BF":"Burkina Faso","BI":"Burundi","KY":"Cayman\u00f6arna","CF":"Centralafrikanska republiken","CL":"Chile","CO":"Colombia","CK":"Cook\u00f6arna","CR":"Costa Rica","CW":"Cura\u00e7ao","CY":"Cypern","CI":"C\u00f4te d\u2019Ivoire","DK":"Danmark","DJ":"Djibouti","DM":"Dominica","DO":"Dominikanska republiken","EC":"Ecuador","EG":"Egypten","GQ":"Ekvatorialguinea","SV":"El Salvador","ER":"Eritrea","EE":"Estland","ET":"Etiopien","FK":"Falklands\u00f6arna","FJ":"Fiji","PH":"Filippinerna","FI":"Finland","FR":"Frankrike","GF":"Franska Guyana","PF":"Franska Polynesien","TF":"Franska sydterritorierna","FO":"F\u00e4r\u00f6arna","AE":"F\u00f6renade Arabemiraten","GA":"Gabon","GM":"Gambia","GE":"Georgien","GH":"Ghana","GI":"Gibraltar","GR":"Grekland","GD":"Grenada","GL":"Gr\u00f6nland","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GW":"Guinea-Bissau","GY":"Guyana","HT":"Haiti","HM":"Heard\u00f6n och McDonald\u00f6arna","HN":"Honduras","HK":"Hongkong","IN":"Indien","ID":"Indonesien","IQ":"Irak","IR":"Iran","IE":"Irland","IS":"Island","IM":"Isle of Man","IL":"Israel","IT":"Italien","JM":"Jamaica","JP":"Japan","YE":"Jemen","JE":"Jersey","JO":"Jordanien","CX":"Jul\u00f6n","KH":"Kambodja","CM":"Kamerun","CA":"Kanada","CV":"Kap Verde","BQ":"Karibiska Nederl\u00e4nderna","KZ":"Kazakstan","KE":"Kenya","CN":"Kina","KG":"Kirgizistan","KI":"Kiribati","CC":"Kokos\u00f6arna","KM":"Komorerna","CG":"Kongo-Brazzaville","CD":"Kongo-Kinshasa","HR":"Kroatien","CU":"Kuba","KW":"Kuwait","LA":"Laos","LS":"Lesotho","LV":"Lettland","LB":"Libanon","LR":"Liberia","LY":"Libyen","LI":"Liechtenstein","LT":"Litauen","LU":"Luxemburg","MO":"Macao","MG":"Madagaskar","MW":"Malawi","MY":"Malaysia","MV":"Maldiverna","ML":"Mali","MT":"Malta","MA":"Marocko","MH":"Marshall\u00f6arna","MQ":"Martinique","MR":"Mauretanien","MU":"Mauritius","YT":"Mayotte","MX":"Mexiko","FM":"Mikronesien","MZ":"Mo\u00e7ambique","MD":"Moldavien","MC":"Monaco","MN":"Mongoliet","ME":"Montenegro","MS":"Montserrat","MM":"Myanmar (Burma)","NA":"Namibia","NR":"Nauru","NL":"Nederl\u00e4nderna","NP":"Nepal","NI":"Nicaragua","NE":"Niger","NG":"Nigeria","NU":"Niue","KP":"Nordkorea","MK":"Nordmakedonien","MP":"Nordmarianerna","NF":"Norfolk\u00f6n","NO":"Norge","NC":"Nya Kaledonien","NZ":"Nya Zeeland","OM":"Oman","PK":"Pakistan","PW":"Palau","PS":"Palestinska territorierna","PA":"Panama","PG":"Papua Nya Guinea","PY":"Paraguay","PE":"Peru","PN":"Pitcairn\u00f6arna","PL":"Polen","PT":"Portugal","PR":"Puerto Rico","QA":"Qatar","RE":"R\u00e9union","RO":"Rum\u00e4nien","RW":"Rwanda","RU":"Ryssland","BL":"S:t Barth\u00e9lemy","SH":"S:t Helena","KN":"S:t Kitts och Nevis","LC":"S:t Lucia","PM":"S:t Pierre och Miquelon","VC":"S:t Vincent och Grenadinerna","MF":"Saint-Martin","SB":"Salomon\u00f6arna","WS":"Samoa","SM":"San Marino","ST":"S\u00e3o Tom\u00e9 och Pr\u00edncipe","SA":"Saudiarabien","CH":"Schweiz","SN":"Senegal","RS":"Serbien","SC":"Seychellerna","SL":"Sierra Leone","SG":"Singapore","SX":"Sint Maarten","SK":"Slovakien","SI":"Slovenien","SO":"Somalia","ES":"Spanien","LK":"Sri Lanka","GB":"Storbritannien","SD":"Sudan","SR":"Surinam","SJ":"Svalbard och Jan Mayen","SE":"Sverige","SZ":"Swaziland","ZA":"Sydafrika","GS":"Sydgeorgien och Sydsandwich\u00f6arna","KR":"Sydkorea","SS":"Sydsudan","SY":"Syrien","TJ":"Tadzjikistan","TW":"Taiwan","TZ":"Tanzania","TD":"Tchad","TH":"Thailand","CZ":"Tjeckien","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad och Tobago","TN":"Tunisien","TR":"Turkiet","TM":"Turkmenistan","TC":"Turks- och Caicos\u00f6arna","TV":"Tuvalu","DE":"Tyskland","UG":"Uganda","UA":"Ukraina","HU":"Ungern","UY":"Uruguay","US":"USA","UM":"USA:s yttre \u00f6ar","UZ":"Uzbekistan","VU":"Vanuatu","VA":"Vatikanstaten","VE":"Venezuela","VN":"Vietnam","BY":"Vitryssland","EH":"V\u00e4stsahara","WF":"Wallis- och Futuna\u00f6arna","ZM":"Zambia","ZW":"Zimbabwe","AX":"\u00c5land","AT":"\u00d6sterrike","TL":"\u00d6sttimor"}

View File

@ -0,0 +1 @@
{"UM":"ABD K\u00fc\u00e7\u00fck Harici Adalar\u0131","VI":"ABD Virjin Adalar\u0131","AF":"Afganistan","AX":"\u00c5land Adalar\u0131","DE":"Almanya","US":"Amerika Birle\u015fik Devletleri","AS":"Amerikan Samoas\u0131","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktika","AG":"Antigua ve Barbuda","AR":"Arjantin","AL":"Arnavutluk","AW":"Aruba","AU":"Avustralya","AT":"Avusturya","AZ":"Azerbaycan","BS":"Bahamalar","BH":"Bahreyn","BD":"Banglade\u015f","BB":"Barbados","EH":"Bat\u0131 Sahra","BY":"Belarus","BE":"Bel\u00e7ika","BZ":"Belize","BJ":"Benin","BM":"Bermuda","AE":"Birle\u015fik Arap Emirlikleri","GB":"Birle\u015fik Krall\u0131k","BO":"Bolivya","BA":"Bosna-Hersek","BW":"Botsvana","BV":"Bouvet Adas\u0131","BR":"Brezilya","IO":"Britanya Hint Okyanusu Topraklar\u0131","VG":"Britanya Virjin Adalar\u0131","BN":"Brunei","BG":"Bulgaristan","BF":"Burkina Faso","BI":"Burundi","BT":"Butan","CV":"Cape Verde","KY":"Cayman Adalar\u0131","GI":"Cebelitar\u0131k","DZ":"Cezayir","CX":"Christmas Adas\u0131","DJ":"Cibuti","CC":"Cocos (Keeling) Adalar\u0131","CK":"Cook Adalar\u0131","CI":"C\u00f4te d\u2019Ivoire","CW":"Cura\u00e7ao","TD":"\u00c7ad","CZ":"\u00c7ekya","CN":"\u00c7in","HK":"\u00c7in Hong Kong \u00d6\u0130B","MO":"\u00c7in Makao \u00d6\u0130B","DK":"Danimarka","DO":"Dominik Cumhuriyeti","DM":"Dominika","EC":"Ekvador","GQ":"Ekvator Ginesi","SV":"El Salvador","ID":"Endonezya","ER":"Eritre","AM":"Ermenistan","EE":"Estonya","SZ":"Esvatini","ET":"Etiyopya","FK":"Falkland Adalar\u0131","FO":"Faroe Adalar\u0131","MA":"Fas","FJ":"Fiji","PH":"Filipinler","PS":"Filistin B\u00f6lgeleri","FI":"Finlandiya","FR":"Fransa","GF":"Frans\u0131z Guyanas\u0131","TF":"Frans\u0131z G\u00fcney Topraklar\u0131","PF":"Frans\u0131z Polinezyas\u0131","GA":"Gabon","GM":"Gambiya","GH":"Gana","GN":"Gine","GW":"Gine-Bissau","GD":"Grenada","GL":"Gr\u00f6nland","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GY":"Guyana","ZA":"G\u00fcney Afrika","GS":"G\u00fcney Georgia ve G\u00fcney Sandwich Adalar\u0131","KR":"G\u00fcney Kore","SS":"G\u00fcney Sudan","GE":"G\u00fcrcistan","HT":"Haiti","HM":"Heard Adas\u0131 ve McDonald Adalar\u0131","HR":"H\u0131rvatistan","IN":"Hindistan","NL":"Hollanda","HN":"Honduras","IQ":"Irak","IR":"\u0130ran","IE":"\u0130rlanda","ES":"\u0130spanya","IL":"\u0130srail","SE":"\u0130sve\u00e7","CH":"\u0130svi\u00e7re","IT":"\u0130talya","IS":"\u0130zlanda","JM":"Jamaika","JP":"Japonya","JE":"Jersey","KH":"Kambo\u00e7ya","CM":"Kamerun","CA":"Kanada","ME":"Karada\u011f","BQ":"Karayip Hollandas\u0131","QA":"Katar","KZ":"Kazakistan","KE":"Kenya","CY":"K\u0131br\u0131s","KG":"K\u0131rg\u0131zistan","KI":"Kiribati","CO":"Kolombiya","KM":"Komorlar","CG":"Kongo - Brazavil","CD":"Kongo - Kin\u015fasa","CR":"Kosta Rika","KW":"Kuveyt","KP":"Kuzey Kore","MK":"Kuzey Makedonya","MP":"Kuzey Mariana Adalar\u0131","CU":"K\u00fcba","LA":"Laos","LS":"Lesotho","LV":"Letonya","LR":"Liberya","LY":"Libya","LI":"Liechtenstein","LT":"Litvanya","LB":"L\u00fcbnan","LU":"L\u00fcksemburg","HU":"Macaristan","MG":"Madagaskar","MW":"Malavi","MV":"Maldivler","MY":"Malezya","ML":"Mali","MT":"Malta","IM":"Man Adas\u0131","MH":"Marshall Adalar\u0131","MQ":"Martinik","MU":"Mauritius","YT":"Mayotte","MX":"Meksika","EG":"M\u0131s\u0131r","FM":"Mikronezya","MN":"Mo\u011folistan","MD":"Moldova","MC":"Monako","MS":"Montserrat","MR":"Moritanya","MZ":"Mozambik","MM":"Myanmar (Burma)","NA":"Namibya","NR":"Nauru","NP":"Nepal","NE":"Nijer","NG":"Nijerya","NI":"Nikaragua","NU":"Niue","NF":"Norfolk Adas\u0131","NO":"Norve\u00e7","CF":"Orta Afrika Cumhuriyeti","UZ":"\u00d6zbekistan","PK":"Pakistan","PW":"Palau","PA":"Panama","PG":"Papua Yeni Gine","PY":"Paraguay","PE":"Peru","PN":"Pitcairn Adalar\u0131","PL":"Polonya","PT":"Portekiz","PR":"Porto Riko","RE":"Reunion","RO":"Romanya","RW":"Ruanda","RU":"Rusya","BL":"Saint Barthelemy","SH":"Saint Helena","KN":"Saint Kitts ve Nevis","LC":"Saint Lucia","MF":"Saint Martin","PM":"Saint Pierre ve Miquelon","VC":"Saint Vincent ve Grenadinler","WS":"Samoa","SM":"San Marino","ST":"Sao Tome ve Principe","SN":"Senegal","SC":"Sey\u015feller","RS":"S\u0131rbistan","SL":"Sierra Leone","SG":"Singapur","SX":"Sint Maarten","SK":"Slovakya","SI":"Slovenya","SB":"Solomon Adalar\u0131","SO":"Somali","LK":"Sri Lanka","SD":"Sudan","SR":"Surinam","SY":"Suriye","SA":"Suudi Arabistan","SJ":"Svalbard ve Jan Mayen","CL":"\u015eili","TJ":"Tacikistan","TZ":"Tanzanya","TH":"Tayland","TW":"Tayvan","TL":"Timor-Leste","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad ve Tobago","TN":"Tunus","TC":"Turks ve Caicos Adalar\u0131","TV":"Tuvalu","TR":"T\u00fcrkiye","TM":"T\u00fcrkmenistan","UG":"Uganda","UA":"Ukrayna","OM":"Umman","UY":"Uruguay","JO":"\u00dcrd\u00fcn","VU":"Vanuatu","VA":"Vatikan","VE":"Venezuela","VN":"Vietnam","WF":"Wallis ve Futuna","YE":"Yemen","NC":"Yeni Kaledonya","NZ":"Yeni Zelanda","GR":"Yunanistan","ZM":"Zambiya","ZW":"Zimbabve"}

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,10 @@ const app = createSlice({
initialState: { initialState: {
locale: getItem(LOCALE_CONFIG) || 'en-US', locale: getItem(LOCALE_CONFIG) || 'en-US',
theme: getItem(THEME_CONFIG) || 'light', theme: getItem(THEME_CONFIG) || 'light',
versions: {
current: process.env.VERSION,
latest: null,
},
}, },
reducers: { reducers: {
setLocale(state, action) { setLocale(state, action) {
@ -17,9 +21,51 @@ const app = createSlice({
state.theme = action.payload; state.theme = action.payload;
return state; return state;
}, },
setVersions(state, action) {
state.versions = action.payload;
return state;
},
}, },
}); });
export const { setLocale, setTheme } = app.actions; export const { setLocale, setTheme, setVersions } = app.actions;
export default app.reducer; export default app.reducer;
export function checkVersion() {
return async (dispatch, getState) => {
const {
app: {
versions: { current },
},
} = getState();
const data = await fetch('https://api.github.com/repos/mikecao/umami/releases/latest', {
method: 'get',
headers: {
Accept: 'application/vnd.github.v3+json',
},
}).then(res => {
if (res.ok) {
return res.json();
}
return null;
});
if (!data) {
return;
}
const { tag_name } = data;
const latest = tag_name.startsWith('v') ? tag_name.slice(1) : tag_name;
return dispatch(
setVersions({
current,
latest,
}),
);
};
}

View File

@ -9,5 +9,5 @@ export default {
file: 'public/umami.js', file: 'public/umami.js',
format: 'iife', format: 'iife',
}, },
plugins: [resolve(), buble(), terser({ compress: { evaluate: false } })], plugins: [resolve(), buble({ objectAssign: true }), terser({ compress: { evaluate: false } })],
}; };

24
scripts/check-lang.js Normal file
View File

@ -0,0 +1,24 @@
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const messages = require('../lang/en-US.json');
const dir = path.resolve(__dirname, '../lang');
const files = fs.readdirSync(dir);
const keys = Object.keys(messages).sort();
files.forEach(file => {
if (file !== 'en-US.json') {
const lang = require(`../lang/${file}`);
console.log(chalk.yellowBright(`\n## ${file}`));
keys.forEach(key => {
const orig = messages[key];
const check = lang[key];
if (!check || check === orig) {
console.log(chalk.redBright('*'), chalk.greenBright(`${key}:`), orig);
}
});
}
});

4
scripts/start-env.js Normal file
View File

@ -0,0 +1,4 @@
require('dotenv').config();
const cli = require('next/dist/cli/next-start');
cli.nextStart(['-p', process.env.PORT || 3000, '-H', process.env.HOSTNAME || '0.0.0.0']);

View File

@ -1193,15 +1193,15 @@
resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6"
integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==
"@prisma/cli@2.7.1": "@prisma/cli@2.8.0":
version "2.7.1" version "2.8.0"
resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.7.1.tgz#98f2cb434bb931341e6c6292c7bab601e5f842f8" resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.8.0.tgz#919d7f66023affa76d14823212b62a8512cfd37d"
integrity sha512-0uA+gWkNQ35DveVHDPltiTCTr4wcXtEhnPs463IEM+Xn8dTv9x0gtZiYHSuQM3t7uwlOxj1rurBsqSbiljynfQ== integrity sha512-Kg1C47d75jdEIMmJif8TMlv/2Ihx08E1qWp0euwoZhjd807HGnjgC9tJYjTfkdf+NMJSAUbvoPXKInEX0HoOMw==
"@prisma/client@2.7.1": "@prisma/client@2.8.0":
version "2.7.1" version "2.8.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.7.1.tgz#0a37ddff7fe80ae3a86dfa620c1141c8607be6c2" resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.8.0.tgz#a0f7247786c9b6ee804437acf8215854c5eb3946"
integrity sha512-IEWDCuvIaQTira8/jAyf+uY+AuPPUFDIXMSN4zEA/gvoJv2woq7RmkaubS+NQVgDbbyOR6F3UcXLiFTYQDzZkQ== integrity sha512-5+GzRTkPnmv4OEV2tB8kwQt/xLLxBR/daJBcMt6pnnonJvrREsu0tSTdz2LJNPaj3kTT0fSS/OaeGMMdfVYSpw==
dependencies: dependencies:
pkg-up "^3.1.0" pkg-up "^3.1.0"
@ -4145,10 +4145,10 @@ for-own@^0.1.3:
dependencies: dependencies:
for-in "^1.0.1" for-in "^1.0.1"
formik@^2.1.5: formik@^2.1.6:
version "2.1.5" version "2.1.6"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.5.tgz#de5bbbe35543fa6d049fe96b8ee329d6cd6892b8" resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.6.tgz#f723bfccb2c7abec886aa6a4930b360d20f1a0b3"
integrity sha512-bWpo3PiqVDYslvrRjTq0Isrm0mFXHiO33D8MS6t6dWcqSFGeYF52nlpCM2xwOJ6tRVRznDkL+zz/iHPL4LDuvQ== integrity sha512-m9DcxlZw/58p4xuhH3dzUzQWaC4dig0RKX7yNQOJt4VRhXn7p+YRrs3o17r3YwzvOLua3zC53VMbfupLsDwO5w==
dependencies: dependencies:
deepmerge "^2.1.1" deepmerge "^2.1.1"
hoist-non-react-statics "^3.3.0" hoist-non-react-statics "^3.3.0"
@ -5493,10 +5493,10 @@ mathml-tag-names@^2.1.3:
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
maxmind@^4.1.4: maxmind@^4.2.0:
version "4.1.4" version "4.2.0"
resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-4.1.4.tgz#14fa0cf9a88f15b708edfd1378c5e49e0f0105c1" resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-4.2.0.tgz#912e5ec4a961807d20d7fb541160aeb5ea802c1c"
integrity sha512-DfcZPpc0XJVF1yypRpVqMs9JiSYYShVkfexSjwTbfqZMCEoQlCU83ooX9cRmWMUaLqm4zffi7HtZg7XqcJaL1A== integrity sha512-TADiE11Q10IjvLtlo05tTD52xLqfCJMhE3eYJHmpYIKg668STi/fQZGH9X3FpqpIP/2WPgKFxf899awFvfMtQA==
dependencies: dependencies:
tiny-lru "7.0.6" tiny-lru "7.0.6"
@ -8347,10 +8347,10 @@ stylelint-config-recommended@^3.0.0:
resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz#e0e547434016c5539fe2650afd58049a2fd1d657" resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz#e0e547434016c5539fe2650afd58049a2fd1d657"
integrity sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ== integrity sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==
stylelint@^13.7.1: stylelint@^13.7.2:
version "13.7.1" version "13.7.2"
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.1.tgz#bee97ee78d778a3f1dbe3f7397b76414973e263e" resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.2.tgz#6f3c58eea4077680ed0ceb0d064b22b100970486"
integrity sha512-qzqazcyRxrSRdmFuO0/SZOJ+LyCxYy0pwcvaOBBnl8/2VfHSMrtNIE+AnyJoyq6uKb+mt+hlgmVrvVi6G6XHfQ== integrity sha512-mmieorkfmO+ZA6CNDu1ic9qpt4tFvH2QUB7vqXgrMVHe5ENU69q7YDq0YUg/UHLuCsZOWhUAvcMcLzLDIERzSg==
dependencies: dependencies:
"@stylelint/postcss-css-in-js" "^0.37.2" "@stylelint/postcss-css-in-js" "^0.37.2"
"@stylelint/postcss-markdown" "^0.36.1" "@stylelint/postcss-markdown" "^0.36.1"