feat: add i18n with ttag

This commit is contained in:
Maël Gangloff 2024-07-28 15:36:22 +02:00
parent 05baaf911f
commit df0a93b8e6
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
13 changed files with 447 additions and 98 deletions

View File

@ -31,6 +31,7 @@ import LoginPage, {AuthenticatedContext} from "./pages/LoginPage";
import ConnectorsPage from "./pages/tracking/ConnectorsPage";
import NotFoundPage from "./pages/NotFoundPage";
import {ItemType, MenuItemType} from "antd/lib/menu/interface";
import {t} from 'ttag'
export default function App() {
const {
@ -67,36 +68,36 @@ export default function App() {
const menuItems: ItemType<MenuItemType>[] = [
{
key: 'home',
label: 'Home',
label: t`Home`,
icon: <HomeOutlined/>,
onClick: () => navigate('/home')
},
{
key: 'search',
label: 'Search',
label: t`Search`,
icon: <SearchOutlined/>,
children: [
{
key: 'domain-finder',
icon: <CompassOutlined/>,
label: 'Domain',
title: 'Domain Finder',
label: t`Domain`,
title: t`Domain Finder`,
disabled: !isAuthenticated,
onClick: () => navigate('/search/domain')
},
{
key: 'entity-finder',
icon: <TeamOutlined/>,
label: 'Entity',
title: 'Entity Finder',
label: t`Entity`,
title: t`Entity Finder`,
disabled: !isAuthenticated,
onClick: () => navigate('/search/entity')
},
{
key: 'ns-finder',
icon: <CloudServerOutlined/>,
label: 'Nameserver',
title: 'Nameserver Finder',
label: t`Nameserver`,
title: t`Nameserver Finder`,
disabled: !isAuthenticated,
onClick: () => navigate('/search/nameserver')
}
@ -104,21 +105,21 @@ export default function App() {
},
{
key: 'info',
label: 'Information',
label: t`Information`,
icon: <InfoCircleOutlined/>,
children: [
{
key: 'tld-list',
icon: <BankOutlined/>,
label: 'TLD',
title: 'TLD list',
label: t`TLD`,
title: t`TLD list`,
disabled: !isAuthenticated,
onClick: () => navigate('/info/tld')
},
{
key: 'stats',
icon: <LineChartOutlined/>,
label: 'Statistics',
label: t`Statistics`,
disabled: !isAuthenticated,
onClick: () => navigate('/info/stats')
}
@ -126,20 +127,20 @@ export default function App() {
},
{
key: 'tracking',
label: 'Tracking',
label: t`Tracking`,
icon: <FileSearchOutlined/>,
children: [
{
key: 'watchlist',
icon: <Badge count={0} size="small"><FileSearchOutlined/></Badge>,
label: 'My Watchlists',
label: t`My Watchlists`,
disabled: !isAuthenticated,
onClick: () => navigate('/tracking/watchlist')
},
{
key: 'connectors',
icon: <ApiOutlined/>,
label: 'My connectors',
label: t`My connectors`,
disabled: !isAuthenticated,
onClick: () => navigate('/tracking/connectors')
}
@ -147,26 +148,26 @@ export default function App() {
},
{
key: 'watchdog',
label: 'My Watchdog',
label: t`My Watchdog`,
icon: <UserOutlined/>,
children: [
{
key: 'account',
icon: <UserOutlined/>,
label: 'My Account',
label: t`My Account`,
disabled: !isAuthenticated,
onClick: () => navigate('/user')
},
{
key: 'tos',
icon: <InfoCircleOutlined/>,
label: 'TOS',
label: t`TOS`,
onClick: () => navigate('/tos')
},
{
key: 'privacy',
icon: <FileProtectOutlined/>,
label: 'Privacy Policy',
label: t`Privacy Policy`,
onClick: () => navigate('/privacy')
}
]
@ -174,7 +175,7 @@ export default function App() {
{
key: '5',
icon: <QuestionCircleOutlined/>,
label: 'FAQ',
label: t`FAQ`,
onClick: () => navigate('/faq')
},
@ -192,13 +193,13 @@ export default function App() {
items={[...menuItems, isAuthenticated ? {
key: '8',
icon: <LogoutOutlined/>,
label: 'Log out',
label: t`Log out`,
danger: true,
onClick: () => window.location.replace("/logout")
} : {
key: '8',
icon: <LoginOutlined/>,
label: 'Log in',
label: t`Log in`,
onClick: () => navigate('/login')
}]}
/>

13
assets/i18n/index.ts Normal file
View File

@ -0,0 +1,13 @@
import {addLocale, useLocale} from 'ttag'
const locale = navigator.language.split('-')[0];
if (locale !== 'en') {
fetch(`/locales/${locale}.po.json`).then(response => {
if (!response.ok) throw new Error(`Failed to load translations for locale ${locale}`);
response.json().then(translationsObj => {
addLocale(locale, translationsObj);
useLocale(locale);
})
})
}

View File

@ -4,6 +4,7 @@ import App from "./App";
import {HashRouter} from "react-router-dom";
import 'antd/dist/reset.css';
import './i18n'
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)

View File

@ -2,6 +2,7 @@ import React, {createContext, useContext, useState} from "react";
import {Alert, Button, Card, Flex, Form, Input} from "antd";
import {login} from "../utils/api";
import {useNavigate} from "react-router-dom";
import {t} from 'ttag'
type FieldType = {
username: string;
@ -14,7 +15,7 @@ export default function Page() {
const [error, setError] = useState()
const navigate = useNavigate()
const {isAuthenticated, setIsAuthenticated} = useContext(AuthenticatedContext)
const {setIsAuthenticated} = useContext(AuthenticatedContext)
const onFinish = (data: FieldType) => {
login(data.username, data.password).then(() => {
@ -27,12 +28,12 @@ export default function Page() {
}
return <Flex gap="middle" align="center" justify="center" vertical><Card
title="Log in"
title={t`Log in`}
>
{error &&
<Alert
type='error'
message='Error'
message={t`Error`}
banner={true}
role='role'
description={error}
@ -47,29 +48,29 @@ export default function Page() {
autoComplete="off"
>
<Form.Item
label="Username"
label={t`Username`}
name="username"
rules={[{required: true, message: 'Required'}]}
rules={[{required: true, message: t`Required`}]}
>
<Input/>
</Form.Item>
<Form.Item<FieldType>
label="Password"
label={t`Password`}
name="password"
rules={[{required: true, message: 'Required'}]}
rules={[{required: true, message: t`Required`}]}
>
<Input.Password/>
</Form.Item>
<Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type="primary" htmlType="submit">
Submit
{t`Submit`}
</Button>
</Form.Item>
<Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type="primary" htmlType="button" href="/login/oauth">
Log in with SSO
{t`Log in with SSO`}
</Button>
</Form.Item>
</Form>

View File

@ -1,11 +1,12 @@
import {Result} from "antd";
import React from "react";
import { t } from 'ttag'
export default function NotFoundPage() {
return <Result
status="404"
title="404"
subTitle="Sorry, the page you visited does not exist."
subTitle={t`Sorry, the page you visited does not exist.`}
/>
}

View File

@ -1,6 +1,7 @@
import React, {useEffect, useState} from "react";
import {Collapse, Divider, Table, Typography} from "antd";
import {getTldList, Tld} from "../../utils/api";
import {t} from 'ttag'
const {Text, Paragraph} = Typography
@ -60,24 +61,24 @@ function TldTable(filters: FiltersType) {
let columns = [
{
title: "TLD",
title: t`TLD`,
dataIndex: "TLD",
width: 20
}
]
if (filters.type === 'ccTLD') columns = [...columns, {
title: "Flag",
title: t`Flag`,
dataIndex: "Flag",
width: 20
}, {
title: "Country",
title: t`Country`,
dataIndex: "Country",
width: 200
}]
if (filters.type === 'gTLD') columns = [...columns, {
title: "Registry Operator",
title: t`Registry Operator`,
dataIndex: "Operator",
width: 50
}]
@ -102,16 +103,13 @@ function TldTable(filters: FiltersType) {
export default function TldPage() {
return <>
<Paragraph>
This page presents all active TLDs in the root zone database.
{t`This page presents all active TLDs in the root zone database.`}
</Paragraph>
<Paragraph>
IANA provides the list of currently active
TLDs, regardless of their type, and ICANN provides the list of gTLDs.
In most cases, the two-letter ccTLD assigned to a country is made in accordance with the ISO 3166-1
standard.
This data is updated every month. Three HTTP requests are needed for the complete update of TLDs in Domain
Watchdog (two requests to IANA and one to ICANN).
At the same time, the list of root RDAP servers is updated.
{t`IANA provides the list of currently active TLDs, regardless of their type, and ICANN provides the list of gTLDs.
In most cases, the two-letter ccTLD assigned to a country is made in accordance with the ISO 3166-1 standard.
This data is updated every month. Three HTTP requests are needed for the complete update of TLDs in Domain Watchdog (two requests to IANA and one to ICANN).
At the same time, the list of root RDAP servers is updated.`}
</Paragraph>
<Divider/>
<Collapse
@ -119,41 +117,36 @@ export default function TldPage() {
items={[
{
key: 'sTLD',
label: 'Sponsored Top-Level-Domains',
label: t`Sponsored Top-Level-Domains`,
children: <>
<Text>Top-level domains sponsored by specific organizations that set rules for
registration and use, often related to particular interest groups or
industries.</Text>
<Text>{t`Top - level domains sponsored by specific organizations that set rules for registration and use, often related to particular interest groups or industries.`}</Text>
<Divider/>
<TldTable type='sTLD'/>
</>
},
{
key: 'gTLD',
label: 'Generic Top-Level-Domains',
label: t`Generic Top-Level-Domains`,
children: <>
<Text>Generic top-level domains open to everyone, not restricted by specific
criteria, representing various themes or industries.</Text>
<Text>{t`Generic top-level domains open to everyone, not restricted by specific criteria, representing various themes or industries.`}</Text>
<Divider/>
<TldTable type='gTLD' contractTerminated={false} specification13={false}/>
</>
},
{
key: 'ngTLD',
label: 'Brand Generic Top-Level-Domains',
label: t`Brand Generic Top-Level-Domains`,
children: <>
<Text>Generic top-level domains associated with specific brands, allowing companies
to use their own brand names as domains.</Text>
<Text>{t`Generic top-level domains associated with specific brands, allowing companies to use their own brand names as domains.`}</Text>
<Divider/>
<TldTable type='gTLD' contractTerminated={false} specification13={true}/>
</>
},
{
key: 'ccTLD',
label: 'Country-Code Top-Level-Domains',
label: t`Country-Code Top-Level-Domains`,
children: <>
<Text>Top-level domains based on country codes, identifying websites according to
their country of origin.</Text>
<Text>{`Top-level domains based on country codes, identifying websites according to their country of origin.`}</Text>
<Divider/><TldTable type='ccTLD'/>
</>
}

View File

@ -33,6 +33,7 @@ import {
import {Domain, getDomain} from "../../utils/api";
import {AxiosError} from "axios"
import vCard from 'vcf'
import {t} from 'ttag'
type FieldType = {
@ -48,16 +49,16 @@ export default function DomainSearchPage() {
setDomain(null)
getDomain(values.ldhName).then(d => {
setDomain(d)
messageApi.success('Found !')
messageApi.success(t`Found !`)
}).catch((e: AxiosError) => {
const data = e?.response?.data as { detail: string }
setDomain(undefined)
messageApi.error(data.detail ?? 'An error occurred')
messageApi.error(data.detail ?? t`An error occurred`)
})
}
return <Flex gap="middle" align="center" justify="center" vertical>
<Card title="Domain finder" style={{width: '100%'}}>
<Card title={t`Domain finder`} style={{width: '100%'}}>
{contextHolder}
<Form
name="basic"
@ -70,10 +71,10 @@ export default function DomainSearchPage() {
name="ldhName"
rules={[{
required: true,
message: 'Required'
message: t`Required`
}, {
pattern: /^(?=.*\.)\S*[^.\s]$/,
message: 'This domain name does not appear to be valid',
message: t`This domain name does not appear to be valid`,
max: 63,
min: 2
}]}
@ -189,8 +190,7 @@ export default function DomainSearchPage() {
: <Empty
description={
<Typography.Text>
Although the domain exists in my database, it has been deleted from the WHOIS by its
registrar.
{t`Although the domain exists in my database, it has been deleted from the WHOIS by its registrar.`}
</Typography.Text>
}/>)
}

View File

@ -4,6 +4,7 @@ import {Button, Card, Divider, Flex, Form, Input, message, Popconfirm, Select, S
import {DeleteFilled, MinusCircleOutlined, PlusOutlined, ThunderboltFilled} from "@ant-design/icons";
import {deleteWatchlist, EventAction, getWatchlists, postWatchlist} from "../../utils/api";
import {AxiosError} from "axios";
import {t} from 'ttag'
const formItemLayout = {
@ -26,42 +27,42 @@ const formItemLayoutWithOutLabel = {
const triggerEventItems: { label: string, value: EventAction }[] = [
{
label: 'When a domain is expired',
label: t`When a domain is expired`,
value: 'expiration'
},
{
label: 'When a domain is updated',
label: t`When a domain is updated`,
value: 'last changed'
},
{
label: 'When a domain is deleted',
label: t`When a domain is deleted`,
value: 'deletion'
},
{
label: 'When a domain is transferred',
label: t`When a domain is transferred`,
value: 'transfer'
},
{
label: 'When a domain is locked',
label: t`When a domain is locked`,
value: 'locked'
},
{
label: 'When a domain is unlocked',
label: t`When a domain is unlocked`,
value: 'unlocked'
},
{
label: 'When a domain is reregistered',
label: t`When a domain is reregistered`,
value: 'reregistration'
},
{
label: 'When a domain is reinstantiated',
label: t`When a domain is reinstantiated`,
value: 'reinstantiation'
}
]
const trigerActionItems = [
{
label: 'Send me an email',
label: t`Send me an email`,
value: 'email'
}
]
@ -78,10 +79,10 @@ export default function WatchlistPage() {
postWatchlist(domainsURI, values.triggers).then((w) => {
form.resetFields()
refreshWatchlists()
messageApi.success('Watchlist created !')
messageApi.success(t`Watchlist created !`)
}).catch((e: AxiosError) => {
const data = e?.response?.data as { detail: string }
messageApi.error(data.detail ?? 'An error occurred')
messageApi.error(data.detail ?? t`An error occurred`)
})
}
@ -94,7 +95,7 @@ export default function WatchlistPage() {
}, [])
return <Flex gap="middle" align="center" justify="center" vertical>
<Card title="Create a watchlist" style={{width: '100%'}}>
<Card title={t`Create a watchlist`} style={{width: '100%'}}>
{contextHolder}
<Form
{...formItemLayoutWithOutLabel}
@ -107,7 +108,7 @@ export default function WatchlistPage() {
{
validator: async (_, domains) => {
if (!domains || domains.length < 1) {
return Promise.reject(new Error('At least one domain name'));
return Promise.reject(new Error(t`At least one domain name`));
}
},
},
@ -118,7 +119,7 @@ export default function WatchlistPage() {
{fields.map((field, index) => (
<Form.Item
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
label={index === 0 ? 'Domain names' : ''}
label={index === 0 ? t`Domain names` : ''}
required={true}
key={field.key}
>
@ -127,16 +128,16 @@ export default function WatchlistPage() {
validateTrigger={['onChange', 'onBlur']}
rules={[{
required: true,
message: 'Required'
message: t`Required`
}, {
pattern: /^(?=.*\.)\S*[^.\s]$/,
message: 'This domain name does not appear to be valid',
message: t`This domain name does not appear to be valid`,
max: 63,
min: 2
}]}
noStyle
>
<Input placeholder="Domain name" style={{width: '60%'}} autoComplete='off'/>
<Input placeholder={t`Domain name`} style={{width: '60%'}} autoComplete='off'/>
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
@ -153,7 +154,7 @@ export default function WatchlistPage() {
style={{width: '60%'}}
icon={<PlusOutlined/>}
>
Add a Domain name
{t`Add a Domain name`}
</Button>
<Form.ErrorList errors={errors}/>
</Form.Item>
@ -166,7 +167,7 @@ export default function WatchlistPage() {
{
validator: async (_, domains) => {
if (!domains || domains.length < 1) {
return Promise.reject(new Error('At least one domain trigger'));
return Promise.reject(new Error(t`At least one domain trigger`));
}
},
},
@ -177,7 +178,7 @@ export default function WatchlistPage() {
{fields.map((field, index) => (
<Form.Item
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
label={index === 0 ? 'Domain trigger' : ''}
label={index === 0 ? t`Domain trigger` : ''}
required={true}
key={field.key}
>
@ -187,21 +188,21 @@ export default function WatchlistPage() {
validateTrigger={['onChange', 'onBlur']}
rules={[{
required: true,
message: 'Required'
message: t`Required`
}]}
noStyle name={[field.name, 'event']}>
<Select style={{minWidth: 300}} options={triggerEventItems} showSearch
placeholder="If this happens" optionFilterProp="label"/>
placeholder={t`If this happens`} optionFilterProp="label"/>
</Form.Item>
<Form.Item {...field}
validateTrigger={['onChange', 'onBlur']}
rules={[{
required: true,
message: 'Required'
message: t`Required`
}]}
noStyle name={[field.name, 'action']}>
<Select style={{minWidth: 300}} options={trigerActionItems} showSearch
placeholder="Then do that"
placeholder={t`Then do that`}
optionFilterProp="label"/>
</Form.Item>
</Space>
@ -222,7 +223,7 @@ export default function WatchlistPage() {
style={{width: '60%'}}
icon={<ThunderboltFilled/>}
>
Add a Trigger
{t`Add a Trigger`}
</Button>
<Form.ErrorList errors={errors}/>
</Form.Item>
@ -232,10 +233,10 @@ export default function WatchlistPage() {
<Form.Item>
<Space>
<Button type="primary" htmlType="submit">
Create
{t`Create`}
</Button>
<Button type="default" htmlType="reset">
Reset
{t`Reset`}
</Button>
</Space>
</Form.Item>
@ -247,19 +248,19 @@ export default function WatchlistPage() {
{watchlists && watchlists.length > 0 && <Card title="My Watchlists" style={{width: '100%'}}>
{watchlists.map(watchlist =>
<>
<Card title={"Watchlist " + watchlist.token} extra={<Popconfirm
title="Delete the Watchlist"
description="Are you sure to delete this Watchlist ?"
<Card title={t`Watchlist ${watchlist.token}`} extra={<Popconfirm
title={t`"Delete the Watchlist"`}
description={t`Are you sure to delete this Watchlist?`}
onConfirm={() => deleteWatchlist(watchlist.token).then(refreshWatchlists)}
okText="Yes"
cancelText="No"
okText={t`"Yes"`}
cancelText={t`No`}
><DeleteFilled/> </Popconfirm>}>
<Typography.Paragraph>
Domains : {watchlist?.domains.map(d => d.ldhName).join(',')}
{t`Domain name`} : {watchlist?.domains.map(d => d.ldhName).join(',')}
</Typography.Paragraph>
{
watchlist.triggers && <Typography.Paragraph>
Triggers : {watchlist.triggers.map(t => `${t.event} => ${t.action}`).join(',')}
{t`Domain triggers`} : {watchlist.triggers.map(t => `${t.event} => ${t.action}`).join(',')}
</Typography.Paragraph>
}
</Card>

View File

@ -37,6 +37,7 @@
"regenerator-runtime": "^0.13.9",
"snarkdown": "^2.0.0",
"ts-loader": "^9.5.1",
"ttag": "^1.8.7",
"typescript": "^5.5.3",
"vcf": "^2.1.2",
"webpack": "^5.74.0",

1
public/locales/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.po.json

318
public/locales/fr.po Normal file
View File

@ -0,0 +1,318 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Plural-Forms: nplurals = 2; plural = (n > 1);\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
#: assets/pages/NotFoundPage.tsx:10
msgid "Sorry, the page you visited does not exist."
msgstr ""
#: assets/App.tsx:114
#: assets/pages/info/TldPage.tsx:64
msgid "TLD"
msgstr ""
#: assets/pages/info/TldPage.tsx:71
msgid "Flag"
msgstr ""
#: assets/pages/info/TldPage.tsx:75
msgid "Country"
msgstr ""
#: assets/pages/info/TldPage.tsx:81
msgid "Registry Operator"
msgstr ""
#: assets/pages/info/TldPage.tsx:106
msgid "This page presents all active TLDs in the root zone database."
msgstr ""
#: assets/pages/info/TldPage.tsx:109
msgid ""
"IANA provides the list of currently active TLDs, regardless of their type, and ICANN provides the list of gTLDs.\n"
"In most cases, the two-letter ccTLD assigned to a country is made in accordance with the ISO 3166-1 standard.\n"
"This data is updated every month. Three HTTP requests are needed for the complete update of TLDs in Domain Watchdog (two requests to IANA and one to ICANN).\n"
"At the same time, the list of root RDAP servers is updated."
msgstr ""
#: assets/pages/info/TldPage.tsx:120
msgid "Sponsored Top-Level-Domains"
msgstr ""
#: assets/pages/info/TldPage.tsx:122
msgid "Top - level domains sponsored by specific organizations that set rules for registration and use, often related to particular interest groups or industries."
msgstr ""
#: assets/pages/info/TldPage.tsx:129
msgid "Generic Top-Level-Domains"
msgstr ""
#: assets/pages/info/TldPage.tsx:131
msgid "Generic top-level domains open to everyone, not restricted by specific criteria, representing various themes or industries."
msgstr ""
#: assets/pages/info/TldPage.tsx:138
msgid "Brand Generic Top-Level-Domains"
msgstr ""
#: assets/pages/info/TldPage.tsx:140
msgid "Generic top-level domains associated with specific brands, allowing companies to use their own brand names as domains."
msgstr ""
#: assets/pages/info/TldPage.tsx:147
msgid "Country-Code Top-Level-Domains"
msgstr ""
#: assets/App.tsx:202
#: assets/pages/LoginPage.tsx:31
msgid "Log in"
msgstr ""
#: assets/pages/LoginPage.tsx:36
msgid "Error"
msgstr ""
#: assets/pages/LoginPage.tsx:51
msgid "Username"
msgstr ""
#: assets/pages/LoginPage.tsx:53
#: assets/pages/LoginPage.tsx:61
#: assets/pages/search/DomainSearchPage.tsx:74
#: assets/pages/tracking/WatchlistPage.tsx:131
#: assets/pages/tracking/WatchlistPage.tsx:191
#: assets/pages/tracking/WatchlistPage.tsx:201
msgid "Required"
msgstr ""
#: assets/pages/LoginPage.tsx:59
msgid "Password"
msgstr ""
#: assets/pages/LoginPage.tsx:68
msgid "Submit"
msgstr ""
#: assets/pages/LoginPage.tsx:73
msgid "Log in with SSO"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:52
msgid "Found !"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:56
#: assets/pages/tracking/WatchlistPage.tsx:85
msgid "An error occurred"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:61
msgid "Domain finder"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:77
#: assets/pages/tracking/WatchlistPage.tsx:134
msgid "This domain name does not appear to be valid"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:193
msgid "Although the domain exists in my database, it has been deleted from the WHOIS by its registrar."
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:30
msgid "When a domain is expired"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:34
msgid "When a domain is updated"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:38
msgid "When a domain is deleted"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:42
msgid "When a domain is transferred"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:46
msgid "When a domain is locked"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:50
msgid "When a domain is unlocked"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:54
msgid "When a domain is reregistered"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:58
msgid "When a domain is reinstantiated"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:65
msgid "Send me an email"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:82
msgid "Watchlist created !"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:98
msgid "Create a watchlist"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:111
msgid "At least one domain name"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:122
msgid "Domain names"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:140
#: assets/pages/tracking/WatchlistPage.tsx:259
msgid "Domain name"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:157
msgid "Add a Domain name"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:170
msgid "At least one domain trigger"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:181
msgid "Domain trigger"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:195
msgid "If this happens"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:205
msgid "Then do that"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:226
msgid "Add a Trigger"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:236
msgid "Create"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:239
msgid "Reset"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:251
#, javascript-format
msgid "Watchlist ${ watchlist.token }"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:252
msgid "\"Delete the Watchlist\""
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:253
msgid "Are you sure to delete this Watchlist?"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:255
msgid "\"Yes\""
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:256
msgid "No"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:263
msgid "Domain triggers"
msgstr ""
#: assets/App.tsx:71
msgid "Home"
msgstr ""
#: assets/App.tsx:77
msgid "Search"
msgstr ""
#: assets/App.tsx:83
msgid "Domain"
msgstr ""
#: assets/App.tsx:84
msgid "Domain Finder"
msgstr ""
#: assets/App.tsx:91
msgid "Entity"
msgstr ""
#: assets/App.tsx:92
msgid "Entity Finder"
msgstr ""
#: assets/App.tsx:99
msgid "Nameserver"
msgstr ""
#: assets/App.tsx:100
msgid "Nameserver Finder"
msgstr ""
#: assets/App.tsx:108
msgid "Information"
msgstr ""
#: assets/App.tsx:115
msgid "TLD list"
msgstr ""
#: assets/App.tsx:122
msgid "Statistics"
msgstr ""
#: assets/App.tsx:130
msgid "Tracking"
msgstr ""
#: assets/App.tsx:136
msgid "My Watchlists"
msgstr ""
#: assets/App.tsx:143
msgid "My connectors"
msgstr ""
#: assets/App.tsx:151
msgid "My Watchdog"
msgstr ""
#: assets/App.tsx:157
msgid "My Account"
msgstr ""
#: assets/App.tsx:164
msgid "TOS"
msgstr ""
#: assets/App.tsx:170
msgid "Privacy Policy"
msgstr ""
#: assets/App.tsx:178
msgid "FAQ"
msgstr ""
#: assets/App.tsx:196
msgid "Log out"
msgstr ""

View File

@ -2522,6 +2522,11 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
dependencies:
ms "2.1.2"
dedent@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff"
integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==
default-gateway@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71"
@ -4267,6 +4272,11 @@ pkg-up@^3.1.0:
dependencies:
find-up "^3.0.0"
plural-forms@^0.5.3:
version "0.5.5"
resolved "https://registry.yarnpkg.com/plural-forms/-/plural-forms-0.5.5.tgz#d15ca5597aff37373c97edc039ba11659461120e"
integrity sha512-rJw4xp22izsfJOVqta5Hyvep2lR3xPkFUtj7dyQtpf/FbxUiX7PQCajTn2EHDRylizH5N/Uqqodfdu22I0ju+g==
possible-typed-array-names@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f"
@ -5687,6 +5697,14 @@ tslib@^2.0.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
ttag@^1.8.7:
version "1.8.7"
resolved "https://registry.yarnpkg.com/ttag/-/ttag-1.8.7.tgz#0a5621f18f727379cbe21df2a02dab66a2d9b614"
integrity sha512-k9Ym8cvG7SHwikudT6GHe0Qmy1D+Ib1q87lKRQbQIGxUdHbaXgbU5p1gv2wcO5ouhjMorm/X0MvMNgr3iyI1JA==
dependencies:
dedent "1.5.1"
plural-forms "^0.5.3"
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"