Skip to content

Commit 48f113c

Browse files
committed
feat: Added multi language support
- Added multi language support - Added german translation - Added prettier config - Improved useStorage hook - Moved navbar code to separate component
1 parent a4828e2 commit 48f113c

23 files changed

+704
-141
lines changed

.prettierrc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"trailingComma": "es5",
3+
"tabWidth": 2,
4+
"semi": true,
5+
"singleQuote": false,
6+
"printWidth": 200
7+
}

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,16 @@
2828
- Remember and forget issue (not assigned to you)
2929
- Pin and unpin issue
3030
- View time entries overview
31+
- Multiple languages
3132
- Dark & light mode (system default)
3233

34+
# Supported languages
35+
36+
- English
37+
- German
38+
39+
> If you want to add more languages or extend existing ones, feel free to contribute. Just create a pull request with the desired changes. The language files are located under [src/lang](src/lang).
40+
3341
# Screenshots
3442

3543
![issues](screenshots/issues-dark.png)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"react": "^18.2.0",
3535
"react-dom": "^18.2.0",
3636
"react-flatpickr": "^3.10.13",
37+
"react-intl": "^6.4.4",
3738
"react-router-dom": "^6.14.2",
3839
"react-tooltip": "^5.18.0",
3940
"tailwind-merge": "^1.14.0",

pnpm-lock.yaml

Lines changed: 110 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/App.tsx

Lines changed: 24 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { faGear, faList, faStopwatch } from "@fortawesome/free-solid-svg-icons";
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3-
import clsx from "clsx";
43
import { Suspense, lazy } from "react";
5-
import { Link, Navigate, Route, Routes, useLocation } from "react-router-dom";
4+
import { useIntl } from "react-intl";
5+
import { Navigate, Route, Routes } from "react-router-dom";
6+
import Navbar from "./components/general/Navbar";
67
import Toast from "./components/general/Toast";
78

89
const IssuesPage = lazy(() => import("./pages/IssuesPage"));
910
const SettingsPage = lazy(() => import("./pages/SettingsPage"));
1011
const TimePage = lazy(() => import("./pages/TimePage"));
1112

1213
function App() {
13-
const location = useLocation();
14+
const { formatMessage } = useIntl();
1415

1516
return (
1617
<div
@@ -19,52 +20,25 @@ function App() {
1920
e.preventDefault();
2021
}}
2122
>
22-
<nav className="sticky top-0 z-10 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-1">
23-
<ul className="flex flex-wrap gap-x-2 -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
24-
<li>
25-
<Link
26-
to="/issues"
27-
className={clsx(
28-
"inline-flex items-center gap-x-1 p-2 rounded-t-lg",
29-
location.pathname === "/issues" ? "text-primary-600 border-b-2 border-primary-600 dark:text-primary-500 dark:border-primary-500" : "border-b-2 border-transparent hover:text-gray-600 hover:border-gray-30 dark:hover:text-gray-300",
30-
"focus:ring-2 focus:ring-primary-300 focus:outline-none dark:focus:ring-primary-600 select-none"
31-
)}
32-
tabIndex={-1}
33-
>
34-
<FontAwesomeIcon icon={faList} />
35-
Issues
36-
</Link>
37-
</li>
38-
<li>
39-
<Link
40-
to="/time"
41-
className={clsx(
42-
"inline-flex items-center gap-x-1 p-2 rounded-t-lg",
43-
location.pathname === "/time" ? "text-primary-600 border-b-2 border-primary-600 dark:text-primary-500 dark:border-primary-500" : "border-b-2 border-transparent hover:text-gray-600 hover:border-gray-30 dark:hover:text-gray-300",
44-
"focus:ring-2 focus:ring-primary-300 focus:outline-none dark:focus:ring-primary-600 select-none"
45-
)}
46-
tabIndex={-1}
47-
>
48-
<FontAwesomeIcon icon={faStopwatch} />
49-
Time
50-
</Link>
51-
</li>
52-
<li>
53-
<Link
54-
to="/settings"
55-
className={clsx(
56-
"inline-flex items-center gap-x-1 p-2 rounded-t-lg",
57-
location.pathname === "/settings" ? "text-primary-600 border-b-2 border-primary-600 dark:text-primary-500 dark:border-primary-500" : "border-b-2 border-transparent hover:text-gray-600 hover:border-gray-30 dark:hover:text-gray-300",
58-
"focus:ring-2 focus:ring-primary-300 focus:outline-none dark:focus:ring-primary-600 select-none"
59-
)}
60-
tabIndex={-1}
61-
>
62-
<FontAwesomeIcon icon={faGear} />
63-
Settings
64-
</Link>
65-
</li>
66-
</ul>
67-
</nav>
23+
<Navbar
24+
navigation={[
25+
{
26+
href: "/issues",
27+
icon: <FontAwesomeIcon icon={faList} />,
28+
name: formatMessage({ id: "nav.tabs.issues" }),
29+
},
30+
{
31+
href: "/time",
32+
icon: <FontAwesomeIcon icon={faStopwatch} />,
33+
name: formatMessage({ id: "nav.tabs.time" }),
34+
},
35+
{
36+
href: "/settings",
37+
icon: <FontAwesomeIcon icon={faGear} />,
38+
name: formatMessage({ id: "nav.tabs.settings" }),
39+
},
40+
]}
41+
/>
6842
<main className="h-[500px] overflow-y-auto p-2">
6943
<Routes>
7044
<Route index element={<Navigate to="/issues" replace />} />
@@ -94,7 +68,7 @@ function App() {
9468
}
9569
/>
9670

97-
<Route path="*" element={<Toast type="error" message="Page not found!" allowClose={false} />} />
71+
<Route path="*" element={<Toast type="error" message={formatMessage({ id: "nav.error.page-not-found" })} allowClose={false} />} />
9872
</Routes>
9973
</main>
10074
</div>

src/IntlProvider.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { setDefaultOptions } from "date-fns";
2+
import { de as dateFnsLocalDE, enUS as dateFnsLocalEN } from "date-fns/locale";
3+
import React from "react";
4+
import { IntlProvider as ReactIntlProvider } from "react-intl";
5+
import useSettings from "./hooks/useSettings";
6+
import messagesDE from "./lang/de.json";
7+
import messagesEN from "./lang/en.json";
8+
9+
type Language = "de" | "en";
10+
// eslint-disable-next-line react-refresh/only-export-components
11+
export const LANGUAGES: Language[] = ["de", "en"];
12+
13+
type PropTypes = {
14+
children: React.ReactNode;
15+
};
16+
17+
const IntlProvider = ({ children }: PropTypes) => {
18+
const { settings } = useSettings();
19+
20+
let locale: Language;
21+
if (settings.language === "browser") {
22+
locale = LANGUAGES.find((lang) => navigator.language === lang || navigator.language.startsWith(`${lang}-`)) ?? "en";
23+
} else {
24+
locale = LANGUAGES.find((lang) => settings.language === lang) ?? "en";
25+
}
26+
27+
let messages;
28+
let dateFnsLocal;
29+
switch (locale) {
30+
case "de":
31+
messages = messagesDE;
32+
dateFnsLocal = dateFnsLocalDE;
33+
break;
34+
case "en":
35+
messages = messagesEN;
36+
dateFnsLocal = dateFnsLocalEN;
37+
break;
38+
}
39+
40+
setDefaultOptions({
41+
locale: dateFnsLocal,
42+
});
43+
44+
return (
45+
<ReactIntlProvider locale={locale} messages={messages}>
46+
{children}
47+
</ReactIntlProvider>
48+
);
49+
};
50+
51+
export default IntlProvider;

src/components/general/KBD.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
import clsx from "clsx";
2+
import React from "react";
23

34
type PropTypes = {
4-
text: string;
5+
children: React.ReactNode;
56
space?: "md" | "xl";
67
className?: string;
78
};
89

9-
const KBD = ({ text, space = "md", className }: PropTypes) => {
10-
return <kbd className={clsx(space === "xl" ? "px-2" : "px-1", "mx-1 py-0.5 text-xs text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500", className)}>{text}</kbd>;
10+
const KBD = ({ children, space = "md", className }: PropTypes) => {
11+
return (
12+
<kbd
13+
className={clsx(
14+
space === "xl" ? "px-2" : "px-1",
15+
"mx-1 py-0.5 text-xs text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500",
16+
className
17+
)}
18+
>
19+
{children}
20+
</kbd>
21+
);
1122
};
1223

1324
// eslint-disable-next-line react-refresh/only-export-components

0 commit comments

Comments
 (0)