diff --git a/examples/test-vitest/.gitignore b/examples/test-vitest/.gitignore new file mode 100644 index 00000000..472010b4 --- /dev/null +++ b/examples/test-vitest/.gitignore @@ -0,0 +1,35 @@ +# compiled output +/dist +/node_modules + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json diff --git a/examples/test-vitest/@types/i18next.d.ts b/examples/test-vitest/@types/i18next.d.ts new file mode 100644 index 00000000..4bb27ff4 --- /dev/null +++ b/examples/test-vitest/@types/i18next.d.ts @@ -0,0 +1,18 @@ +/** + * If you want to enable locale keys typechecking and enhance IDE experience. + * + * Requires `resolveJsonModule:true` in your tsconfig.json. + * + * @link https://www.i18next.com/overview/typescript + */ +import 'i18next' + +// resources.ts file is generated with `npm run toc` +import resources from './resources.ts' + +declare module 'i18next' { + interface CustomTypeOptions { + defaultNS: 'common' + resources: typeof resources + } +} diff --git a/examples/test-vitest/@types/resources.ts b/examples/test-vitest/@types/resources.ts new file mode 100644 index 00000000..a955d5c6 --- /dev/null +++ b/examples/test-vitest/@types/resources.ts @@ -0,0 +1,11 @@ +import common from '../public/locales/en/common.json'; +import footer from '../public/locales/en/footer.json'; +import secondpage from '../public/locales/en/second-page.json'; + +const resources = { + common, + footer, + 'second-page': secondpage +} as const; + +export default resources; diff --git a/examples/test-vitest/components/Footer.test.tsx b/examples/test-vitest/components/Footer.test.tsx new file mode 100644 index 00000000..e8b8ace6 --- /dev/null +++ b/examples/test-vitest/components/Footer.test.tsx @@ -0,0 +1,26 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { Footer } from "./Footer"; + +vi.mock("next-i18next", () => ({ + Trans: ({ children }: { children: React.ReactNode }) => <>{children}>, + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +vi.mock("next-i18next/package.json", () => ({ + default: { version: "1.0.0" }, +})); + +describe("Footer", () => { + it("renders correctly", () => { + render(); + + expect(screen.getByText("description")).toBeInTheDocument(); + expect(screen.getByText("next-i18next v1.0.0")).toBeInTheDocument(); + expect(screen.getByText(/With using/)).toBeInTheDocument(); + expect(screen.getByText("locize")).toBeInTheDocument(); + expect(screen.getByText("i18next")).toBeInTheDocument(); + }); +}); diff --git a/examples/test-vitest/components/Footer.tsx b/examples/test-vitest/components/Footer.tsx new file mode 100644 index 00000000..a7427ed7 --- /dev/null +++ b/examples/test-vitest/components/Footer.tsx @@ -0,0 +1,33 @@ +import { Trans, useTranslation } from "next-i18next"; +import pkg from "next-i18next/package.json"; +import type { FC } from "react"; + +export const Footer: FC = () => { + const { t } = useTranslation("footer"); + + return ( + + ); +}; diff --git a/examples/test-vitest/components/Header.test.tsx b/examples/test-vitest/components/Header.test.tsx new file mode 100644 index 00000000..2bb0aea0 --- /dev/null +++ b/examples/test-vitest/components/Header.test.tsx @@ -0,0 +1,25 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { Header } from "./Header"; + +vi.mock("next/head", () => ({ + default: ({ children }: { children: React.ReactNode }) => <>{children}>, +})); + +describe("Header", () => { + it("renders correctly", () => { + const props = { + heading: "Test Heading", + title: "Test Title", + }; + + render(); + + expect(screen.getByText("next-i18next")).toBeInTheDocument(); + expect(screen.getByText(props.heading)).toBeInTheDocument(); + expect(screen.getByRole("link", { name: "" })).toHaveAttribute( + "href", + "//github.com/i18next/next-i18next", + ); + }); +}); diff --git a/examples/test-vitest/components/Header.tsx b/examples/test-vitest/components/Header.tsx new file mode 100644 index 00000000..7fa7a378 --- /dev/null +++ b/examples/test-vitest/components/Header.tsx @@ -0,0 +1,24 @@ +import Head from "next/head"; +import Link from "next/link"; +import type { FC } from "react"; + +type Props = { + heading: string; + title: string; +}; + +export const Header: FC = ({ heading, title }) => ( + <> + + {title} + + + next-i18next + + + {heading} + + + + > +); diff --git a/examples/test-vitest/next-env.d.ts b/examples/test-vitest/next-env.d.ts new file mode 100644 index 00000000..4f11a03d --- /dev/null +++ b/examples/test-vitest/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/test-vitest/next-i18next.config.js b/examples/test-vitest/next-i18next.config.js new file mode 100644 index 00000000..95aa3db0 --- /dev/null +++ b/examples/test-vitest/next-i18next.config.js @@ -0,0 +1,28 @@ +// @ts-check + +/** + * @type {import('next-i18next').UserConfig} + */ +module.exports = { + // https://www.i18next.com/overview/configuration-options#logging + debug: process.env.NODE_ENV === 'development', + i18n: { + defaultLocale: 'en', + locales: ['en', 'de'], + }, + /** To avoid issues when deploying to some paas (vercel...) */ + localePath: + typeof window === 'undefined' + ? require('path').resolve('./public/locales') + : '/locales', + + reloadOnPrerender: process.env.NODE_ENV === 'development', + + /** + * @link https://github.com/i18next/next-i18next#6-advanced-configuration + */ + // saveMissing: false, + // strictMode: true, + // serializeConfig: false, + // react: { useSuspense: false } +} diff --git a/examples/test-vitest/next-utils.config.js b/examples/test-vitest/next-utils.config.js new file mode 100644 index 00000000..3a056e23 --- /dev/null +++ b/examples/test-vitest/next-utils.config.js @@ -0,0 +1,26 @@ +const pc = require('picocolors') + +const nextUtilsConfig = () => { + const trueEnv = ['true', '1', 'yes'] + const esmExternals = trueEnv.includes( + process.env?.NEXTJS_ESM_EXTERNALS ?? 'false' + ) + const tsconfigPath = process.env.NEXTJS_TSCONFIG_PATH + ? process.env.NEXTJS_TSCONFIG_PATH + : './tsconfig.json' + + // eslint-disable-next-line no-console + console.warn( + `${pc.green('warn -')} experimental.esmExternals is ${ + esmExternals ? 'enabled' : 'disabled' + }` + ) + return { + esmExternals, + tsconfigPath, + } +} + +module.exports = { + loadCustomBuildParams: nextUtilsConfig, +} diff --git a/examples/test-vitest/next.config.js b/examples/test-vitest/next.config.js new file mode 100644 index 00000000..bbef0063 --- /dev/null +++ b/examples/test-vitest/next.config.js @@ -0,0 +1,21 @@ +// @ts-check +const { i18n } = require('./next-i18next.config.js') + +// You can remove the following 2 lines when integrating our example. +const { loadCustomBuildParams } = require('./next-utils.config') +const { esmExternals = false, tsconfigPath } = + loadCustomBuildParams() + +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + esmExternals, // https://nextjs.org/blog/next-11-1#es-modules-support + }, + i18n, + reactStrictMode: true, + typescript: { + tsconfigPath, + }, +} + +module.exports = nextConfig diff --git a/examples/test-vitest/package.json b/examples/test-vitest/package.json new file mode 100644 index 00000000..6fd6fb13 --- /dev/null +++ b/examples/test-vitest/package.json @@ -0,0 +1,45 @@ +{ + "name": "next-i18next-example-test-vitest", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start -p ${PORT:=3000}", + "typecheck": "tsc --project ./tsconfig.json --noEmit", + "clean": "rimraf .next", + "nuke:install": "rimraf ./node_modules ./package-lock.json", + "toc": "i18next-resources-for-ts toc -i ./public/locales/en -o ./@types/resources.ts", + "merge": "i18next-resources-for-ts merge -i ./public/locales/en -o ./@types/resources.json", + "interface": "i18next-resources-for-ts interface -i ./public/locales/en -o ./@types/resources.d.ts", + "test": "vitest", + "coverage": "vitest run --coverage" + }, + "dependencies": { + "i18next": "23.11.5", + "next": "^14.2.4", + "next-i18next": "^15.3.0", + "react": "^18.3.1", + "react-i18next": "^14.1.2", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.4.8", + "@testing-library/react": "^16.0.0", + "@types/node": "^20.14.6", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "@vitest/coverage-v8": "^2.0.4", + "eslint-config-next": "^14.2.4", + "i18next-resources-for-ts": "1.5.0", + "jsdom": "^24.1.1", + "picocolors": "^1.0.1", + "rimraf": "^5.0.7", + "typescript": "^5.4.5", + "vitest": "^2.0.4", + "whatwg-fetch": "^3.6.20" + }, + "trustedDependencies": ["core-js"] +} diff --git a/examples/test-vitest/pages/_app.tsx b/examples/test-vitest/pages/_app.tsx new file mode 100644 index 00000000..f26cdebb --- /dev/null +++ b/examples/test-vitest/pages/_app.tsx @@ -0,0 +1,10 @@ +import { appWithTranslation } from "next-i18next"; +import type { AppProps } from "next/app"; +// import nextI18NextConfig from '../next-i18next.config.js' + +const MyApp = ({ Component, pageProps }: AppProps) => ( + +); + +// https://github.com/i18next/next-i18next#unserializable-configs +export default appWithTranslation(MyApp /*, nextI18NextConfig */); diff --git a/examples/test-vitest/pages/_document.tsx b/examples/test-vitest/pages/_document.tsx new file mode 100644 index 00000000..f3cc17eb --- /dev/null +++ b/examples/test-vitest/pages/_document.tsx @@ -0,0 +1,45 @@ +import Document, { Html, Head, Main, NextScript } from "next/document"; +import type { DocumentProps } from "next/document"; +import i18nextConfig from "../next-i18next.config"; + +type Props = DocumentProps & { + // add custom document props +}; + +class MyDocument extends Document { + render() { + const currentLocale = + this.props.__NEXT_DATA__.locale ?? i18nextConfig.i18n.defaultLocale; + return ( + + + + + + + + + + + + + + + ); + } +} + +export default MyDocument; diff --git a/examples/test-vitest/pages/index.test.tsx b/examples/test-vitest/pages/index.test.tsx new file mode 100644 index 00000000..d515ebcf --- /dev/null +++ b/examples/test-vitest/pages/index.test.tsx @@ -0,0 +1,64 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import Homepage from "./index"; + +const mockPush = vi.fn(); +const mockChangeLanguage = vi.fn(); + +vi.mock("next/router", () => ({ + useRouter: () => ({ + asPath: "/", + locale: "en", + pathname: "/", + push: mockPush, + query: {}, + }), +})); + +vi.mock("next-i18next", () => ({ + Trans: ({ children }: { children: React.ReactNode }) => <>{children}>, + useTranslation: () => ({ + i18n: { + changeLanguage: mockChangeLanguage, + }, + t: (key: string) => key, + }), +})); + +vi.mock("../components/Footer", () => ({ + Footer: () => , +})); + +vi.mock("../components/Header", () => ({ + Header: () => , +})); + +describe("Homepage", () => { + it("renders correctly", () => { + render(); + + expect(screen.getByTestId("header")).toBeInTheDocument(); + expect(screen.getByTestId("footer")).toBeInTheDocument(); + expect(screen.getByText("blog.appDir.question")).toBeInTheDocument(); + expect(screen.getByText("blog.optimized.question")).toBeInTheDocument(); + expect(screen.getByText("blog.ssg.question")).toBeInTheDocument(); + expect(screen.getAllByText("change-locale")).toHaveLength(3); + expect(screen.getByText("to-second-page")).toBeInTheDocument(); + }); + + it("changes language when clicking the change locale button", () => { + render(); + const changeLocaleButtons = screen.getAllByText("change-locale"); + fireEvent.click(changeLocaleButtons[1]); // Click the second button (index 1) + expect(mockPush).toHaveBeenCalledWith({ pathname: "/", query: {} }, "/", { + locale: "de", + }); + }); + + it("changes language on client side", () => { + render(); + const changeLocaleButtons = screen.getAllByText("change-locale"); + fireEvent.click(changeLocaleButtons[2]); // Click the third button (index 2) + expect(mockChangeLanguage).toHaveBeenCalledWith("de"); + }); +}); diff --git a/examples/test-vitest/pages/index.tsx b/examples/test-vitest/pages/index.tsx new file mode 100644 index 00000000..69ce5f64 --- /dev/null +++ b/examples/test-vitest/pages/index.tsx @@ -0,0 +1,122 @@ +import type { GetStaticProps, InferGetStaticPropsType } from "next"; +import Link from "next/link"; +import { useRouter } from "next/router"; + +import { Trans, useTranslation } from "next-i18next"; +import { serverSideTranslations } from "next-i18next/serverSideTranslations"; + +import { Footer } from "../components/Footer"; +import { Header } from "../components/Header"; + +// type Props = { +// // Add custom props here +// }; + +const Homepage = (_props: InferGetStaticPropsType) => { + const router = useRouter(); + const { t, i18n } = useTranslation("common"); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const onToggleLanguageClick = (newLocale: string) => { + const { pathname, asPath, query } = router; + router.push({ pathname, query }, asPath, { locale: newLocale }); + }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const clientSideLanguageChange = (newLocale: string) => { + i18n.changeLanguage(newLocale); + }; + + const changeTo = router.locale === "en" ? "de" : "en"; + // const changeTo = i18n.resolvedLanguage === 'en' ? 'de' : 'en' + + return ( + <> + + + + + {t("blog.appDir.question")} + + + Then check out + this blog post. + + + + + + + + + {t("blog.optimized.question")} + + + Then you may have a look at + this blog post. + + + + + + + + {t("blog.ssg.question")} + + + Then you may have a look at + this blog post. + + + + + + + + + + + {t("change-locale", { changeTo })} + + alternative language change without using Link component + onToggleLanguageClick(changeTo)}> + {t("change-locale", { changeTo })} + + alternative language change without using Link component, but this + will change language only on client side + clientSideLanguageChange(changeTo)} + > + {t("change-locale", { changeTo })} + + + {t("to-second-page")} + + + + + > + ); +}; + +// or getServerSideProps: GetServerSideProps = async ({ locale }) +export const getStaticProps: GetStaticProps = async ({ locale }) => ({ + props: { + ...(await serverSideTranslations(locale ?? "en", ["common", "footer"])), + }, +}); + +export default Homepage; diff --git a/examples/test-vitest/pages/second-page.test.tsx b/examples/test-vitest/pages/second-page.test.tsx new file mode 100644 index 00000000..81a3f90d --- /dev/null +++ b/examples/test-vitest/pages/second-page.test.tsx @@ -0,0 +1,55 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import SecondPage, { getServerSideProps } from "./second-page"; + +vi.mock("next-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +vi.mock("next-i18next/serverSideTranslations", () => ({ + serverSideTranslations: vi.fn().mockResolvedValue({ _nextI18Next: {} }), +})); + +vi.mock("../components/Footer", () => ({ + Footer: () => , +})); + +vi.mock("../components/Header", () => ({ + Header: () => , +})); + +describe("SecondPage", () => { + it("renders correctly", () => { + render(); + + expect(screen.getByTestId("header")).toBeInTheDocument(); + expect(screen.getByTestId("footer")).toBeInTheDocument(); + expect(screen.getByText("second-page:back-to-home")).toBeInTheDocument(); + }); +}); + +describe("getServerSideProps", () => { + it("returns correct props", async () => { + const context = { locale: "en" }; + const result = await getServerSideProps(context as any); + + expect(result).toEqual({ + props: { + _nextI18Next: {}, + }, + }); + }); + + it("uses default locale when not provided", async () => { + const context = {}; + const result = await getServerSideProps(context as any); + + expect(result).toEqual({ + props: { + _nextI18Next: {}, + }, + }); + }); +}); diff --git a/examples/test-vitest/pages/second-page.tsx b/examples/test-vitest/pages/second-page.tsx new file mode 100644 index 00000000..53b660e6 --- /dev/null +++ b/examples/test-vitest/pages/second-page.tsx @@ -0,0 +1,41 @@ +import type { GetServerSideProps, InferGetServerSidePropsType } from "next"; +import Link from "next/link"; + +import { useTranslation } from "next-i18next"; +import { serverSideTranslations } from "next-i18next/serverSideTranslations"; + +import { Footer } from "../components/Footer"; +import { Header } from "../components/Header"; + +// type Props = { +// // Add custom props here +// } + +const SecondPage = ( + _props: InferGetServerSidePropsType, +) => { + const { t } = useTranslation(["common", "second-page"]); + + return ( + <> + + + + {t("second-page:back-to-home")} + + + + > + ); +}; + +export const getServerSideProps: GetServerSideProps = async ({ locale }) => ({ + props: { + ...(await serverSideTranslations(locale ?? "en", [ + "second-page", + "footer", + ])), + }, +}); + +export default SecondPage; diff --git a/examples/test-vitest/public/app.css b/examples/test-vitest/public/app.css new file mode 100644 index 00000000..61378a55 --- /dev/null +++ b/examples/test-vitest/public/app.css @@ -0,0 +1,185 @@ +#__next { + font-family: 'Open Sans', sans-serif; + text-align: center; + background-image: linear-gradient( + to left top, + #ffffff, + #f5f5f5, + #eaeaea, + #e0e0e0, + #d6d6d6 + ); + display: flex; + flex-direction: column; + margin: 0; + min-height: 100vh; + min-width: 100vw; +} + +* body { + overflow-x: hidden; /* Hide x-axis overflow */ +} + +h1, +h2 { + font-family: 'Oswald', sans-serif; +} + +h1 { + font-size: 3rem; + margin: 5rem 0; +} +h2 { + min-width: 18rem; + font-size: 2rem; + opacity: 0.3; +} +h3 { + font-size: 1.5rem; + opacity: 0.5; +} + +.mainBox { + display: grid; + gap: 20px; + width: 90%; + grid-template-columns: 1fr; + grid-auto-rows: auto; +} + +.card { + background-color: #fff; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + overflow: hidden; + text-align: center; + box-sizing: border-box; + transition: box-shadow 0.3s ease; /* Smooth transition for the hover effect */ + display: grid; + grid-template-rows: auto 1fr auto; +} + +.card:hover { + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); /* Increased shadow on hover */ +} + +.card-title { + padding-top: 11px; + min-height: 70px; + font-size: 1.5em; + margin: 0 0 10px; +} + +.card-text { + font-size: 1em; + color: #666; + margin-bottom: 15px; + padding: 0 10px; /* Add padding for text inside the card */ +} + +.card-text a { + color: #007bff; + text-decoration: none; +} + +.card-text a:hover { + text-decoration: underline; +} + +.card-img { + width: 100%; /* Full width of the parent div */ + height: auto; /* Maintain aspect ratio */ + border-radius: 0 0 10px 10px; /* Rounded corners on the bottom only */ + display: block; /* Prevent extra space below the image */ + transition: + transform 0.5s ease, + box-shadow 0.5s ease; /* Smooth transition for hover effects with longer duration */ +} + +.card-img:hover { + transform: scale(1.05); /* Slightly scale up the image */ + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); /* Add a subtle shadow */ +} + +/* Media queries for responsive design */ +@media (min-width: 600px) { + .mainBox { + grid-template-columns: repeat( + 2, + 1fr + ); /* Show 2 cards per row on medium screens */ + } +} + +@media (min-width: 900px) { + .mainBox { + grid-template-columns: repeat( + 3, + 1fr + ); /* Show 3 cards per row on large screens */ + } +} + +p { + line-height: 1.65em; +} +p:nth-child(2) { + font-style: italic; + opacity: 0.65; + margin-top: 1rem; +} + +a.github { + position: fixed; + top: 0.5rem; + right: 0.75rem; + font-size: 4rem; + color: #888; + opacity: 0.8; +} +a.github:hover { + opacity: 1; +} + +button { + display: inline-block; + vertical-align: bottom; + outline: 0; + text-decoration: none; + cursor: pointer; + background-color: rgba(255, 255, 255, 0.5); + box-sizing: border-box; + font-size: 1em; + font-family: inherit; + border-radius: 3px; + transition: box-shadow 0.2s ease; + user-select: none; + line-height: 2.5em; + min-height: 40px; + padding: 0 0.8em; + border: 0; + border-radius: 5px; + color: inherit; + position: relative; + transform: translateZ(0); + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); + margin: 0.8rem; +} + +button:hover, +button:focus { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4); +} + +main { + display: flex; + flex-direction: column; + flex: 1; + justify-content: center; + align-items: center; +} +footer { + background-color: rgba(255, 255, 255, 0.5); + width: 100vw; + padding: 3rem 0; +} diff --git a/examples/test-vitest/public/locales/de/common.json b/examples/test-vitest/public/locales/de/common.json new file mode 100644 index 00000000..e5cfc9d5 --- /dev/null +++ b/examples/test-vitest/public/locales/de/common.json @@ -0,0 +1,25 @@ +{ + "h1": "Ein einfaches Beispiel", + "change-locale": "Sprache wechseln zu \"{{changeTo}}\"", + "to-second-page": "Zur zweiten Seite", + "error-with-status": "Auf dem Server ist ein Fehler ({{statusCode}}) aufgetreten", + "error-without-status": "Auf dem Server ist ein Fehler aufgetreten", + "title": "Hauptseite | next-i18next", + "blog": { + "appDir": { + "question": "Verwendest du das neue Next.js 13/14 mit app directory?", + "answer": "Dann schau dir <1>diesen Blogbeitrag1> an.", + "link": "https://locize.com/blog/next-app-dir-i18n/" + }, + "optimized": { + "question": "Möchtest du einige Superkräfte entfesseln, um für alle Seiten optimierte Übersetzungen zu haben?", + "answer": "Dann schaue dir vielleicht <1>diesen Blogbeitrag1> an.", + "link": "https://locize.com/blog/next-i18next-de/" + }, + "ssg": { + "question": "Möchtest du SSG (next export) verwenden?", + "answer": "Dann schaue dir vielleicht <1>diesen Blogbeitrag1> an.", + "link": "https://locize.com/blog/next-i18n-statisch/" + } + } +} diff --git a/examples/test-vitest/public/locales/de/footer.json b/examples/test-vitest/public/locales/de/footer.json new file mode 100644 index 00000000..2848ba9d --- /dev/null +++ b/examples/test-vitest/public/locales/de/footer.json @@ -0,0 +1,4 @@ +{ + "description": "Dies ist eine Nicht-Seitenkomponente, die einen eigenen Namespace erfordert", + "helpLocize": "Wenn Sie <1>locize1> einsetzen, unterstützen Sie direkt die Zukunft von <3>i18next3>." +} diff --git a/examples/test-vitest/public/locales/de/second-page.json b/examples/test-vitest/public/locales/de/second-page.json new file mode 100644 index 00000000..73bb73f2 --- /dev/null +++ b/examples/test-vitest/public/locales/de/second-page.json @@ -0,0 +1,5 @@ +{ + "h1": "Eine zweite Seite, um das Routing zu demonstrieren", + "back-to-home": "Zurück zur Hauptseite", + "title": "Zweite Seite | next-i18next" +} diff --git a/examples/test-vitest/public/locales/en/common.json b/examples/test-vitest/public/locales/en/common.json new file mode 100644 index 00000000..24797b4b --- /dev/null +++ b/examples/test-vitest/public/locales/en/common.json @@ -0,0 +1,25 @@ +{ + "h1": "A simple example", + "change-locale": "Change locale to \"{{changeTo}}\"", + "to-second-page": "To second page", + "error-with-status": "A {{statusCode}} error occurred on server", + "error-without-status": "An error occurred on the server", + "title": "Home | next-i18next", + "blog": { + "appDir": { + "question": "Are you using the new Next.js 13/14 app directory?", + "answer": "Then check out <1>this blog post1>.", + "link": "https://locize.com/blog/next-app-dir-i18n/" + }, + "optimized": { + "question": "Do you like to unleash some super powers to have all side optimized translations?", + "answer": "Then you may have a look at <1>this blog post1>.", + "link": "https://locize.com/blog/next-i18next/" + }, + "ssg": { + "question": "Do you want to use SSG (next export)?", + "answer": "Then you may have a look at <1>this blog post1>.", + "link": "https://locize.com/blog/next-i18n-static/" + } + } +} diff --git a/examples/test-vitest/public/locales/en/footer.json b/examples/test-vitest/public/locales/en/footer.json new file mode 100644 index 00000000..59c4bde9 --- /dev/null +++ b/examples/test-vitest/public/locales/en/footer.json @@ -0,0 +1,4 @@ +{ + "description": "This is a non-page component that requires its own namespace", + "helpLocize": "With using <1>locize1> you directly support the future of <3>i18next3>." +} diff --git a/examples/test-vitest/public/locales/en/second-page.json b/examples/test-vitest/public/locales/en/second-page.json new file mode 100644 index 00000000..64d0decc --- /dev/null +++ b/examples/test-vitest/public/locales/en/second-page.json @@ -0,0 +1,5 @@ +{ + "h1": "A second page, to demonstrate routing", + "back-to-home": "Back to home", + "title": "Second page | next-i18next" +} diff --git a/examples/test-vitest/tsconfig.json b/examples/test-vitest/tsconfig.json new file mode 100644 index 00000000..8b2f04cb --- /dev/null +++ b/examples/test-vitest/tsconfig.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "esnext", + "lib": ["dom", "dom.iterable", "esnext"], + "baseUrl": ".", + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "**/*.cjs", + "**/*.mjs" + ], + "exclude": ["**/node_modules", "**/.*/"] +} diff --git a/examples/test-vitest/vercel.json b/examples/test-vitest/vercel.json new file mode 100644 index 00000000..7ae9a3de --- /dev/null +++ b/examples/test-vitest/vercel.json @@ -0,0 +1,5 @@ +{ + "github": { + "silent": true + } +} diff --git a/examples/test-vitest/vitest.config.ts b/examples/test-vitest/vitest.config.ts new file mode 100644 index 00000000..a4a680f4 --- /dev/null +++ b/examples/test-vitest/vitest.config.ts @@ -0,0 +1,34 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [react()], + test: { + coverage: { + exclude: [ + "**/_app.tsx", + "**/_document.tsx", + "coverage/**", + "dist/**", + "**/[.]**", + "packages/*/test?(s)/**", + "**/*.d.ts", + "**/virtual:*", + "**/__x00__*", + "**/\x00*", + "cypress/**", + "test?(s)/**", + "test?(-*).?(c|m)[jt]s?(x)", + "**/*{.,-}{test,spec}?(-d).?(c|m)[jt]s?(x)", + "**/__tests__/**", + "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*", + "**/vitest.{workspace,projects}.[jt]s?(on)", + "**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}", + ], + include: ["components/**/*", "pages/**/*"], + provider: "v8", + }, + environment: "jsdom", + setupFiles: ["./vitest.setup.ts"], + }, +}); diff --git a/examples/test-vitest/vitest.setup.ts b/examples/test-vitest/vitest.setup.ts new file mode 100644 index 00000000..fc612935 --- /dev/null +++ b/examples/test-vitest/vitest.setup.ts @@ -0,0 +1,10 @@ +import matchers from "@testing-library/jest-dom/matchers"; +import "@testing-library/jest-dom/vitest"; +import { cleanup } from "@testing-library/react"; +import { afterEach, expect } from "vitest"; + +expect.extend({ ...matchers }); + +afterEach(() => { + cleanup(); +}); diff --git a/jest.config.js b/jest.config.js index 2b80e966..b19bba80 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,15 +1,16 @@ module.exports = { - automock: false, - collectCoverage: true, - collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], - moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - createClient: '/src/createClient/browser.ts', - }, - rootDir: '.', - testPathIgnorePatterns: [ - '/.next/', - '/dist/', - '/node_modules/', - ], -} + automock: false, + collectCoverage: true, + collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}"], + moduleDirectories: ["node_modules", "src"], + moduleNameMapper: { + createClient: "/src/createClient/browser.ts", + }, + rootDir: ".", + testPathIgnorePatterns: [ + "/.next/", + "/dist/", + "/node_modules/", + "/examples/test-vitest/", + ], +}; diff --git a/package.json b/package.json index 8f3af647..1bfb41ac 100644 --- a/package.json +++ b/package.json @@ -143,5 +143,9 @@ "next": ">= 12.0.0", "react": ">= 17.0.2", "react-i18next": ">= 13.5.0" - } + }, + "trustedDependencies": [ + "core-js", + "core-js-pure" + ] }
+ + Then check out + this blog post. + +
+ + Then you may have a look at + this blog post. + +