mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-17 23:03:38 +01:00
commit
5d74e86222
@ -24,6 +24,7 @@ function Button({
|
||||
data-tip={tooltip}
|
||||
data-effect="solid"
|
||||
data-for={tooltipId}
|
||||
data-offset={JSON.stringify({ left: 10 })}
|
||||
type={type}
|
||||
className={classNames(styles.button, className, {
|
||||
[styles.large]: size === 'large',
|
||||
|
@ -106,9 +106,14 @@ export default function Calendar({ date, minDate, maxDate, onChange }) {
|
||||
}
|
||||
|
||||
const DaySelector = ({ date, minDate, maxDate, locale, onSelect }) => {
|
||||
const startWeek = startOfWeek(date, { locale: getDateLocale(locale) });
|
||||
const startMonth = startOfMonth(date, { locale: getDateLocale(locale) });
|
||||
const startDay = subDays(startMonth, startMonth.getDay());
|
||||
const dateLocale = getDateLocale(locale);
|
||||
const weekStartsOn = dateLocale?.options?.weekStartsOn || 0;
|
||||
const startWeek = startOfWeek(date, {
|
||||
locale: dateLocale,
|
||||
weekStartsOn,
|
||||
});
|
||||
const startMonth = startOfMonth(date);
|
||||
const startDay = subDays(startMonth, startMonth.getDay() - weekStartsOn);
|
||||
const month = date.getMonth();
|
||||
const year = date.getFullYear();
|
||||
|
||||
|
@ -6,7 +6,7 @@ import Modal from './Modal';
|
||||
import DropDown from './DropDown';
|
||||
import DatePickerForm from 'components/forms/DatePickerForm';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { getDateRange, dateFormat } from 'lib/date';
|
||||
import { dateFormat } from 'lib/date';
|
||||
import Calendar from 'assets/calendar-alt.svg';
|
||||
import Icon from './Icon';
|
||||
|
||||
@ -47,6 +47,11 @@ const filterOptions = [
|
||||
value: '90day',
|
||||
},
|
||||
{ label: <FormattedMessage id="label.this-year" defaultMessage="This year" />, value: '1year' },
|
||||
{
|
||||
label: <FormattedMessage id="label.all-time" defaultMessage="All time" />,
|
||||
value: 'all',
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="label.custom-range" defaultMessage="Custom range" />,
|
||||
value: 'custom',
|
||||
@ -55,7 +60,6 @@ const filterOptions = [
|
||||
];
|
||||
|
||||
function DateFilter({ value, startDate, endDate, onChange, className }) {
|
||||
const { locale } = useLocale();
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
const displayValue =
|
||||
value === 'custom' ? (
|
||||
@ -64,12 +68,12 @@ function DateFilter({ value, startDate, endDate, onChange, className }) {
|
||||
value
|
||||
);
|
||||
|
||||
function handleChange(value) {
|
||||
async function handleChange(value) {
|
||||
if (value === 'custom') {
|
||||
setShowPicker(true);
|
||||
return;
|
||||
}
|
||||
onChange(getDateRange(value, locale));
|
||||
onChange(value);
|
||||
}
|
||||
|
||||
function handlePickerChange(value) {
|
||||
|
@ -7,12 +7,9 @@ import Button from './Button';
|
||||
import Refresh from 'assets/redo.svg';
|
||||
import Dots from 'assets/ellipsis-h.svg';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import { getDateRange } from '../../lib/date';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
|
||||
function RefreshButton({ websiteId }) {
|
||||
const dispatch = useDispatch();
|
||||
const { locale } = useLocale();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const completed = useSelector(state => state.queries[`/api/website/${websiteId}/stats`]);
|
||||
@ -20,7 +17,7 @@ function RefreshButton({ websiteId }) {
|
||||
function handleClick() {
|
||||
if (dateRange) {
|
||||
setLoading(true);
|
||||
dispatch(setDateRange(websiteId, getDateRange(dateRange.value, locale)));
|
||||
dispatch(setDateRange(websiteId, dateRange));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
||||
import classNames from 'classnames';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { colord } from 'colord';
|
||||
import useTheme from 'hooks/useTheme';
|
||||
import { ISO_COUNTRIES, THEME_COLORS, MAP_FILE } from 'lib/constants';
|
||||
import styles from './WorldMap.module.css';
|
||||
@ -35,9 +35,9 @@ function WorldMap({ data, className }) {
|
||||
return colors.fillColor;
|
||||
}
|
||||
|
||||
return tinycolor(colors.baseColor)[theme === 'light' ? 'lighten' : 'darken'](
|
||||
40 * (1.0 - country.z / 100),
|
||||
);
|
||||
return colord(colors.baseColor)
|
||||
[theme === 'light' ? 'lighten' : 'darken'](0.4 * (1.0 - country.z / 100))
|
||||
.toHex();
|
||||
}
|
||||
|
||||
function getOpacity(code) {
|
||||
|
@ -6,10 +6,10 @@ import { formatLongNumber } from 'lib/format';
|
||||
import { dateFormat } from 'lib/date';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useTheme from 'hooks/useTheme';
|
||||
import useForceUpdate from 'hooks/useForceUpdate';
|
||||
import { DEFAUL_CHART_HEIGHT, DEFAULT_ANIMATION_DURATION, THEME_COLORS } from 'lib/constants';
|
||||
import styles from './BarChart.module.css';
|
||||
import ChartTooltip from './ChartTooltip';
|
||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
|
||||
export default function BarChart({
|
||||
chartId,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { colord } from 'colord';
|
||||
import BarChart from './BarChart';
|
||||
import { getDateArray, getDateLength } from 'lib/date';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
@ -51,13 +51,13 @@ export default function EventsChart({ websiteId, className, token }) {
|
||||
});
|
||||
|
||||
return Object.keys(map).map((key, index) => {
|
||||
const color = tinycolor(EVENT_COLORS[index % EVENT_COLORS.length]);
|
||||
const color = colord(EVENT_COLORS[index % EVENT_COLORS.length]);
|
||||
return {
|
||||
label: key,
|
||||
data: map[key],
|
||||
lineTension: 0,
|
||||
backgroundColor: color.setAlpha(0.6).toRgbString(),
|
||||
borderColor: color.setAlpha(0.7).toRgbString(),
|
||||
backgroundColor: color.alpha(0.6).toRgbString(),
|
||||
borderColor: color.alpha(0.7).toRgbString(),
|
||||
borderWidth: 1,
|
||||
};
|
||||
});
|
||||
|
31
components/metrics/LanguagesTable.js
Normal file
31
components/metrics/LanguagesTable.js
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import useLanguageNames from 'hooks/useLanguageNames';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
|
||||
export default function LanguagesTable({ websiteId, onDataLoad, ...props }) {
|
||||
const { locale } = useLocale();
|
||||
const languageNames = useLanguageNames(locale);
|
||||
|
||||
function renderLabel({ x }) {
|
||||
return (
|
||||
<div className={locale}>
|
||||
{languageNames[x] ?? <FormattedMessage id="label.unknown" defaultMessage="Unknown" />}{' '}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MetricsTable
|
||||
{...props}
|
||||
title={<FormattedMessage id="metrics.languages" defaultMessage="Languages" />}
|
||||
type="language"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
onDataLoad={data => onDataLoad?.(percentFilter(data))}
|
||||
renderLabel={renderLabel}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { colord } from 'colord';
|
||||
import classNames from 'classnames';
|
||||
import Dot from 'components/common/Dot';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useForceUpdate from 'hooks/useForceUpdate';
|
||||
import styles from './Legend.module.css';
|
||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
|
||||
export default function Legend({ chart }) {
|
||||
const { locale } = useLocale();
|
||||
@ -25,16 +26,20 @@ export default function Legend({ chart }) {
|
||||
|
||||
return (
|
||||
<div className={styles.legend}>
|
||||
{chart.legend.legendItems.map(({ text, fillStyle, datasetIndex, hidden }) => (
|
||||
<div
|
||||
key={text}
|
||||
className={classNames(styles.label, { [styles.hidden]: hidden })}
|
||||
onClick={() => handleClick(datasetIndex)}
|
||||
>
|
||||
<Dot color={fillStyle} />
|
||||
<span className={locale}>{text}</span>
|
||||
</div>
|
||||
))}
|
||||
{chart.legend.legendItems.map(({ text, fillStyle, datasetIndex, hidden }) => {
|
||||
const color = colord(fillStyle);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={text}
|
||||
className={classNames(styles.label, { [styles.hidden]: hidden })}
|
||||
onClick={() => handleClick(datasetIndex)}
|
||||
>
|
||||
<Dot color={color.alpha(color.alpha() + 0.2).toHex()} />
|
||||
<span className={locale}>{text}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ const MetricCard = ({
|
||||
label,
|
||||
reverseColors = false,
|
||||
format = formatNumber,
|
||||
hideComparison = false,
|
||||
}) => {
|
||||
const props = useSpring({ x: Number(value) || 0, from: { x: 0 } });
|
||||
const changeProps = useSpring({ x: Number(change) || 0, from: { x: 0 } });
|
||||
@ -18,8 +19,8 @@ const MetricCard = ({
|
||||
<animated.div className={styles.value}>{props.x.interpolate(x => format(x))}</animated.div>
|
||||
<div className={styles.label}>
|
||||
{label}
|
||||
{~~change === 0 && <span className={styles.change}>{format(0)}</span>}
|
||||
{~~change !== 0 && (
|
||||
{~~change === 0 && !hideComparison && <span className={styles.change}>{format(0)}</span>}
|
||||
{~~change !== 0 && !hideComparison && (
|
||||
<animated.span
|
||||
className={`${styles.change} ${
|
||||
change >= 0
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { colord } from 'colord';
|
||||
import CheckVisible from 'components/helpers/CheckVisible';
|
||||
import BarChart from './BarChart';
|
||||
import useTheme from 'hooks/useTheme';
|
||||
@ -18,15 +18,15 @@ export default function PageviewsChart({
|
||||
}) {
|
||||
const intl = useIntl();
|
||||
const [theme] = useTheme();
|
||||
const primaryColor = tinycolor(THEME_COLORS[theme].primary);
|
||||
const primaryColor = colord(THEME_COLORS[theme].primary);
|
||||
const colors = {
|
||||
views: {
|
||||
background: primaryColor.setAlpha(0.4).toRgbString(),
|
||||
border: primaryColor.setAlpha(0.5).toRgbString(),
|
||||
background: primaryColor.alpha(0.4).toRgbString(),
|
||||
border: primaryColor.alpha(0.5).toRgbString(),
|
||||
},
|
||||
visitors: {
|
||||
background: primaryColor.setAlpha(0.6).toRgbString(),
|
||||
border: primaryColor.setAlpha(0.7).toRgbString(),
|
||||
background: primaryColor.alpha(0.6).toRgbString(),
|
||||
border: primaryColor.alpha(0.7).toRgbString(),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PageHeader from '../layout/PageHeader';
|
||||
import DropDown from '../common/DropDown';
|
||||
import ActiveUsers from './ActiveUsers';
|
||||
import MetricCard from './MetricCard';
|
||||
import styles from './RealtimeHeader.module.css';
|
||||
|
||||
@ -24,24 +25,31 @@ export default function RealtimeHeader({ websites, data, websiteId, onSelect })
|
||||
<div>
|
||||
<FormattedMessage id="label.realtime" defaultMessage="Realtime" />
|
||||
</div>
|
||||
<div>
|
||||
<ActiveUsers className={styles.active} websiteId={websiteId} />
|
||||
</div>
|
||||
<DropDown value={websiteId} options={options} onChange={onSelect} />
|
||||
</PageHeader>
|
||||
<div className={styles.metrics}>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.views" defaultMessage="Views" />}
|
||||
value={pageviews.length}
|
||||
hideComparison
|
||||
/>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
value={sessions.length}
|
||||
hideComparison
|
||||
/>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.events" defaultMessage="Events" />}
|
||||
value={events.length}
|
||||
hideComparison
|
||||
/>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.countries" defaultMessage="Countries" />}
|
||||
value={countries.length}
|
||||
hideComparison
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@ -7,7 +7,7 @@ import Tag from 'components/common/Tag';
|
||||
import Dot from 'components/common/Dot';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import NoData from 'components/common/NoData';
|
||||
import { devices } from 'components/messages';
|
||||
import { getDeviceMessage } from 'components/messages';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import { BROWSERS } from 'lib/constants';
|
||||
@ -137,7 +137,7 @@ export default function RealtimeLog({ data, websites, websiteId }) {
|
||||
),
|
||||
browser: <b>{BROWSERS[browser]}</b>,
|
||||
os: <b>{os}</b>,
|
||||
device: <b>{intl.formatMessage(devices[device])?.toLowerCase()}</b>,
|
||||
device: <b>{getDeviceMessage(device)}</b>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -35,7 +35,6 @@ export default function ReferrersTable({ websiteId, websiteDomain, showFilters,
|
||||
];
|
||||
|
||||
const renderLink = ({ w: link, x: label }) => {
|
||||
console.log({ link, label });
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
<Link href={resolve({ ref: label })} replace={true}>
|
||||
|
@ -1,19 +1,22 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useRouter } from 'next/router';
|
||||
import PageviewsChart from './PageviewsChart';
|
||||
import MetricsBar from './MetricsBar';
|
||||
import WebsiteHeader from './WebsiteHeader';
|
||||
import DateFilter from 'components/common/DateFilter';
|
||||
import StickyHeader from 'components/helpers/StickyHeader';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import FilterTags from 'components/metrics/FilterTags';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import useTimezone from 'hooks/useTimezone';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { getDateArray, getDateLength } from 'lib/date';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import FilterTags from 'components/metrics/FilterTags';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { getDateArray, getDateLength, getDateRange, getDateRangeValues } from 'lib/date';
|
||||
import useShareToken from 'hooks/useShareToken';
|
||||
import { TOKEN_HEADER } from 'lib/constants';
|
||||
import { get } from 'lib/web';
|
||||
import styles from './WebsiteChart.module.css';
|
||||
|
||||
export default function WebsiteChart({
|
||||
@ -22,13 +25,15 @@ export default function WebsiteChart({
|
||||
domain,
|
||||
stickyHeader = false,
|
||||
showLink = false,
|
||||
hideChart = false,
|
||||
showChart = true,
|
||||
onDataLoad = () => {},
|
||||
}) {
|
||||
const shareToken = useShareToken();
|
||||
const [dateRange, setDateRange] = useDateRange(websiteId);
|
||||
const { startDate, endDate, unit, value, modified } = dateRange;
|
||||
const { locale } = useLocale();
|
||||
const [timezone] = useTimezone();
|
||||
const { basePath } = useRouter();
|
||||
const {
|
||||
router,
|
||||
resolve,
|
||||
@ -66,6 +71,17 @@ export default function WebsiteChart({
|
||||
router.push(resolve({ [param]: undefined }));
|
||||
}
|
||||
|
||||
async function handleDateChange(value) {
|
||||
if (value === 'all') {
|
||||
const { data, ok } = await get(`${basePath}/api/website/${websiteId}`);
|
||||
if (ok) {
|
||||
setDateRange({ value, ...getDateRangeValues(new Date(data.created_at), Date.now()) });
|
||||
}
|
||||
} else {
|
||||
setDateRange(getDateRange(value, locale));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<WebsiteHeader websiteId={websiteId} title={title} domain={domain} showLink={showLink} />
|
||||
@ -84,7 +100,7 @@ export default function WebsiteChart({
|
||||
value={value}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onChange={setDateRange}
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
</div>
|
||||
</StickyHeader>
|
||||
@ -92,7 +108,7 @@ export default function WebsiteChart({
|
||||
<div className="row">
|
||||
<div className={classNames(styles.chart, 'col')}>
|
||||
{error && <ErrorMessage />}
|
||||
{!hideChart && (
|
||||
{showChart && (
|
||||
<PageviewsChart
|
||||
websiteId={websiteId}
|
||||
data={chartData}
|
||||
|
@ -4,19 +4,22 @@ import classNames from 'classnames';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import Page from '../layout/Page';
|
||||
import PageHeader from '../layout/PageHeader';
|
||||
import useFetch from '../../hooks/useFetch';
|
||||
import DropDown from '../common/DropDown';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import DropDown from 'components/common/DropDown';
|
||||
import WebsiteChart from 'components/metrics/WebsiteChart';
|
||||
import EventsChart from 'components/metrics/EventsChart';
|
||||
import Button from 'components/common/Button';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import Icon from 'components/common/Icon';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import ChevronDown from 'assets/chevron-down.svg';
|
||||
import styles from './TestConsole.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 [show, setShow] = useState(true);
|
||||
const { basePath } = useRouter();
|
||||
const { data } = useFetch('/api/websites');
|
||||
|
||||
@ -55,33 +58,46 @@ export default function TestConsole() {
|
||||
{!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>
|
||||
<Icon
|
||||
icon={<ChevronDown />}
|
||||
className={classNames({ [styles.hidden]: !show })}
|
||||
onClick={() => setShow(!show)}
|
||||
/>
|
||||
</div>
|
||||
{show && (
|
||||
<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
|
||||
|
@ -3,3 +3,7 @@
|
||||
border-radius: 5px;
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import BrowsersTable from '../metrics/BrowsersTable';
|
||||
import OSTable from '../metrics/OSTable';
|
||||
import DevicesTable from '../metrics/DevicesTable';
|
||||
import CountriesTable from '../metrics/CountriesTable';
|
||||
import LanguagesTable from '../metrics/LanguagesTable';
|
||||
import EventsTable from '../metrics/EventsTable';
|
||||
import EventsChart from '../metrics/EventsChart';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
@ -30,6 +31,7 @@ const views = {
|
||||
os: OSTable,
|
||||
device: DevicesTable,
|
||||
country: CountriesTable,
|
||||
language: LanguagesTable,
|
||||
event: EventsTable,
|
||||
};
|
||||
|
||||
@ -82,6 +84,10 @@ export default function WebsiteDetails({ websiteId }) {
|
||||
label: <FormattedMessage id="metrics.countries" defaultMessage="Countries" />,
|
||||
value: resolve({ view: 'country' }),
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="metrics.languages" defaultMessage="Languages" />,
|
||||
value: resolve({ view: 'language' }),
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="metrics.events" defaultMessage="Events" />,
|
||||
value: resolve({ view: 'event' }),
|
||||
|
@ -12,7 +12,7 @@ import styles from './WebsiteList.module.css';
|
||||
|
||||
export default function WebsiteList({ userId }) {
|
||||
const { data } = useFetch('/api/websites', { params: { user_id: userId } });
|
||||
const [hideCharts, setHideCharts] = useState(false);
|
||||
const [showCharts, setShowCharts] = useState(true);
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
@ -43,7 +43,7 @@ export default function WebsiteList({ userId }) {
|
||||
<Button
|
||||
tooltip={<FormattedMessage id="message.toggle-charts" defaultMessage="Toggle charts" />}
|
||||
icon={<Chart />}
|
||||
onClick={() => setHideCharts(!hideCharts)}
|
||||
onClick={() => setShowCharts(!showCharts)}
|
||||
/>
|
||||
</div>
|
||||
{data.map(({ website_id, name, domain }) => (
|
||||
@ -52,7 +52,7 @@ export default function WebsiteList({ userId }) {
|
||||
websiteId={website_id}
|
||||
title={name}
|
||||
domain={domain}
|
||||
hideChart={hideCharts}
|
||||
showChart={showCharts}
|
||||
showLink
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useSelector } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'components/common/Link';
|
||||
import Table from 'components/common/Table';
|
||||
@ -25,6 +26,7 @@ import useFetch from 'hooks/useFetch';
|
||||
import styles from './WebsiteSettings.module.css';
|
||||
|
||||
export default function WebsiteSettings() {
|
||||
const user = useSelector(state => state.user);
|
||||
const [editWebsite, setEditWebsite] = useState();
|
||||
const [resetWebsite, setResetWebsite] = useState();
|
||||
const [deleteWebsite, setDeleteWebsite] = useState();
|
||||
@ -33,7 +35,9 @@ export default function WebsiteSettings() {
|
||||
const [showUrl, setShowUrl] = useState();
|
||||
const [saved, setSaved] = useState(0);
|
||||
const [message, setMessage] = useState();
|
||||
const { data } = useFetch(`/api/websites`, {}, [saved]);
|
||||
const { data } = useFetch(`/api/websites` + (user.is_admin ? '?include_all=true' : ''), {}, [
|
||||
saved,
|
||||
]);
|
||||
|
||||
const Buttons = row => (
|
||||
<ButtonLayout align="right">
|
||||
@ -55,15 +59,27 @@ export default function WebsiteSettings() {
|
||||
tooltipId={`button-code-${row.website_id}`}
|
||||
onClick={() => setShowCode(row)}
|
||||
/>
|
||||
<Button icon={<Pen />} size="small" onClick={() => setEditWebsite(row)}>
|
||||
<FormattedMessage id="label.edit" defaultMessage="Edit" />
|
||||
</Button>
|
||||
<Button icon={<Reset />} size="small" onClick={() => setResetWebsite(row)}>
|
||||
<FormattedMessage id="label.reset" defaultMessage="Reset" />
|
||||
</Button>
|
||||
<Button icon={<Trash />} size="small" onClick={() => setDeleteWebsite(row)}>
|
||||
<FormattedMessage id="label.delete" defaultMessage="Delete" />
|
||||
</Button>
|
||||
<Button
|
||||
icon={<Pen />}
|
||||
size="small"
|
||||
tooltip={<FormattedMessage id="label.edit" defaultMessage="Edit" />}
|
||||
tooltipId={`button-edit-${row.website_id}`}
|
||||
onClick={() => setEditWebsite(row)}
|
||||
/>
|
||||
<Button
|
||||
icon={<Reset />}
|
||||
size="small"
|
||||
tooltip={<FormattedMessage id="label.reset" defaultMessage="Reset" />}
|
||||
tooltipId={`button-reset-${row.website_id}`}
|
||||
onClick={() => setResetWebsite(row)}
|
||||
/>
|
||||
<Button
|
||||
icon={<Trash />}
|
||||
size="small"
|
||||
tooltip={<FormattedMessage id="label.delete" defaultMessage="Delete" />}
|
||||
tooltipId={`button-delete-${row.website_id}`}
|
||||
onClick={() => setDeleteWebsite(row)}
|
||||
/>
|
||||
</ButtonLayout>
|
||||
);
|
||||
|
||||
@ -74,6 +90,30 @@ export default function WebsiteSettings() {
|
||||
</Link>
|
||||
);
|
||||
|
||||
const adminColumns = [
|
||||
{
|
||||
key: 'name',
|
||||
label: <FormattedMessage id="label.name" defaultMessage="Name" />,
|
||||
className: 'col-4 col-xl-3',
|
||||
render: DetailsLink,
|
||||
},
|
||||
{
|
||||
key: 'domain',
|
||||
label: <FormattedMessage id="label.domain" defaultMessage="Domain" />,
|
||||
className: 'col-4 col-xl-3',
|
||||
},
|
||||
{
|
||||
key: 'account',
|
||||
label: <FormattedMessage id="label.owner" defaultMessage="Owner" />,
|
||||
className: 'col-4 col-xl-1',
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
className: classNames(styles.buttons, 'col-12 col-xl-5 pt-2 pt-xl-0'),
|
||||
render: Buttons,
|
||||
},
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: 'name',
|
||||
@ -137,7 +177,7 @@ export default function WebsiteSettings() {
|
||||
<FormattedMessage id="label.add-website" defaultMessage="Add website" />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Table columns={columns} rows={data} empty={empty} />
|
||||
<Table columns={user.is_admin ? adminColumns : columns} rows={data} empty={empty} />
|
||||
{editWebsite && (
|
||||
<Modal title={<FormattedMessage id="label.edit-website" defaultMessage="Edit website" />}>
|
||||
<WebsiteEditForm values={editWebsite} onSave={handleSave} onClose={handleClose} />
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useRouter } from 'next/router';
|
||||
import { get } from 'lib/web';
|
||||
import { updateQuery } from 'redux/actions/queries';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
export default function useFetch(url, options = {}, update = []) {
|
||||
const dispatch = useDispatch();
|
||||
|
34
hooks/useLanguageNames.js
Normal file
34
hooks/useLanguageNames.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { get } from 'lib/web';
|
||||
import enUS from 'public/language/en-US.json';
|
||||
|
||||
const languageNames = {
|
||||
'en-US': enUS,
|
||||
};
|
||||
|
||||
export default function useLanguageNames(locale) {
|
||||
const [list, setList] = useState(languageNames[locale] || enUS);
|
||||
const { basePath } = useRouter();
|
||||
|
||||
async function loadData(locale) {
|
||||
const { ok, data } = await get(`${basePath}/language/${locale}.json`);
|
||||
|
||||
if (ok) {
|
||||
languageNames[locale] = data;
|
||||
setList(languageNames[locale]);
|
||||
} else {
|
||||
setList(enUS);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!languageNames[locale]) {
|
||||
loadData(locale);
|
||||
} else {
|
||||
setList(languageNames[locale]);
|
||||
}
|
||||
}, [locale]);
|
||||
|
||||
return list;
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "مدير عام؟",
|
||||
"label.all": "الكل",
|
||||
"label.all-events": "كافة الأحداث",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "كافة المواقع",
|
||||
"label.back": "للخلف",
|
||||
"label.cancel": "إلغاء",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "المزيد",
|
||||
"label.name": "الإسم",
|
||||
"label.new-password": "كلمة مرور جديدة",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "كلمة المرور",
|
||||
"label.passwords-dont-match": "كلمة المرور غير متطابقة",
|
||||
"label.profile": "الملف الشخصي",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "مجمعة",
|
||||
"metrics.filter.domain-only": "نطاق فقط",
|
||||
"metrics.filter.raw": "مفصلة",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "نظام التشغيل",
|
||||
"metrics.page-views": "مشاهدات الصفحة",
|
||||
"metrics.pages": "الصفحات",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrador",
|
||||
"label.all": "Tots",
|
||||
"label.all-events": "Tots els esdeveniments",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Tots els llocs web",
|
||||
"label.back": "Enrere",
|
||||
"label.cancel": "Cancel·la",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Més",
|
||||
"label.name": "Nom",
|
||||
"label.new-password": "Contrasenya nova",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Contrasenya",
|
||||
"label.passwords-dont-match": "Les contrasenyes no coincideixen",
|
||||
"label.profile": "Perfil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Combinat",
|
||||
"metrics.filter.domain-only": "Només domini",
|
||||
"metrics.filter.raw": "En cru",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Sistemes operatius",
|
||||
"metrics.page-views": "Pàgines vistes",
|
||||
"metrics.pages": "Pàgines",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrátor",
|
||||
"label.all": "Vše",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Všechny weby",
|
||||
"label.back": "Zpět",
|
||||
"label.cancel": "Zrušit",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Více",
|
||||
"label.name": "Jméno",
|
||||
"label.new-password": "Nové heslo",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Heslo",
|
||||
"label.passwords-dont-match": "Hesla se neschodují",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Kombinace",
|
||||
"metrics.filter.domain-only": "Domény",
|
||||
"metrics.filter.raw": "Nezpracované",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Operační systém",
|
||||
"metrics.page-views": "Zobrazení stránek",
|
||||
"metrics.pages": "Stránky",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrator",
|
||||
"label.all": "Alle",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Alle websites",
|
||||
"label.back": "Tilbage",
|
||||
"label.cancel": "Afvis",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Mere",
|
||||
"label.name": "Navn",
|
||||
"label.new-password": "Ny adgangskode",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Adgangskode",
|
||||
"label.passwords-dont-match": "Adgangskoder matcher ikke",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Kombineret",
|
||||
"metrics.filter.domain-only": "Kun domæne",
|
||||
"metrics.filter.raw": "Rå",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Operativsystemer",
|
||||
"metrics.page-views": "Sidevisninger",
|
||||
"metrics.pages": "Sider",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrator",
|
||||
"label.all": "Alle",
|
||||
"label.all-events": "Alle Ereignisse",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Alle Webseiten",
|
||||
"label.back": "Zurück",
|
||||
"label.cancel": "Abbrechen",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Mehr",
|
||||
"label.name": "Name",
|
||||
"label.new-password": "Neues Passwort",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Passwort",
|
||||
"label.passwords-dont-match": "Passwörter stimmen nicht überein",
|
||||
"label.profile": "Profil",
|
||||
@ -43,7 +45,7 @@
|
||||
"label.refresh": "Aktualisieren",
|
||||
"label.required": "Erforderlich",
|
||||
"label.reset": "Zurücksetzen",
|
||||
"label.reset-website": "Reset statistics",
|
||||
"label.reset-website": "Statistik zurücksetzen",
|
||||
"label.save": "Speichern",
|
||||
"label.settings": "Einstellungen",
|
||||
"label.share-url": "Freigabe-URL",
|
||||
@ -74,13 +76,13 @@
|
||||
"message.no-websites-configured": "Es ist keine Webseite vorhanden.",
|
||||
"message.page-not-found": "Seite nicht gefunden.",
|
||||
"message.powered-by": "Betrieben durch {name}",
|
||||
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
|
||||
"message.reset-warning": "Alle Daten für diese Website werden gelöscht, jedoch bleibt der tracking code bestehen.",
|
||||
"message.save-success": "Erfolgreich gespeichert.",
|
||||
"message.share-url": "Dies ist die öffentliche URL zum Teilen für {target}.",
|
||||
"message.toggle-charts": "Toggle charts",
|
||||
"message.toggle-charts": "Schaubilder umschalten",
|
||||
"message.track-stats": "Um die Statistiken für {target} zu übermitteln, platzieren Sie bitte den folgenden Quelltext im {head} ihrer Webseite.",
|
||||
"message.type-delete": "Geben Sie {delete} in das Feld unten ein um zu bestätigen.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Geben Sie {reset} in das Feld unten ein um zu bestätigen.",
|
||||
"metrics.actions": "Aktionen",
|
||||
"metrics.average-visit-time": "Durchschn. Besuchszeit",
|
||||
"metrics.bounce-rate": "Absprungrate",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Kombiniert",
|
||||
"metrics.filter.domain-only": "Nur diese Domain",
|
||||
"metrics.filter.raw": "Rohdaten",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Betriebssysteme",
|
||||
"metrics.page-views": "Seitenaufrufe",
|
||||
"metrics.pages": "Seiten",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Διαχειριστής",
|
||||
"label.all": "All",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "All websites",
|
||||
"label.back": "Πίσω",
|
||||
"label.cancel": "Ακύρωση",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Περισσότερα",
|
||||
"label.name": "Όνομα",
|
||||
"label.new-password": "Νέος κωδικός",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Κωδικός",
|
||||
"label.passwords-dont-match": "Οι κωδικοί πρόσβασης δεν ταιριάζουν",
|
||||
"label.profile": "Προφίλ",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Σε συνδυασμό",
|
||||
"metrics.filter.domain-only": "Μόνο τομέας",
|
||||
"metrics.filter.raw": "Ακατέργαστο",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Λειτουργικά συστήματα",
|
||||
"metrics.page-views": "Προβολές σελίδας",
|
||||
"metrics.pages": "Σελίδες",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrator",
|
||||
"label.all": "All",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "All websites",
|
||||
"label.back": "Back",
|
||||
"label.cancel": "Cancel",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "More",
|
||||
"label.name": "Name",
|
||||
"label.new-password": "New password",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Password",
|
||||
"label.passwords-dont-match": "Passwords don't match",
|
||||
"label.profile": "Profile",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Combined",
|
||||
"metrics.filter.domain-only": "Domain only",
|
||||
"metrics.filter.raw": "Raw",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Operating systems",
|
||||
"metrics.page-views": "Page views",
|
||||
"metrics.pages": "Pages",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrator",
|
||||
"label.all": "All",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "All websites",
|
||||
"label.back": "Back",
|
||||
"label.cancel": "Cancel",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "More",
|
||||
"label.name": "Name",
|
||||
"label.new-password": "New password",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Password",
|
||||
"label.passwords-dont-match": "Passwords don't match",
|
||||
"label.profile": "Profile",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Combined",
|
||||
"metrics.filter.domain-only": "Domain only",
|
||||
"metrics.filter.raw": "Raw",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Operating systems",
|
||||
"metrics.page-views": "Page views",
|
||||
"metrics.pages": "Pages",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrador",
|
||||
"label.all": "Todos",
|
||||
"label.all-events": "Todos los eventos",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Todos los sitios",
|
||||
"label.back": "Atrás",
|
||||
"label.cancel": "Cancelar",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Más",
|
||||
"label.name": "Nombre",
|
||||
"label.new-password": "Nueva contraseña",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Contraseña",
|
||||
"label.passwords-dont-match": "Las contraseñas no coinciden",
|
||||
"label.profile": "Perfil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Combinado",
|
||||
"metrics.filter.domain-only": "Únicamente dominio",
|
||||
"metrics.filter.raw": "Personalizado",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Sistemas operativos",
|
||||
"metrics.page-views": "Vistas",
|
||||
"metrics.pages": "Páginas",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "مدیر",
|
||||
"label.all": "همه",
|
||||
"label.all-events": "همهی رویدادها",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "همهی وبسایتها",
|
||||
"label.back": "برگشت",
|
||||
"label.cancel": "انصراف",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "بیشتر",
|
||||
"label.name": "نام",
|
||||
"label.new-password": "رمز جدید",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "رمز",
|
||||
"label.passwords-dont-match": "رمزها یکسان نیستند",
|
||||
"label.profile": "پروفایل",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "ترکیب شده",
|
||||
"metrics.filter.domain-only": "فقط دامنه",
|
||||
"metrics.filter.raw": "خام",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "سیستمعاملها",
|
||||
"metrics.page-views": "بازدید صفحه",
|
||||
"metrics.pages": "صفحهها",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Järjestelmänvalvoja",
|
||||
"label.all": "Kaikki",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Kaikki verkkosivut",
|
||||
"label.back": "Takaisin",
|
||||
"label.cancel": "Peruuta",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Lisää",
|
||||
"label.name": "Nimi",
|
||||
"label.new-password": "Uusi salasana",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Salasana",
|
||||
"label.passwords-dont-match": "Salasanat eivät täsmää",
|
||||
"label.profile": "Profiili",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Yhdistetty",
|
||||
"metrics.filter.domain-only": "Vain verkkotunnus",
|
||||
"metrics.filter.raw": "Käsittelemätön",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Käyttöjärjestelmät",
|
||||
"metrics.page-views": "Sivun näyttökertoja",
|
||||
"metrics.pages": "Sivut",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Fyrisitari",
|
||||
"label.all": "Alt",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Allar heimasíður",
|
||||
"label.back": "Aftur",
|
||||
"label.cancel": "Strika",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Meira",
|
||||
"label.name": "Navn",
|
||||
"label.new-password": "Nýtt loyniorð",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Loyniorð",
|
||||
"label.passwords-dont-match": "Loyniorðini eru ikki eins",
|
||||
"label.profile": "Vangi",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Samansett",
|
||||
"metrics.filter.domain-only": "Bara økisnavn",
|
||||
"metrics.filter.raw": "Óviðgjørt",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Stýrikervir",
|
||||
"metrics.page-views": "Opnaðar síðir",
|
||||
"metrics.pages": "Síðir",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrateur",
|
||||
"label.all": "Tout",
|
||||
"label.all-events": "Tous les événements",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Tous les sites web",
|
||||
"label.back": "Retour",
|
||||
"label.cancel": "Annuler",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Plus",
|
||||
"label.name": "Nom",
|
||||
"label.new-password": "Nouveau mot de passe",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Mot de passe",
|
||||
"label.passwords-dont-match": "Les mots de passe ne correspondent pas",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Combiné",
|
||||
"metrics.filter.domain-only": "Domaine uniquement",
|
||||
"metrics.filter.raw": "Brute",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Systèmes d'exploitation",
|
||||
"metrics.page-views": "Pages vues",
|
||||
"metrics.pages": "Pages",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "מנהל",
|
||||
"label.all": "הכל",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "כל האתרים",
|
||||
"label.back": "חזרה",
|
||||
"label.cancel": "ביטול",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "עוד",
|
||||
"label.name": "שם",
|
||||
"label.new-password": "סיסמה חדשה",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "סיסמה",
|
||||
"label.passwords-dont-match": "סיסמאות לא תואמות",
|
||||
"label.profile": "פרופיל",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "משותף",
|
||||
"metrics.filter.domain-only": "דומיין בלבד",
|
||||
"metrics.filter.raw": "גולמי",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "מערכות הפעלה",
|
||||
"metrics.page-views": "צפיות בדפים",
|
||||
"metrics.pages": "דפים",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "प्रशासक",
|
||||
"label.all": "सब",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "सभी वेबसाइटें",
|
||||
"label.back": "पीछे",
|
||||
"label.cancel": "रद्द करें",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "और",
|
||||
"label.name": "नाम",
|
||||
"label.new-password": "नया पासवर्ड",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "पासवर्ड",
|
||||
"label.passwords-dont-match": "पासवर्ड मेल नहीं खाते",
|
||||
"label.profile": "प्रोफ़ाइल",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "संयुक्त",
|
||||
"metrics.filter.domain-only": "केवल डोमेन",
|
||||
"metrics.filter.raw": "रॉ",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "ऑपरेटिंग सिस्टम",
|
||||
"metrics.page-views": "पृष्ठ दृश्य",
|
||||
"metrics.pages": "पृष्ठों",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Adminisztrátor",
|
||||
"label.all": "Összes",
|
||||
"label.all-events": "Összes esemény",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Összes weboldal",
|
||||
"label.back": "Vissza",
|
||||
"label.cancel": "Mégsem",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Bővebben",
|
||||
"label.name": "Név",
|
||||
"label.new-password": "Új jelszó",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Jelszó",
|
||||
"label.passwords-dont-match": "A jelszavak nem egyeznek",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Összevont",
|
||||
"metrics.filter.domain-only": "Csak domain",
|
||||
"metrics.filter.raw": "Nyers",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Operációs rendszerek",
|
||||
"metrics.page-views": "Oldalmegtekintések",
|
||||
"metrics.pages": "Oldalak",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Pengelola",
|
||||
"label.all": "Semua",
|
||||
"label.all-events": "Semua peristiwa",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Semua website",
|
||||
"label.back": "Kembali",
|
||||
"label.cancel": "Batal",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Lebih banyak",
|
||||
"label.name": "Nama",
|
||||
"label.new-password": "Kata sandi baru",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Kata sandi",
|
||||
"label.passwords-dont-match": "Kata sandi tidak cocok",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Gabungan",
|
||||
"metrics.filter.domain-only": "Hanya domain",
|
||||
"metrics.filter.raw": "Mentah",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Sistem Operasi",
|
||||
"metrics.page-views": "Tampilan halaman",
|
||||
"metrics.pages": "Halaman",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Amministratore",
|
||||
"label.all": "Tutto",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Tutti i siti web",
|
||||
"label.back": "Indietro",
|
||||
"label.cancel": "Annulla",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Dettagli",
|
||||
"label.name": "Nome",
|
||||
"label.new-password": "Nuova password",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Password",
|
||||
"label.passwords-dont-match": "Le password non corrispondono",
|
||||
"label.profile": "Profilo",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Aggregati",
|
||||
"metrics.filter.domain-only": "Solo dominio",
|
||||
"metrics.filter.raw": "Raw",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Sistemi operativi",
|
||||
"metrics.page-views": "Visualizzazioni di pagina",
|
||||
"metrics.pages": "Pagine",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "管理者",
|
||||
"label.all": "すべて表示",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "すべてのWebサイト",
|
||||
"label.back": "戻る",
|
||||
"label.cancel": "キャンセル",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "さらに表示",
|
||||
"label.name": "名前",
|
||||
"label.new-password": "新しいパスワード",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "パスワード",
|
||||
"label.passwords-dont-match": "パスワードが一致しません",
|
||||
"label.profile": "プロファイル",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "パスまで",
|
||||
"metrics.filter.domain-only": "ドメインのみ",
|
||||
"metrics.filter.raw": "すべて表示",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "OS",
|
||||
"metrics.page-views": "閲覧数",
|
||||
"metrics.pages": "ページ",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "관리자",
|
||||
"label.all": "전체",
|
||||
"label.all-events": "모든 이벤트",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "모든 웹사이트",
|
||||
"label.back": "뒤로",
|
||||
"label.cancel": "취소",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "더 보기",
|
||||
"label.name": "이름",
|
||||
"label.new-password": "새 비밀번호",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "비밀번호",
|
||||
"label.passwords-dont-match": "비밀번호가 일치하지 않음",
|
||||
"label.profile": "프로필",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "합쳐서 보기",
|
||||
"metrics.filter.domain-only": "도메인만",
|
||||
"metrics.filter.raw": "전체 보기",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "운영체제",
|
||||
"metrics.page-views": "페이지 뷰(PV)",
|
||||
"metrics.pages": "페이지",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Админ",
|
||||
"label.all": "Бүх",
|
||||
"label.all-events": "Бүх үйл явдал",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Бүх вебүүд",
|
||||
"label.back": "Буцах",
|
||||
"label.cancel": "Цуцлах",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Цааш",
|
||||
"label.name": "Нэр",
|
||||
"label.new-password": "Шинэ нууц үг",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Нууц үг",
|
||||
"label.passwords-dont-match": "Нууц үг тохирохгүй байна",
|
||||
"label.profile": "Бүртгэл",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Нэгтгэсэн",
|
||||
"metrics.filter.domain-only": "Зөвхөн домэйн",
|
||||
"metrics.filter.raw": "Түүхий",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Үйлдлийн систем",
|
||||
"metrics.page-views": "Хуудас үзсэн",
|
||||
"metrics.pages": "Хуудас",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Pentadbir",
|
||||
"label.all": "Semua",
|
||||
"label.all-events": "Semua peristiwa",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Semua laman web",
|
||||
"label.back": "Kembali",
|
||||
"label.cancel": "Batal",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Lebih banyak lagi",
|
||||
"label.name": "Nama",
|
||||
"label.new-password": "Kata laluan baru",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Kata laluan",
|
||||
"label.passwords-dont-match": "Kata laluan tidak sepadan",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Digabungkan",
|
||||
"metrics.filter.domain-only": "Domain sahaja",
|
||||
"metrics.filter.raw": "Mentah",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Sistem operasi",
|
||||
"metrics.page-views": "Paparan halaman",
|
||||
"metrics.pages": "Halaman",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrator",
|
||||
"label.all": "Alle",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Alle nettsteder",
|
||||
"label.back": "Tilbake",
|
||||
"label.cancel": "Avvis",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Mer",
|
||||
"label.name": "Navn",
|
||||
"label.new-password": "Nytt passord",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Passord",
|
||||
"label.passwords-dont-match": "Passordene er ikke like",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Kombinert",
|
||||
"metrics.filter.domain-only": "Bare domene",
|
||||
"metrics.filter.raw": "Rå",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Operativsystemer",
|
||||
"metrics.page-views": "Sidevisninger",
|
||||
"metrics.pages": "Sider",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrator",
|
||||
"label.all": "Alles",
|
||||
"label.all-events": "Alle gebeurtenissen",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Alle websites",
|
||||
"label.back": "Terug",
|
||||
"label.cancel": "Annuleren",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Toon meer",
|
||||
"label.name": "Naam",
|
||||
"label.new-password": "Nieuw wachtwoord",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Wachtwoord",
|
||||
"label.passwords-dont-match": "Wachtwoorden komen niet overeen",
|
||||
"label.profile": "Profiel",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Gecombineerd",
|
||||
"metrics.filter.domain-only": "Alleen domein",
|
||||
"metrics.filter.raw": "Ruw",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Besturingssysteem",
|
||||
"metrics.page-views": "Paginaweergaven",
|
||||
"metrics.pages": "Pagina's",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrator",
|
||||
"label.all": "Wszystkie",
|
||||
"label.all-events": "Wszystkie wydarzenia",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Wszystkie witryny",
|
||||
"label.back": "Powrót",
|
||||
"label.cancel": "Anuluj",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Więcej",
|
||||
"label.name": "Nazwa",
|
||||
"label.new-password": "Nowe hasło",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Hasło",
|
||||
"label.passwords-dont-match": "Hasła się nie zgadzają",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Połączone",
|
||||
"metrics.filter.domain-only": "Tylko domena",
|
||||
"metrics.filter.raw": "Surowe dane",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "System operacyjny",
|
||||
"metrics.page-views": "Wyświetlenia strony",
|
||||
"metrics.pages": "Strony",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrador",
|
||||
"label.all": "Todos",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Todos os sites",
|
||||
"label.back": "Voltar",
|
||||
"label.cancel": "Cancelar",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Mais",
|
||||
"label.name": "Nome",
|
||||
"label.new-password": "Nova senha",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Senha",
|
||||
"label.passwords-dont-match": "As senhas não correspondem",
|
||||
"label.profile": "Perfil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Combinado",
|
||||
"metrics.filter.domain-only": "Apenas domínio",
|
||||
"metrics.filter.raw": "Dados brutos",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Sistemas operacionais",
|
||||
"metrics.page-views": "Visualizações de página",
|
||||
"metrics.pages": "Páginas",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrador",
|
||||
"label.all": "Todos",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Todos os websites",
|
||||
"label.back": "Voltar",
|
||||
"label.cancel": "Cancelar",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Mais",
|
||||
"label.name": "Nome",
|
||||
"label.new-password": "Nova palavra-passe",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Palavra-passe",
|
||||
"label.passwords-dont-match": "Palavra-passes não correspondem",
|
||||
"label.profile": "Perfil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Combinado",
|
||||
"metrics.filter.domain-only": "Apenas domínio",
|
||||
"metrics.filter.raw": "Dados brutos",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Sistemas operativos",
|
||||
"metrics.page-views": "Visualizações da página",
|
||||
"metrics.pages": "Páginas",
|
||||
|
@ -3,9 +3,10 @@
|
||||
"label.add-account": "Adăugare cont",
|
||||
"label.add-website": "Adăugare site web",
|
||||
"label.administrator": "Administrator",
|
||||
"label.all": "All",
|
||||
"label.all-events": "All events",
|
||||
"label.all-websites": "All websites",
|
||||
"label.all": "Toate",
|
||||
"label.all-events": "Toate evenimentele",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Toate site-urile web",
|
||||
"label.back": "Înapoi",
|
||||
"label.cancel": "Anulează",
|
||||
"label.change-password": "Schimbare parolă",
|
||||
@ -24,17 +25,18 @@
|
||||
"label.edit": "Editare",
|
||||
"label.edit-account": "Editare cont",
|
||||
"label.edit-website": "Editare site web",
|
||||
"label.enable-share-url": "Activare adresa URL de distribuire",
|
||||
"label.enable-share-url": "Activare adresă URL de distribuire",
|
||||
"label.invalid": "Invalid",
|
||||
"label.invalid-domain": "Invalid domain",
|
||||
"label.invalid-domain": "Domeniu nu este valid",
|
||||
"label.last-days": "Ultimele {x} zile",
|
||||
"label.last-hours": "Ultimele {x} ore",
|
||||
"label.logged-in-as": "Autentificat ca {username}",
|
||||
"label.login": "Autentificare",
|
||||
"label.logout": "Dezautentificare",
|
||||
"label.logout": "Iesire din cont",
|
||||
"label.more": "Mai mult",
|
||||
"label.name": "Nume",
|
||||
"label.new-password": "Parola nouă",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Parolă",
|
||||
"label.passwords-dont-match": "Parolele nu se potrivesc",
|
||||
"label.profile": "Profil",
|
||||
@ -43,7 +45,7 @@
|
||||
"label.refresh": "Reîmprospătare",
|
||||
"label.required": "Obligatoriu",
|
||||
"label.reset": "Resetează",
|
||||
"label.reset-website": "Reset statistics",
|
||||
"label.reset-website": "Resetează statisticile pentru site",
|
||||
"label.save": "Salvează",
|
||||
"label.settings": "Setări",
|
||||
"label.share-url": "Partajare URL",
|
||||
@ -55,32 +57,32 @@
|
||||
"label.today": "Astăzi",
|
||||
"label.tracking-code": "Cod de urmărire",
|
||||
"label.unknown": "Necunoscut",
|
||||
"label.username": "Username",
|
||||
"label.username": "Nume utilizator",
|
||||
"label.view-details": "Vizualizare detalii",
|
||||
"label.websites": "Site-uri web",
|
||||
"message.active-users": "{x} {x, plural, one {vizitator activ} other {vizitatori activi}}",
|
||||
"message.confirm-delete": "Sunteți sigur că doriți să ștergeți {target}?",
|
||||
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
|
||||
"message.confirm-reset": "Sunteți sigur că doriți să resetați statisticile pentru {target}?",
|
||||
"message.copied": "Copiat!",
|
||||
"message.delete-warning": "Toate datele asociate vor fi șterse, de asemenea.",
|
||||
"message.failure": "Ceva n-a mers bine.",
|
||||
"message.get-share-url": "Obține adresa URL de partajare",
|
||||
"message.get-tracking-code": "Obține codul de urmărire",
|
||||
"message.go-to-settings": "Mergi la Setări",
|
||||
"message.incorrect-username-password": "Username/parolă incorecte.",
|
||||
"message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
|
||||
"message.incorrect-username-password": "Nume utilizator / parolă incorecte.",
|
||||
"message.log.visitor": "Vizitator din {country} folosind {browser} pe {os} {device}",
|
||||
"message.new-version-available": "Este disponibilă o nouă versiune {version} de umami!",
|
||||
"message.no-data-available": "Nicio informație disponibilă.",
|
||||
"message.no-data-available": "Nici o informație disponibilă.",
|
||||
"message.no-websites-configured": "Nu aveți niciun site web configurat.",
|
||||
"message.page-not-found": "Pagina nu a fost găsită.",
|
||||
"message.powered-by": "Cu sprijinul {name}",
|
||||
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
|
||||
"message.reset-warning": "Toate statisticile pentru acest site web vor fi șterse, dar codul de urmărire va rămâne intact.",
|
||||
"message.save-success": "Salvat cu succes.",
|
||||
"message.share-url": "Aceasta este adresa URL de partajare pentru {target}.",
|
||||
"message.toggle-charts": "Toggle charts",
|
||||
"message.toggle-charts": "Schimbă graficele",
|
||||
"message.track-stats": "Pentru a urmări statisticile pentru {target}, plasați următorul cod în secțiunea {head} a site-ului dvs. web.",
|
||||
"message.type-delete": "Tastați {delete} în casuța de mai jos pentru a confirma.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Introduceți {reset} în căsuța de mai jos pentru a confirma.",
|
||||
"metrics.actions": "Acțiuni",
|
||||
"metrics.average-visit-time": "Timp mediu de vizitare",
|
||||
"metrics.bounce-rate": "Rata de respingere",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Combinat",
|
||||
"metrics.filter.domain-only": "Numai domeniu",
|
||||
"metrics.filter.raw": "Brut",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Sisteme de operare",
|
||||
"metrics.page-views": "Vizualizări de pagină",
|
||||
"metrics.pages": "Pagini",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Администратор",
|
||||
"label.all": "Все",
|
||||
"label.all-events": "Все события",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Все сайты",
|
||||
"label.back": "Назад",
|
||||
"label.cancel": "Отменить",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Больше",
|
||||
"label.name": "Имя",
|
||||
"label.new-password": "Новый пароль",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Пароль",
|
||||
"label.passwords-dont-match": "Пароли не совпадают",
|
||||
"label.profile": "Профиль",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Объединенные",
|
||||
"metrics.filter.domain-only": "Только домен",
|
||||
"metrics.filter.raw": "Сырые данные",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Операционные системы",
|
||||
"metrics.page-views": "Просмотры страниц",
|
||||
"metrics.pages": "Страницы",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrátor",
|
||||
"label.all": "Všetko",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Všetky weby",
|
||||
"label.back": "Späť",
|
||||
"label.cancel": "Zrušiť",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Viac",
|
||||
"label.name": "Meno",
|
||||
"label.new-password": "Nové heslo",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Heslo",
|
||||
"label.passwords-dont-match": "Hesla se nezhodujú",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Kombinácie",
|
||||
"metrics.filter.domain-only": "Domény",
|
||||
"metrics.filter.raw": "Nezpracované",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Operačný systém",
|
||||
"metrics.page-views": "Zobrazenie stánok",
|
||||
"metrics.pages": "Stránky",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administrator",
|
||||
"label.all": "Vse",
|
||||
"label.all-events": "Vsi dogodki",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Vsa spletna mesta",
|
||||
"label.back": "Nazaj",
|
||||
"label.cancel": "Prekliči",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Več",
|
||||
"label.name": "Ime",
|
||||
"label.new-password": "Novo geslo",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Geslo",
|
||||
"label.passwords-dont-match": "Gesli se ne ujemata",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Skupno",
|
||||
"metrics.filter.domain-only": "Samo domena",
|
||||
"metrics.filter.raw": "Neobdelane meritve",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Operacijski sistemi",
|
||||
"metrics.page-views": "Ogledi strani",
|
||||
"metrics.pages": "Strani",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Administratör",
|
||||
"label.all": "Alla",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Alla sajter",
|
||||
"label.back": "Tillbaka",
|
||||
"label.cancel": "Avbryt",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Mer",
|
||||
"label.name": "Namn",
|
||||
"label.new-password": "Nytt lösenord",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Lösenord",
|
||||
"label.passwords-dont-match": "Lösenorden är inte samma",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Kombinerade",
|
||||
"metrics.filter.domain-only": "Endast domän",
|
||||
"metrics.filter.raw": "Rådata",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Operativsystem",
|
||||
"metrics.page-views": "Sidvisningar",
|
||||
"metrics.pages": "Sidor",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "நிர்வாகியைச் சேர்க்க",
|
||||
"label.all": "எல்லாம்",
|
||||
"label.all-events": "அனைத்து நிகழ்வுகளும்",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "அனைத்து வலைத்தளங்களும்",
|
||||
"label.back": "பின்னால்",
|
||||
"label.cancel": "ரத்துசெய்",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "மேலும்",
|
||||
"label.name": "பெயர்",
|
||||
"label.new-password": "புதிய கடவுச்சொல்",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "கடவுச்சொல்",
|
||||
"label.passwords-dont-match": "இருக்கடவுச்சொல் பொருந்தவில்லை",
|
||||
"label.profile": "சுயவிவரம்",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "ஒருங்கிணைந்த",
|
||||
"metrics.filter.domain-only": "கள முகவரி மட்டும்",
|
||||
"metrics.filter.raw": "மூல",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "இயக்க முறைமைகள்",
|
||||
"metrics.page-views": "பக்க காட்சிகள்",
|
||||
"metrics.pages": "பக்கங்கள்",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Yönetici",
|
||||
"label.all": "Tümü",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Tüm web siteleri",
|
||||
"label.back": "Geri",
|
||||
"label.cancel": "İptal",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Detaylı göster",
|
||||
"label.name": "İsim",
|
||||
"label.new-password": "Yeni parola",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Parola",
|
||||
"label.passwords-dont-match": "Parolalar uyuşmuyor",
|
||||
"label.profile": "Profil",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Birleşik",
|
||||
"metrics.filter.domain-only": "Yalnızca alan adı",
|
||||
"metrics.filter.raw": "Ham",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "İşletim sistemi",
|
||||
"metrics.page-views": "Sayfa görünümü",
|
||||
"metrics.pages": "Sayfalar",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Адміністратор",
|
||||
"label.all": "Всі",
|
||||
"label.all-events": "Всі події",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Всі сайти",
|
||||
"label.back": "Назад",
|
||||
"label.cancel": "Відмінити",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Більше",
|
||||
"label.name": "Ім'я",
|
||||
"label.new-password": "Новий пароль",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Пароль",
|
||||
"label.passwords-dont-match": "Паролі не співпадають",
|
||||
"label.profile": "Профіль",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Об'єднані",
|
||||
"metrics.filter.domain-only": "Лише домен",
|
||||
"metrics.filter.raw": "Сирі дані",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Операційні системи",
|
||||
"metrics.page-views": "Перегляди сторінок",
|
||||
"metrics.pages": "Сторінки",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "Quản Trị",
|
||||
"label.all": "Tất cả",
|
||||
"label.all-events": "Tất cả events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "Tất cả websites",
|
||||
"label.back": "Quay về",
|
||||
"label.cancel": "Huỷ bỏ",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "Thêm",
|
||||
"label.name": "Tên",
|
||||
"label.new-password": "Mật khẩu mới",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "Mật khẩu",
|
||||
"label.passwords-dont-match": "Mật khẩu không đồng nhất",
|
||||
"label.profile": "Hồ sơ",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "Kết hợp",
|
||||
"metrics.filter.domain-only": "Chỉ tên miền",
|
||||
"metrics.filter.raw": "Gốc",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "Hệ điều hành",
|
||||
"metrics.page-views": "Lượt xem",
|
||||
"metrics.pages": "Trang",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "管理员",
|
||||
"label.all": "所有",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "全部网站",
|
||||
"label.back": "返回",
|
||||
"label.cancel": "取消",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "更多",
|
||||
"label.name": "名字",
|
||||
"label.new-password": "新密码",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "密码",
|
||||
"label.passwords-dont-match": "密码不一致",
|
||||
"label.profile": "个人资料",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "总和",
|
||||
"metrics.filter.domain-only": "只看域名",
|
||||
"metrics.filter.raw": "原始",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "操作系统",
|
||||
"metrics.page-views": "页面浏览量",
|
||||
"metrics.pages": "网页",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"label.administrator": "管理員",
|
||||
"label.all": "所有",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-websites": "全部網站",
|
||||
"label.back": "返回",
|
||||
"label.cancel": "取消",
|
||||
@ -35,6 +36,7 @@
|
||||
"label.more": "更多",
|
||||
"label.name": "名字",
|
||||
"label.new-password": "新密碼",
|
||||
"label.owner": "Owner",
|
||||
"label.password": "密碼",
|
||||
"label.passwords-dont-match": "密碼不一致",
|
||||
"label.profile": "個人資料",
|
||||
@ -95,6 +97,7 @@
|
||||
"metrics.filter.combined": "總和",
|
||||
"metrics.filter.domain-only": "僅域名",
|
||||
"metrics.filter.raw": "原始",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.operating-systems": "操作系统",
|
||||
"metrics.page-views": "網頁流量",
|
||||
"metrics.pages": "網頁",
|
||||
|
@ -39,7 +39,11 @@ export function getDateRange(value, locale = 'en-US') {
|
||||
const now = new Date();
|
||||
const dateLocale = getDateLocale(locale);
|
||||
|
||||
const { num, unit } = value.match(/^(?<num>[0-9]+)(?<unit>hour|day|week|month|year)$/).groups;
|
||||
const match = value.match(/^(?<num>[0-9]+)(?<unit>hour|day|week|month|year)$/);
|
||||
|
||||
if (!match) return;
|
||||
|
||||
const { num, unit } = match.groups;
|
||||
|
||||
if (+num === 1) {
|
||||
switch (unit) {
|
||||
|
@ -36,7 +36,7 @@ export async function rawQuery(query, params = []) {
|
||||
|
||||
const sql = db === MYSQL ? query.replace(/\$[0-9]+/g, '?') : query;
|
||||
|
||||
return prisma.$queryRaw.apply(prisma, [sql, ...params]);
|
||||
return prisma.$queryRawUnsafe.apply(prisma, [sql, ...params]);
|
||||
}
|
||||
|
||||
export function getDateQuery(field, unit, timezone) {
|
||||
@ -115,6 +115,29 @@ export async function getUserWebsites(user_id) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getAllWebsites() {
|
||||
let data = await runQuery(
|
||||
prisma.website.findMany({
|
||||
orderBy: [
|
||||
{
|
||||
user_id: 'asc',
|
||||
},
|
||||
{
|
||||
name: 'asc',
|
||||
},
|
||||
],
|
||||
include: {
|
||||
account: {
|
||||
select: {
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
return data.map(i => ({ ...i, account: i.account.username }));
|
||||
}
|
||||
|
||||
export async function createWebsite(user_id, data) {
|
||||
return runQuery(
|
||||
prisma.website.create({
|
||||
@ -147,14 +170,11 @@ export async function resetWebsite(website_id) {
|
||||
|
||||
export async function deleteWebsite(website_id) {
|
||||
return runQuery(
|
||||
/* Prisma bug, does not cascade on non-nullable foreign keys
|
||||
prisma.website.delete({
|
||||
where: {
|
||||
website_id,
|
||||
},
|
||||
}),
|
||||
*/
|
||||
prisma.$queryRaw`delete from website where website_id=${website_id}`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -246,14 +266,11 @@ export async function updateAccount(user_id, data) {
|
||||
|
||||
export async function deleteAccount(user_id) {
|
||||
return runQuery(
|
||||
/* Prisma bug, does not cascade on non-nullable foreign keys
|
||||
prisma.account.delete({
|
||||
where: {
|
||||
user_id,
|
||||
},
|
||||
}),
|
||||
*/
|
||||
prisma.$queryRaw`delete from account where user_id=${user_id}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -37,16 +37,22 @@ export async function getSession(req) {
|
||||
let session = await getSessionByUuid(session_uuid);
|
||||
|
||||
if (!session) {
|
||||
session = await createSession(website_id, {
|
||||
session_uuid,
|
||||
hostname,
|
||||
browser,
|
||||
os,
|
||||
screen,
|
||||
language,
|
||||
country,
|
||||
device,
|
||||
});
|
||||
try {
|
||||
session = await createSession(website_id, {
|
||||
session_uuid,
|
||||
hostname,
|
||||
browser,
|
||||
os,
|
||||
screen,
|
||||
language,
|
||||
country,
|
||||
device,
|
||||
});
|
||||
} catch (e) {
|
||||
if (!e.message.includes('Unique constraint')) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { session_id } = session;
|
||||
|
12
package.json
12
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "umami",
|
||||
"version": "1.24.1",
|
||||
"version": "1.25.0",
|
||||
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
||||
"author": "Mike Cao <mike@mikecao.com>",
|
||||
"license": "MIT",
|
||||
@ -33,6 +33,8 @@
|
||||
"compile-lang": "formatjs compile-folder --ast build public/lang",
|
||||
"check-lang": "node scripts/check-lang.js",
|
||||
"download-country-names": "node scripts/download-country-names.js",
|
||||
"download-language-names": "node scripts/download-language-names.js",
|
||||
"change-password": "node scripts/change-password.js",
|
||||
"loadtest": "node scripts/loadtest.js",
|
||||
"loadtest:medium": "node scripts/loadtest.js --weight=medium",
|
||||
"loadtest:heavy": "node scripts/loadtest.js --weight=heavy --verbose",
|
||||
@ -54,12 +56,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "4.5.0",
|
||||
"@prisma/client": "2.29.1",
|
||||
"@prisma/client": "3.6.0",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chalk": "^4.1.1",
|
||||
"chart.js": "^2.9.4",
|
||||
"classnames": "^2.3.1",
|
||||
"colord": "^2.9.2",
|
||||
"cookie": "^0.4.1",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^2.23.0",
|
||||
@ -74,7 +77,7 @@
|
||||
"jose": "2.0.5",
|
||||
"maxmind": "^4.3.2",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"next": "12.0.1",
|
||||
"next": "12.0.5",
|
||||
"prompts": "2.4.2",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.2",
|
||||
@ -92,7 +95,6 @@
|
||||
"semver": "^7.3.5",
|
||||
"thenby": "^1.3.4",
|
||||
"timezone-support": "^2.0.2",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -122,7 +124,7 @@
|
||||
"postcss-rtlcss": "^3.3.2",
|
||||
"prettier": "^2.3.2",
|
||||
"prettier-eslint": "^13.0.0",
|
||||
"prisma": "2.29.1",
|
||||
"prisma": "3.6.0",
|
||||
"rollup": "^2.48.0",
|
||||
"rollup-plugin-hashbang": "^2.2.2",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
|
@ -2,7 +2,7 @@ import { getPageviewMetrics, getSessionMetrics, getWebsiteById } from 'lib/queri
|
||||
import { ok, methodNotAllowed, unauthorized, badRequest } from 'lib/response';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
|
||||
const sessionColumns = ['browser', 'os', 'device', 'country'];
|
||||
const sessionColumns = ['browser', 'os', 'device', 'country', 'language'];
|
||||
const pageviewColumns = ['url', 'referrer'];
|
||||
|
||||
function getTable(type) {
|
||||
@ -37,7 +37,19 @@ export default async (req, res) => {
|
||||
const endDate = new Date(+end_at);
|
||||
|
||||
if (sessionColumns.includes(type)) {
|
||||
const data = await getSessionMetrics(websiteId, startDate, endDate, type, { url });
|
||||
let data = await getSessionMetrics(websiteId, startDate, endDate, type, { url });
|
||||
|
||||
if (type === 'language') {
|
||||
let combined = {};
|
||||
|
||||
for (let { x, y } of data) {
|
||||
x = String(x).toLowerCase().split('-')[0];
|
||||
if (!combined[x]) combined[x] = { x, y };
|
||||
else combined[x].y += y;
|
||||
}
|
||||
|
||||
data = Object.values(combined);
|
||||
}
|
||||
|
||||
return ok(res, data);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getUserWebsites } from 'lib/queries';
|
||||
import { getAllWebsites, getUserWebsites } from 'lib/queries';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { ok, methodNotAllowed, unauthorized } from 'lib/response';
|
||||
|
||||
@ -6,7 +6,7 @@ export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
||||
const { user_id: current_user_id, is_admin } = req.auth;
|
||||
const { user_id } = req.query;
|
||||
const { user_id, include_all } = req.query;
|
||||
const userId = +user_id;
|
||||
|
||||
if (req.method === 'GET') {
|
||||
@ -14,7 +14,10 @@ export default async (req, res) => {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const websites = await getUserWebsites(userId || current_user_id);
|
||||
const websites =
|
||||
is_admin && include_all
|
||||
? await getAllWebsites()
|
||||
: await getUserWebsites(userId || current_user_id);
|
||||
|
||||
return ok(res, websites);
|
||||
}
|
||||
|
1
public/language/ar-SA.json
Normal file
1
public/language/ar-SA.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/ca-ES.json
Normal file
1
public/language/ca-ES.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/cs-CZ.json
Normal file
1
public/language/cs-CZ.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/da-DK.json
Normal file
1
public/language/da-DK.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/de-DE.json
Normal file
1
public/language/de-DE.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/el-GR.json
Normal file
1
public/language/el-GR.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/en-GB.json
Normal file
1
public/language/en-GB.json
Normal file
File diff suppressed because one or more lines are too long
184
public/language/en-US.json
Normal file
184
public/language/en-US.json
Normal file
@ -0,0 +1,184 @@
|
||||
{
|
||||
"ab": "Abkhaz",
|
||||
"aa": "Afar",
|
||||
"af": "Afrikaans",
|
||||
"ak": "Akan",
|
||||
"sq": "Albanian",
|
||||
"am": "Amharic",
|
||||
"ar": "Arabic",
|
||||
"an": "Aragonese",
|
||||
"hy": "Armenian",
|
||||
"as": "Assamese",
|
||||
"av": "Avaric",
|
||||
"ae": "Avestan",
|
||||
"ay": "Aymara",
|
||||
"az": "Azerbaijani",
|
||||
"bm": "Bambara",
|
||||
"ba": "Bashkir",
|
||||
"eu": "Basque",
|
||||
"be": "Belarusian",
|
||||
"bn": "Bengali",
|
||||
"bh": "Bihari",
|
||||
"bi": "Bislama",
|
||||
"bs": "Bosnian",
|
||||
"br": "Breton",
|
||||
"bg": "Bulgarian",
|
||||
"my": "Burmese",
|
||||
"ca": "Catalan; Valencian",
|
||||
"ch": "Chamorro",
|
||||
"ce": "Chechen",
|
||||
"ny": "Chichewa; Chewa; Nyanja",
|
||||
"zh": "Chinese",
|
||||
"cv": "Chuvash",
|
||||
"kw": "Cornish",
|
||||
"co": "Corsican",
|
||||
"cr": "Cree",
|
||||
"hr": "Croatian",
|
||||
"cs": "Czech",
|
||||
"da": "Danish",
|
||||
"dv": "Divehi; Dhivehi; Maldivian;",
|
||||
"nl": "Dutch",
|
||||
"en": "English",
|
||||
"eo": "Esperanto",
|
||||
"et": "Estonian",
|
||||
"ee": "Ewe",
|
||||
"fo": "Faroese",
|
||||
"fj": "Fijian",
|
||||
"fi": "Finnish",
|
||||
"fr": "French",
|
||||
"ff": "Fula; Fulah; Pulaar; Pular",
|
||||
"gl": "Galician",
|
||||
"ka": "Georgian",
|
||||
"de": "German",
|
||||
"el": "Greek, Modern",
|
||||
"gn": "Guaraní",
|
||||
"gu": "Gujarati",
|
||||
"ht": "Haitian; Haitian Creole",
|
||||
"ha": "Hausa",
|
||||
"he": "Hebrew (modern)",
|
||||
"hz": "Herero",
|
||||
"hi": "Hindi",
|
||||
"ho": "Hiri Motu",
|
||||
"hu": "Hungarian",
|
||||
"ia": "Interlingua",
|
||||
"id": "Indonesian",
|
||||
"ie": "Interlingue",
|
||||
"ga": "Irish",
|
||||
"ig": "Igbo",
|
||||
"ik": "Inupiaq",
|
||||
"io": "Ido",
|
||||
"is": "Icelandic",
|
||||
"it": "Italian",
|
||||
"iu": "Inuktitut",
|
||||
"ja": "Japanese",
|
||||
"jv": "Javanese",
|
||||
"kl": "Kalaallisut, Greenlandic",
|
||||
"kn": "Kannada",
|
||||
"kr": "Kanuri",
|
||||
"ks": "Kashmiri",
|
||||
"kk": "Kazakh",
|
||||
"km": "Khmer",
|
||||
"ki": "Kikuyu, Gikuyu",
|
||||
"rw": "Kinyarwanda",
|
||||
"ky": "Kirghiz, Kyrgyz",
|
||||
"kv": "Komi",
|
||||
"kg": "Kongo",
|
||||
"ko": "Korean",
|
||||
"ku": "Kurdish",
|
||||
"kj": "Kwanyama, Kuanyama",
|
||||
"la": "Latin",
|
||||
"lb": "Luxembourgish, Letzeburgesch",
|
||||
"lg": "Luganda",
|
||||
"li": "Limburgish, Limburgan, Limburger",
|
||||
"ln": "Lingala",
|
||||
"lo": "Lao",
|
||||
"lt": "Lithuanian",
|
||||
"lu": "Luba-Katanga",
|
||||
"lv": "Latvian",
|
||||
"gv": "Manx",
|
||||
"mk": "Macedonian",
|
||||
"mg": "Malagasy",
|
||||
"ms": "Malay",
|
||||
"ml": "Malayalam",
|
||||
"mt": "Maltese",
|
||||
"mi": "Māori",
|
||||
"mr": "Marathi (Marāṭhī)",
|
||||
"mh": "Marshallese",
|
||||
"mn": "Mongolian",
|
||||
"na": "Nauru",
|
||||
"nv": "Navajo, Navaho",
|
||||
"nb": "Norwegian Bokmål",
|
||||
"nd": "North Ndebele",
|
||||
"ne": "Nepali",
|
||||
"ng": "Ndonga",
|
||||
"nn": "Norwegian Nynorsk",
|
||||
"no": "Norwegian",
|
||||
"ii": "Nuosu",
|
||||
"nr": "South Ndebele",
|
||||
"oc": "Occitan",
|
||||
"oj": "Ojibwe, Ojibwa",
|
||||
"cu": "Old Church Slavonic, Church Slavic, Church Slavonic, Old Bulgarian, Old Slavonic",
|
||||
"om": "Oromo",
|
||||
"or": "Oriya",
|
||||
"os": "Ossetian, Ossetic",
|
||||
"pa": "Panjabi, Punjabi",
|
||||
"pi": "Pāli",
|
||||
"fa": "Persian",
|
||||
"pl": "Polish",
|
||||
"ps": "Pashto, Pushto",
|
||||
"pt": "Portuguese",
|
||||
"qu": "Quechua",
|
||||
"rm": "Romansh",
|
||||
"rn": "Kirundi",
|
||||
"ro": "Romanian, Moldavian, Moldovan",
|
||||
"ru": "Russian",
|
||||
"sa": "Sanskrit (Saṁskṛta)",
|
||||
"sc": "Sardinian",
|
||||
"sd": "Sindhi",
|
||||
"se": "Northern Sami",
|
||||
"sm": "Samoan",
|
||||
"sg": "Sango",
|
||||
"sr": "Serbian",
|
||||
"gd": "Scottish Gaelic; Gaelic",
|
||||
"sn": "Shona",
|
||||
"si": "Sinhala, Sinhalese",
|
||||
"sk": "Slovak",
|
||||
"sl": "Slovene",
|
||||
"so": "Somali",
|
||||
"st": "Southern Sotho",
|
||||
"es": "Spanish; Castilian",
|
||||
"su": "Sundanese",
|
||||
"sw": "Swahili",
|
||||
"ss": "Swati",
|
||||
"sv": "Swedish",
|
||||
"ta": "Tamil",
|
||||
"te": "Telugu",
|
||||
"tg": "Tajik",
|
||||
"th": "Thai",
|
||||
"ti": "Tigrinya",
|
||||
"bo": "Tibetan Standard, Tibetan, Central",
|
||||
"tk": "Turkmen",
|
||||
"tl": "Tagalog",
|
||||
"tn": "Tswana",
|
||||
"to": "Tonga (Tonga Islands)",
|
||||
"tr": "Turkish",
|
||||
"ts": "Tsonga",
|
||||
"tt": "Tatar",
|
||||
"tw": "Twi",
|
||||
"ty": "Tahitian",
|
||||
"ug": "Uighur, Uyghur",
|
||||
"uk": "Ukrainian",
|
||||
"ur": "Urdu",
|
||||
"uz": "Uzbek",
|
||||
"ve": "Venda",
|
||||
"vi": "Vietnamese",
|
||||
"vo": "Volapük",
|
||||
"wa": "Walloon",
|
||||
"cy": "Welsh",
|
||||
"wo": "Wolof",
|
||||
"fy": "Western Frisian",
|
||||
"xh": "Xhosa",
|
||||
"yi": "Yiddish",
|
||||
"yo": "Yoruba",
|
||||
"za": "Zhuang, Chuang"
|
||||
}
|
1
public/language/es-MX.json
Normal file
1
public/language/es-MX.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/fa-IR.json
Normal file
1
public/language/fa-IR.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/fi-FI.json
Normal file
1
public/language/fi-FI.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/fo-FO.json
Normal file
1
public/language/fo-FO.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/fr-FR.json
Normal file
1
public/language/fr-FR.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/he-IL.json
Normal file
1
public/language/he-IL.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/hi-IN.json
Normal file
1
public/language/hi-IN.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/hu-HU.json
Normal file
1
public/language/hu-HU.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/id-ID.json
Normal file
1
public/language/id-ID.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/it-IT.json
Normal file
1
public/language/it-IT.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/ja-JP.json
Normal file
1
public/language/ja-JP.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/ko-KR.json
Normal file
1
public/language/ko-KR.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/mn-MN.json
Normal file
1
public/language/mn-MN.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/ms-MY.json
Normal file
1
public/language/ms-MY.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/nb-NO.json
Normal file
1
public/language/nb-NO.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/nl-NL.json
Normal file
1
public/language/nl-NL.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/pl-PL.json
Normal file
1
public/language/pl-PL.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/pt-BR.json
Normal file
1
public/language/pt-BR.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/pt-PT.json
Normal file
1
public/language/pt-PT.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/ro-RO.json
Normal file
1
public/language/ro-RO.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/ru-RU.json
Normal file
1
public/language/ru-RU.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/sk-SK.json
Normal file
1
public/language/sk-SK.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/sl-SI.json
Normal file
1
public/language/sl-SI.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/sv-SE.json
Normal file
1
public/language/sv-SE.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/ta-IN.json
Normal file
1
public/language/ta-IN.json
Normal file
File diff suppressed because one or more lines are too long
1
public/language/tr-TR.json
Normal file
1
public/language/tr-TR.json
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user