diff --git a/package-lock.json b/package-lock.json index 788e0bf..037368d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10676,9 +10676,27 @@ "dev": true }, "i18next": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-13.1.4.tgz", - "integrity": "sha512-qPmjdQrUQ4AGzKBGBpPLC7XiJoqbV/dGmDW/xmwbzAk60eavTkZ+s5FqoSAP2YeurPF8XpNWQaDvXNSWzT0ZYw==" + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-15.1.3.tgz", + "integrity": "sha512-hN2DZLoRSY2h/RYeNqth5XxV4N1ekKGSJDCGhFmmuXkOCAfK5CkUG4VBv9OBXrvf93xApv0KKBVrb0zJP31EKg==", + "requires": { + "@babel/runtime": "^7.3.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", + "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + } + } }, "i18next-browser-languagedetector": { "version": "2.2.4", diff --git a/package.json b/package.json index ff69408..c43d50f 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "bootstrap": "^4.3.1", "bootstrap-daterangepicker": "^3.0.5", "chart.js": "^2.8.0", - "i18next": "^13.1.4", + "i18next": "^15.1.3", "i18next-browser-languagedetector": "^2.2.4", "i18next-xhr-backend": "^2.0.1", "ionicons": "^4.5.8", diff --git a/src/components/common/Sidebar.tsx b/src/components/common/Sidebar.tsx index 3b2dc06..625cf5c 100644 --- a/src/components/common/Sidebar.tsx +++ b/src/components/common/Sidebar.tsx @@ -37,7 +37,7 @@ export const PiholeNavItem = ({ t }: { item: RouteItem; - t: i18next.TranslationFunction; + t: i18next.TFunction; }): ReactElement => ( ( ; - t: i18next.TranslationFunction; + t: i18next.TFunction; location: Location; }): ReactElement => ( diff --git a/src/components/common/__tests__/Sidebar.test.tsx b/src/components/common/__tests__/Sidebar.test.tsx index 56ef07f..4caa1a1 100644 --- a/src/components/common/__tests__/Sidebar.test.tsx +++ b/src/components/common/__tests__/Sidebar.test.tsx @@ -15,6 +15,8 @@ import api from "../../../util/api"; import { RouteCustomItem, RouteGroup } from "../../../routes"; import NavDropdown from "../NavDropdown"; +const t = global.t; + it("expands active drop down items", () => { const item: RouteGroup = { name: "testName", @@ -27,7 +29,7 @@ it("expands active drop down items", () => { const wrapper = shallow( key} + t={t} location={{ pathname: "/testRoute/page" } as Location} /> ); @@ -47,7 +49,7 @@ it("does not expand inactive drop down items", () => { const wrapper = shallow( key} + t={t} location={{ pathname: "/page" } as Location} /> ); @@ -63,7 +65,7 @@ it("creates nav items with correct data", () => { auth: false, component: () =>
}; - const wrapper = shallow( key} />); + const wrapper = shallow(); expect(wrapper.childAt(0)).toHaveProp("to", item.url); expect(wrapper.childAt(0).childAt(0)).toHaveClassName(item.icon); @@ -80,7 +82,7 @@ it("creates a nav dropdown with correct data", () => { }; const location = { pathname: "/blacklist/exact" } as Location; const wrapper = shallow( - key} location={location} /> + ); expect(wrapper).toHaveProp("icon", item.icon); @@ -99,7 +101,7 @@ it("shows auth routes when logged in", () => { auth: true }; const wrapper = shallow( - key} location={{} as Location} /> + ); expect(wrapper.children()).toHaveLength(1); @@ -116,7 +118,7 @@ it("hides auth routes when not logged in", () => { auth: true }; const wrapper = shallow( - key} location={{} as Location} /> + ); expect(wrapper.children()).toHaveLength(0); @@ -134,7 +136,7 @@ it("hides strict non-auth routes when logged in", () => { authStrict: true }; const wrapper = shallow( - key} location={{} as Location} /> + ); expect(wrapper.children()).toHaveLength(0); @@ -152,7 +154,7 @@ it("hides strict auth routes when not logged in", () => { authStrict: true }; const wrapper = shallow( - key} location={{} as Location} /> + ); expect(wrapper.children()).toHaveLength(0); @@ -165,7 +167,7 @@ it("uses a custom component if given", () => { customComponent: () => null }; const wrapper = shallow( - key} location={{} as Location} /> + ); expect(wrapper.children()).toHaveLength(1); diff --git a/src/components/dashboard/TimeRangeSelector.tsx b/src/components/dashboard/TimeRangeSelector.tsx index dfe0a7c..d69bd7a 100644 --- a/src/components/dashboard/TimeRangeSelector.tsx +++ b/src/components/dashboard/TimeRangeSelector.tsx @@ -58,7 +58,7 @@ const renderLabel = ( } if (!props.range) { - return t("Last 24 Hours"); + return t("Last 24 Hours"); } if (props.range.name === "Custom Range") { diff --git a/src/components/dashboard/TopBlockedClients.tsx b/src/components/dashboard/TopBlockedClients.tsx index a879015..ecf3949 100644 --- a/src/components/dashboard/TopBlockedClients.tsx +++ b/src/components/dashboard/TopBlockedClients.tsx @@ -39,7 +39,7 @@ export const transformData = ( * @param t The translation function * @returns Rows of top blocked clients */ -export const generateRows = (t: i18next.TranslationFunction) => ( +export const generateRows = (t: i18next.TFunction) => ( data: TopBlockedClientsData ): ReactNode => { return data.topClients.map(item => { diff --git a/src/components/dashboard/TopBlockedDomains.tsx b/src/components/dashboard/TopBlockedDomains.tsx index 6e118f4..d658c02 100644 --- a/src/components/dashboard/TopBlockedDomains.tsx +++ b/src/components/dashboard/TopBlockedDomains.tsx @@ -39,7 +39,7 @@ export const transformData = ( * @param t the translation function * @returns {function(*): any[]} a function to generate rows of top blocked */ -export const generateRows = (t: i18next.TranslationFunction) => ( +export const generateRows = (t: i18next.TFunction) => ( data: TopBlockedDomainsData ) => { return data.topBlocked.map(item => { diff --git a/src/components/dashboard/TopClients.tsx b/src/components/dashboard/TopClients.tsx index bf789ac..03194ad 100644 --- a/src/components/dashboard/TopClients.tsx +++ b/src/components/dashboard/TopClients.tsx @@ -37,7 +37,7 @@ export const transformData = (data: ApiTopClients): TopClientsData => ({ * @param t the translation function * @returns {function(*): any[]} a function to generate rows of top clients */ -export const generateRows = (t: i18next.TranslationFunction) => ( +export const generateRows = (t: i18next.TFunction) => ( data: TopClientsData ) => { return data.topClients.map(item => { diff --git a/src/components/dashboard/TopDomains.tsx b/src/components/dashboard/TopDomains.tsx index 3bbca52..bc745e5 100644 --- a/src/components/dashboard/TopDomains.tsx +++ b/src/components/dashboard/TopDomains.tsx @@ -37,7 +37,7 @@ export const transformData = (data: ApiTopDomains): TopDomainsData => ({ * @param t the translation function * @returns {function(*): any[]} a function to generate rows of top domains */ -export const generateRows = (t: i18next.TranslationFunction) => ( +export const generateRows = (t: i18next.TFunction) => ( data: TopDomainsData ) => { return data.topDomains.map(item => { diff --git a/src/components/dashboard/__tests__/TopBlockedClients.test.tsx b/src/components/dashboard/__tests__/TopBlockedClients.test.tsx index 2e46c3d..2b0e178 100644 --- a/src/components/dashboard/__tests__/TopBlockedClients.test.tsx +++ b/src/components/dashboard/__tests__/TopBlockedClients.test.tsx @@ -35,7 +35,7 @@ it("transforms the API data correctly", async () => { }); it("creates an appropriately sized table", async () => { - const rows = generateRows(key => key)(transformData(fakeData)); + const rows = generateRows(global.t)(transformData(fakeData)); expect(rows).toHaveLength(fakeData.top_clients.length); }); diff --git a/src/components/dashboard/__tests__/TopBlockedDomains.test.tsx b/src/components/dashboard/__tests__/TopBlockedDomains.test.tsx index 8d133ec..63012cd 100644 --- a/src/components/dashboard/__tests__/TopBlockedDomains.test.tsx +++ b/src/components/dashboard/__tests__/TopBlockedDomains.test.tsx @@ -35,7 +35,7 @@ it("transforms the API data correctly", async () => { }); it("creates an appropriately sized table", async () => { - const rows = generateRows(key => key)(transformData(fakeData)); + const rows = generateRows(global.t)(transformData(fakeData)); expect(rows).toHaveLength(fakeData.top_domains.length); }); diff --git a/src/components/dashboard/__tests__/TopClients.test.tsx b/src/components/dashboard/__tests__/TopClients.test.tsx index 47cfc65..972c83d 100644 --- a/src/components/dashboard/__tests__/TopClients.test.tsx +++ b/src/components/dashboard/__tests__/TopClients.test.tsx @@ -35,7 +35,7 @@ it("transforms the API data correctly", async () => { }); it("creates an appropriately sized table", async () => { - const rows = generateRows(key => key)(transformData(fakeData)); + const rows = generateRows(global.t)(transformData(fakeData)); expect(rows).toHaveLength(fakeData.top_clients.length); }); diff --git a/src/components/dashboard/__tests__/TopDomains.test.tsx b/src/components/dashboard/__tests__/TopDomains.test.tsx index 6be5720..eaf203c 100644 --- a/src/components/dashboard/__tests__/TopDomains.test.tsx +++ b/src/components/dashboard/__tests__/TopDomains.test.tsx @@ -35,7 +35,7 @@ it("transforms the API data correctly", async () => { }); it("creates an appropriately sized table", async () => { - const rows = generateRows(key => key)(transformData(fakeData)); + const rows = generateRows(global.t)(transformData(fakeData)); expect(rows).toHaveLength(fakeData.top_domains.length); }); diff --git a/src/components/log/QueryLog.tsx b/src/components/log/QueryLog.tsx index ab11b26..f2d5c3d 100644 --- a/src/components/log/QueryLog.tsx +++ b/src/components/log/QueryLog.tsx @@ -47,7 +47,7 @@ export interface QueryLogState { * * @param t The translation function */ -const getDefaultRange = (t: i18next.TranslationFunction): TimeRange => { +const getDefaultRange = (t: i18next.TFunction): TimeRange => { const translatedDateRanges = dateRanges(t); const last24Hours = t("Last 24 Hours"); @@ -295,7 +295,7 @@ class QueryLog extends Component { * Convert a status code to a status message. The messages are translated, so * you must pass in the translation function before using the message array. */ -const status = (t: i18next.TranslationFunction) => [ +const status = (t: i18next.TFunction) => [ t("Unknown"), t("Blocked (gravity)"), t("Allowed (forwarded)"), @@ -309,7 +309,7 @@ const status = (t: i18next.TranslationFunction) => [ * Convert a DNSSEC code to a DNSSEC message. The messages are translated, so * you must pass in the translation function before using the message array. */ -const dnssec = (t: i18next.TranslationFunction) => [ +const dnssec = (t: i18next.TFunction) => [ "N/A", // Unspecified, which means DNSSEC is off, so nothing should be shown t("Secure"), t("Insecure"), @@ -331,7 +331,7 @@ const dnssecColor = [ * Convert a reply type code to a reply type. The unknown type is translated, so * you must pass in the translation function before using the message array. */ -const replyTypes = (t: i18next.TranslationFunction) => [ +const replyTypes = (t: i18next.TFunction) => [ t("Unknown"), "NODATA", "NXDOMAIN", @@ -358,7 +358,7 @@ const queryTypes = ["A", "AAAA", "ANY", "SRV", "SOA", "PTR", "TXT"]; */ const selectionFilter = ( items: string[], - t: i18next.TranslationFunction, + t: i18next.TFunction, extras: Array<{ name: string; value: any }> = [] ) => { return ({ @@ -392,7 +392,7 @@ const selectionFilter = ( * The columns of the Query Log. Some pieces are translated, so you must pass in * the translation function before using the columns. */ -const columns = (t: i18next.TranslationFunction) => [ +const columns = (t: i18next.TFunction) => [ { Header: t("Time"), id: "time", diff --git a/src/components/settings/ConditionalForwardingSettings.tsx b/src/components/settings/ConditionalForwardingSettings.tsx index 9bdc1eb..df00d47 100644 --- a/src/components/settings/ConditionalForwardingSettings.tsx +++ b/src/components/settings/ConditionalForwardingSettings.tsx @@ -23,7 +23,7 @@ export interface ConditionalForwardingSettingsProps { onUpdate: (settings: ConditionalForwardingObject) => void; isRouterIpValid: boolean; isDomainValid: boolean; - t: i18next.TranslationFunction; + t: i18next.TFunction; } const ConditionalForwardingSettings = ({ diff --git a/src/components/settings/DnsOptionSettings.tsx b/src/components/settings/DnsOptionSettings.tsx index 60a0bab..1e59ce8 100644 --- a/src/components/settings/DnsOptionSettings.tsx +++ b/src/components/settings/DnsOptionSettings.tsx @@ -23,7 +23,7 @@ export interface DnsOptionsObject { export interface DnsOptionSettingsProps { settings: DnsOptionsObject; onUpdate: (settings: DnsOptionsObject) => void; - t: i18next.TranslationFunction; + t: i18next.TFunction; } const DnsOptionSettings = ({ diff --git a/src/routes.tsx b/src/routes.tsx index 7999fd7..56d4421 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -22,9 +22,7 @@ import Preferences from "./views/Preferences"; import i18next from "i18next"; import { ComponentType } from "react"; -export const routes = ( - t: i18next.TranslationFunction -): { [key: string]: string } => ({ +export const routes = (t: i18next.TFunction): { [key: string]: string } => ({ "/dashboard": t("Dashboard"), "/query-log": t("Query Log"), "/whitelist": t("Whitelist"), diff --git a/src/setupTests.tsx b/src/setupTests.tsx index cb5e536..73e537e 100644 --- a/src/setupTests.tsx +++ b/src/setupTests.tsx @@ -14,17 +14,21 @@ import "jest-enzyme"; import api from "./util/api"; import fetchMock from "fetch-mock"; import "jest-localstorage-mock"; +import i18next from "i18next"; // Setup enzyme configure({ adapter: new Adapter() }); +// A fake translation function which returns its input as the output +global.t = ((key: string) => key) as i18next.TFunction; + // Mock out react-i18next jest.mock("react-i18next", () => ({ // This mock makes sure any components using the withNamespaces HoC receive the t function as a prop withNamespaces: () => (component: any) => { component.defaultProps = { ...component.defaultProps, - t: (key: string) => key + t: global.t }; return component; } diff --git a/src/types/global.d.ts b/src/types/global.d.ts index b1dfb0c..f7c201e 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -9,8 +9,11 @@ * Please see LICENSE file for your rights under this license. */ namespace NodeJS { + import i18next from "i18next"; + interface Global { tick(): Promise ignoreAPI(): Promise + t: i18next.TFunction } } diff --git a/src/util/dateRanges.tsx b/src/util/dateRanges.tsx index ac49699..d50424d 100644 --- a/src/util/dateRanges.tsx +++ b/src/util/dateRanges.tsx @@ -29,7 +29,7 @@ const epoch = moment(0); * @param t The translation function */ export const dateRanges: ( - t: i18next.TranslationFunction + t: i18next.TFunction ) => { [name: string]: [Moment, Moment] } = t => ({ [t("Last 24 Hours")]: [oneDayAgo, now], [t("Today")]: [startOfDay, now],