Skip to content

Commit

Permalink
feat(i18n): create button for toggle language
Browse files Browse the repository at this point in the history
  • Loading branch information
LaercioSR committed Aug 24, 2024
1 parent 45c358d commit 1f5737e
Show file tree
Hide file tree
Showing 15 changed files with 1,362 additions and 438 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = {
],
extends: [
"eslint:recommended",
"next",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:chai-friendly/recommended",
Expand All @@ -37,5 +38,7 @@ module.exports = {
"no-only-tests/no-only-tests": "error",
"no-console": ["error", { allow: ["warn", "error"] }],
"react/no-unknown-property": ["error", { ignore: ["jsx", "global"] }],
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
},
};
1,516 changes: 1,091 additions & 425 deletions package-lock.json

Large diffs are not rendered by default.

25 changes: 19 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,31 @@
"lint": "next lint"
},
"dependencies": {
"next": "14.2.5",
"i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"moment": "^2.30.1",
"next": "14.2.6",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"react-i18next": "^15.0.1"
},
"devDependencies": {
"@types/node": "^20",
"@svgr/webpack": "^8.1.0",
"@types/node": "^22.5.0",
"@types/react": "^18",
"@types/react-dom": "^18",
"@svgr/webpack": "^8.1.0",
"@typescript-eslint/eslint-plugin": "^8.2.0",
"@typescript-eslint/parser": "^8.2.0",
"eslint": "^8",
"eslint-config-next": "14.2.5",
"eslint-config-next": "14.2.6",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-chai-friendly": "^1.0.1",
"eslint-plugin-cypress": "^3.5.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.32.2",
"prettier": "^3.3.3",
"typescript": "^5"
}
}
}
2 changes: 1 addition & 1 deletion src/@types/svgr.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ declare module "*.svg" {
}

declare module "*.svg?url" {
const content: any;
const content: string;
export default content;
}
6 changes: 5 additions & 1 deletion src/app/components/ButtonCustom/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
}
}

.global-button:first-child svg > path {
.global-button svg > path {
fill: var(--primary);
}

.global-button:hover svg > path {
fill: var(--secondary);
}
6 changes: 5 additions & 1 deletion src/app/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import LanguageSwitch from "../LanguageSwitch";
import styles from "./styles.module.css";
import dynamic from "next/dynamic";

Expand All @@ -11,7 +12,10 @@ export default function Header() {
<div className={styles["nav-wrapper"]}>
<ul className={styles["header-side_left"]}></ul>
<ul className={styles["header-side_right"]}>
<li className={styles["header-side-item"]}>
<li>
<LanguageSwitch />
</li>
<li>
<ThemeSwitch />
</li>
</ul>
Expand Down
6 changes: 2 additions & 4 deletions src/app/components/Header/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
justify-content: flex-start;
align-items: center;
width: 100%;
gap: 1rem;
}

.header-side_right {
Expand All @@ -32,8 +33,5 @@
align-items: center;
width: 100%;
list-style: none;
}

.header-side-item {
margin-left: 1rem;
gap: 1rem;
}
44 changes: 44 additions & 0 deletions src/app/components/LanguageSwitch/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import ButtonCustom from "../ButtonCustom";
import LanguageSvg from "../../assets/icons/language.svg";
import React from "react";
import styles from "./styles.module.css";
import { changeLanguage } from "i18next";
import { useTranslation } from "react-i18next";
import Typography from "../Typography";
import i18next from "i18next";

export default function LanguageSwitch() {
const [isOpen, setIsOpen] = React.useState(false);
const { t } = useTranslation();
const currentLanguage = i18next.language;

function handleClick() {
setIsOpen(!isOpen);
}

function handleToggleLanguage(lng: "en" | "pt") {
changeLanguage(lng);
}

return (
<div>
<ButtonCustom icon={<LanguageSvg />} onClick={handleClick} />
{isOpen && (
<ul className={styles["language-switch__list"]}>
<li
className={`${styles["language-switch__item"]} ${currentLanguage === "en" && styles["language-switch__item--active"]}`}
onClick={() => handleToggleLanguage("en")}
>
<Typography variant="body2">{t("languages.en.label")}</Typography>
</li>
<li
className={`${styles["language-switch__item"]} ${currentLanguage === "pt" && styles["language-switch__item--active"]}`}
onClick={() => handleToggleLanguage("pt")}
>
<Typography variant="body2">{t("languages.pt.label")}</Typography>
</li>
</ul>
)}
</div>
);
}
27 changes: 27 additions & 0 deletions src/app/components/LanguageSwitch/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.language-switch__list {
list-style-type: none;
display: flex;
flex-direction: column;
position: absolute;
}

.language-switch__item {
padding: 0.25rem 0.5rem;
border-top: 0.1rem solid var(--background);
cursor: pointer;
background-color: var(--primary);
color: var(--background);
transition: background-color 0.2s ease-in-out;
}

.language-switch__item:hover {
background-color: var(--secondary);
}

.language-switch__item:first-child {
border-top: none;
}

.language-switch__item--active {
background-color: var(--tertiary);
}
31 changes: 31 additions & 0 deletions src/app/components/Typography/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import styles from "./styles.module.css";

interface TypographyProps {
variant:
| "h1"
| "h2"
| "h3"
| "h4"
| "body1"
| "body2"
| "body3"
| "caption1"
| "caption2";
children: React.ReactNode;
}

export default function Typography({ variant, children }: TypographyProps) {
return (
<>
{variant === "h1" && <h1 className={styles.headline1}>{children}</h1>}
{variant === "h2" && <h2 className={styles.headline2}>{children}</h2>}
{variant === "h3" && <h3 className={styles.headline3}>{children}</h3>}
{variant === "h4" && <h4 className={styles.headline4}>{children}</h4>}
{variant === "body1" && <p className={styles.body1}>{children}</p>}
{variant === "body2" && <p className={styles.body2}>{children}</p>}
{variant === "body3" && <p className={styles.body3}>{children}</p>}
{variant === "caption1" && <p className={styles.caption1}>{children}</p>}
{variant === "caption2" && <p className={styles.caption2}>{children}</p>}
</>
);
}
53 changes: 53 additions & 0 deletions src/app/components/Typography/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.headline1 {
font-size: 3rem;
font-weight: 700;
line-height: 120%;
}

.headline2 {
font-size: 2.5rem;
font-weight: 700;
line-height: 120%;
}

.headline3 {
font-size: 2rem;
font-weight: 700;
line-height: 120%;
}

.headline4 {
font-size: 1.5rem;
font-weight: 700;
line-height: 120%;
}

.body1 {
font-size: 1rem;
font-weight: 700;
line-height: 120%;
}

.body2 {
font-size: 1rem;
font-weight: 400;
line-height: 120%;
}

.body3 {
font-size: 0.875rem;
font-weight: 400;
line-height: 120%;
}

.caption1 {
font-size: 0.75rem;
font-weight: 700;
line-height: 120%;
}

.caption2 {
font-size: 0.75rem;
font-weight: 400;
line-height: 120%;
}
60 changes: 60 additions & 0 deletions src/app/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import moment from "moment";
import "moment/dist/locale/pt";

import pt from "./locales/pt.json";
import en from "./locales/en.json";

const options = {
// Order and from where user language should be detected.
order: ["querystring", "localStorage", "navigator", "path"],

// Keys or params to lookup language from:
lookupQuerystring: "lng",
lookupLocalStorage: "i18nextLng",
lookupFromPathIndex: 0,

// Cache user language on:
caches: ["localStorage"],

// Only detect languages that are in the whitelist.
checkWhitelist: true,
};

i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
pt: { translation: pt },
en: { translation: en },
},
fallbackLng: ["en"],
supportedLngs: ["pt", "en"],
detection: options,

interpolation: {
escapeValue: false,
// Format items to each language format.
format(value, format, lng) {
if (format === "number" && value) return value.toLocaleString(lng);

if (format === "roundNumber" && value) {
value = Math.round(value);
return value.toLocaleString(lng);
}

if (value instanceof Date) {
return moment(value)
.locale(lng || "")
.format(format);
}

return value;
},
},
});

export default i18n;
10 changes: 10 additions & 0 deletions src/app/i18n/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"languages": {
"en": {
"label": "English"
},
"pt": {
"label": "Portuguese"
}
}
}
10 changes: 10 additions & 0 deletions src/app/i18n/locales/pt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"languages": {
"en": {
"label": "Inglês"
},
"pt": {
"label": "Português"
}
}
}
1 change: 1 addition & 0 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import styles from "./page.module.css";
import Header from "./components/Header";
import "./i18n";

export default function Home() {
return (
Expand Down

0 comments on commit 1f5737e

Please sign in to comment.