feat(i18n): Implement multilingual support with Polish and English translations

- Added translation context and provider for managing language state.
- Integrated translation functionality into existing components (TaskStatusDropdown, Navigation).
- Created LanguageSwitcher component for language selection.
- Updated task statuses and navigation labels to use translations.
- Added Polish translations for various UI elements, including navigation, tasks, projects, and contracts.
- Refactored utility functions to return localized strings for deadlines and date formatting.
This commit is contained in:
Chop
2025-07-27 22:01:15 +02:00
parent 9b6307eabe
commit e828aa660b
16 changed files with 1166 additions and 234 deletions

View File

@@ -25,9 +25,9 @@ export default async function ProjectViewPage({ params }) {
<PageContainer>
<Card>
<CardContent className="text-center py-8">
<p className="text-red-600 text-lg">Project not found.</p>
<p className="text-red-600 text-lg">Projekt nie został znaleziony.</p>
<Link href="/projects" className="mt-4 inline-block">
<Button variant="primary">Back to Projects</Button>
<Button variant="primary">Powrót do projektów</Button>
</Link>
</CardContent>
</Card>
@@ -55,10 +55,10 @@ export default async function ProjectViewPage({ params }) {
{daysRemaining !== null && (
<Badge variant={getDeadlineVariant(daysRemaining)} size="md">
{daysRemaining === 0
? "Due Today"
? "Termin dzisiaj"
: daysRemaining > 0
? `${daysRemaining} days left`
: `${Math.abs(daysRemaining)} days overdue`}
? `${daysRemaining} dni pozostało`
: `${Math.abs(daysRemaining)} dni po terminie`}
</Badge>
)}
<Link href="/projects">
@@ -76,7 +76,7 @@ export default async function ProjectViewPage({ params }) {
d="M15 19l-7-7 7-7"
/>
</svg>
Back to Projects
Powrót do projektów
</Button>
</Link>
<Link href={`/projects/${id}/edit`}>
@@ -94,7 +94,7 @@ export default async function ProjectViewPage({ params }) {
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
Edit Project
Edytuj projekt
</Button>
</Link>
</div>
@@ -108,7 +108,7 @@ export default async function ProjectViewPage({ params }) {
{" "}
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900">
Project Information
Informacje o projekcie
</h2>
<Badge
variant={
@@ -123,12 +123,12 @@ export default async function ProjectViewPage({ params }) {
size="sm"
>
{project.project_type === "design"
? "Design (P)"
? "Projektowanie (P)"
: project.project_type === "construction"
? "Construction (R)"
? "Realizacja (R)"
: project.project_type === "design+construction"
? "Design + Construction (P+R)"
: "Unknown"}
? "Projektowanie + Realizacja (P+R)"
: "Nieznany"}
</Badge>
</div>
</CardHeader>
@@ -136,7 +136,7 @@ export default async function ProjectViewPage({ params }) {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Location
Lokalizacja
</span>
<p className="text-gray-900 font-medium">
{project.city || "N/A"}
@@ -144,7 +144,7 @@ export default async function ProjectViewPage({ params }) {
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Address
Adres
</span>
<p className="text-gray-900 font-medium">
{project.address || "N/A"}
@@ -152,7 +152,7 @@ export default async function ProjectViewPage({ params }) {
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Plot
Działka
</span>
<p className="text-gray-900 font-medium">
{project.plot || "N/A"}
@@ -160,7 +160,7 @@ export default async function ProjectViewPage({ params }) {
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
District
Dzielnica
</span>
<p className="text-gray-900 font-medium">
{project.district || "N/A"}
@@ -168,7 +168,7 @@ export default async function ProjectViewPage({ params }) {
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Unit
Jednostka
</span>
<p className="text-gray-900 font-medium">
{project.unit || "N/A"}
@@ -176,7 +176,7 @@ export default async function ProjectViewPage({ params }) {
</div>{" "}
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Deadline
Termin zakończenia
</span>
<p className="text-gray-900 font-medium">
{project.finish_date
@@ -194,7 +194,7 @@ export default async function ProjectViewPage({ params }) {
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Investment Number
Numer inwestycji
</span>
<p className="text-gray-900 font-medium">
{project.investment_number || "N/A"}
@@ -205,7 +205,7 @@ export default async function ProjectViewPage({ params }) {
{project.contact && (
<div className="border-t pt-4">
<span className="text-sm font-medium text-gray-500 block mb-1">
Contact
Kontakt
</span>
<p className="text-gray-900 font-medium">{project.contact}</p>
</div>
@@ -214,7 +214,7 @@ export default async function ProjectViewPage({ params }) {
{project.coordinates && (
<div className="border-t pt-4">
<span className="text-sm font-medium text-gray-500 block mb-1">
Coordinates
Współrzędne
</span>
<p className="text-gray-900 font-medium font-mono text-sm">
{formatCoordinates(project.coordinates)}
@@ -237,14 +237,14 @@ export default async function ProjectViewPage({ params }) {
<Card>
<CardHeader>
<h2 className="text-xl font-semibold text-gray-900">
Contract Details
Szczegóły umowy
</h2>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Contract Number
Numer umowy
</span>
<p className="text-gray-900 font-medium">
{project.contract_number || "N/A"}
@@ -252,7 +252,7 @@ export default async function ProjectViewPage({ params }) {
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Contract Name
Nazwa umowy
</span>
<p className="text-gray-900 font-medium">
{project.contract_name || "N/A"}
@@ -260,7 +260,7 @@ export default async function ProjectViewPage({ params }) {
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Customer
Klient
</span>
<p className="text-gray-900 font-medium">
{project.customer || "N/A"}
@@ -268,7 +268,7 @@ export default async function ProjectViewPage({ params }) {
</div>
<div>
<span className="text-sm font-medium text-gray-500 block mb-1">
Investor
Inwestor
</span>
<p className="text-gray-900 font-medium">
{project.investor || "N/A"}
@@ -284,21 +284,21 @@ export default async function ProjectViewPage({ params }) {
<Card>
<CardHeader>
<h2 className="text-lg font-semibold text-gray-900">
Project Status
Status projektu
</h2>
</CardHeader>
<CardContent className="space-y-4">
{" "}
<div>
<span className="text-sm font-medium text-gray-500 block mb-2">
Current Status
Aktualny status
</span>
<ProjectStatusDropdown project={project} size="md" />
</div>
{daysRemaining !== null && (
<div className="border-t pt-4">
<span className="text-sm font-medium text-gray-500 block mb-2">
Timeline
Harmonogram
</span>
<div className="text-center">
<Badge
@@ -306,10 +306,10 @@ export default async function ProjectViewPage({ params }) {
size="lg"
>
{daysRemaining === 0
? "Due Today"
? "Termin dzisiaj"
: daysRemaining > 0
? `${daysRemaining} days remaining`
: `${Math.abs(daysRemaining)} days overdue`}
? `${daysRemaining} dni pozostało`
: `${Math.abs(daysRemaining)} dni po terminie`}
</Badge>
</div>
</div>
@@ -321,7 +321,7 @@ export default async function ProjectViewPage({ params }) {
<Card>
<CardHeader>
<h2 className="text-lg font-semibold text-gray-900">
Quick Actions
Szybkie akcje
</h2>
</CardHeader>
<CardContent className="space-y-3">
@@ -344,7 +344,7 @@ export default async function ProjectViewPage({ params }) {
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
Edit Project
Edytuj projekt
</Button>
</Link>{" "}
<Link href="/projects" className="block">
@@ -366,7 +366,7 @@ export default async function ProjectViewPage({ params }) {
d="M15 19l-7-7 7-7"
/>
</svg>
Back to Projects
Powrót do projektów
</Button>
</Link>
<Link href="/projects/map" className="block">
@@ -388,7 +388,7 @@ export default async function ProjectViewPage({ params }) {
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"
/>
</svg>
View All on Map
Zobacz wszystkie na mapie
</Button>
</Link>
</CardContent>
@@ -404,7 +404,7 @@ export default async function ProjectViewPage({ params }) {
{" "}
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900">
Project Location
Lokalizacja projektu
</h2>
{project.coordinates && (
<Link
@@ -428,7 +428,7 @@ export default async function ProjectViewPage({ params }) {
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"
/>
</svg>
View on Full Map
Zobacz na pełnej mapie
</Button>
</Link>
)}
@@ -454,7 +454,7 @@ export default async function ProjectViewPage({ params }) {
{/* Notes Section */}
<Card>
<CardHeader>
<h2 className="text-xl font-semibold text-gray-900">Notes</h2>
<h2 className="text-xl font-semibold text-gray-900">Notatki</h2>
</CardHeader>
<CardContent>
<div className="mb-6">
@@ -475,10 +475,10 @@ export default async function ProjectViewPage({ params }) {
</svg>
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">
No notes yet
Brak notatek
</h3>
<p className="text-gray-500">
Add your first note using the form above.
Dodaj swoją pierwszą notatkę używając formularza powyżej.
</p>
</div>
) : (

View File

@@ -11,8 +11,10 @@ import PageHeader from "@/components/ui/PageHeader";
import SearchBar from "@/components/ui/SearchBar";
import { LoadingState } from "@/components/ui/States";
import { formatDate } from "@/lib/utils";
import { useTranslation } from "@/lib/i18n";
export default function ProjectListPage() {
const { t } = useTranslation();
const [projects, setProjects] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const [filteredProjects, setFilteredProjects] = useState([]);
@@ -45,7 +47,7 @@ export default function ProjectListPage() {
}, [searchTerm, projects]);
async function handleDelete(id) {
const confirmed = confirm("Are you sure you want to delete this project?");
const confirmed = confirm(t('projects.deleteConfirm'));
if (!confirmed) return;
const res = await fetch(`/api/projects/${id}`, {
@@ -61,7 +63,7 @@ export default function ProjectListPage() {
};
return (
<PageContainer>
<PageHeader title="Projects" description="Manage and track your projects">
<PageHeader title={t('projects.title')} description={t('projects.subtitle')}>
<div className="flex gap-2">
<Link href="/projects/map">
<Button variant="outline" size="lg">
@@ -78,7 +80,7 @@ export default function ProjectListPage() {
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"
/>
</svg>
Map View
Widok mapy
</Button>
</Link>
<Link href="/projects/new">
@@ -96,7 +98,7 @@ export default function ProjectListPage() {
d="M12 4v16m8-8H4"
/>
</svg>
Add Project
{t('projects.newProject')}
</Button>
</Link>
</div>
@@ -105,9 +107,9 @@ export default function ProjectListPage() {
<SearchBar
searchTerm={searchTerm}
onSearchChange={handleSearchChange}
placeholder="Search by project name, WP, plot, or investment number..."
placeholder={t('projects.searchPlaceholder')}
resultsCount={filteredProjects.length}
resultsText="projects"
resultsText="projektów"
/>
{filteredProjects.length === 0 && searchTerm ? (
<Card>
@@ -126,14 +128,13 @@ export default function ProjectListPage() {
</svg>
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">
No projects found
{t('common.noResults')}
</h3>
<p className="text-gray-500 mb-6">
No projects match your search criteria. Try adjusting your search
terms.
Brak projektów pasujących do kryteriów wyszukiwania. Spróbuj zmienić wyszukiwane frazy.
</p>
<Button variant="outline" onClick={() => setSearchTerm("")}>
Clear Search
Wyczyść wyszukiwanie
</Button>
</CardContent>
</Card>
@@ -154,13 +155,13 @@ export default function ProjectListPage() {
</svg>
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">
No projects yet
{t('projects.noProjects')}
</h3>
<p className="text-gray-500 mb-6">
Get started by creating your first project
{t('projects.noProjectsMessage')}
</p>
<Link href="/projects/new">
<Button variant="primary">Create First Project</Button>
<Button variant="primary">Utwórz pierwszy projekt</Button>
</Link>
</CardContent>
</Card>
@@ -170,34 +171,34 @@ export default function ProjectListPage() {
<thead>
<tr className="bg-gray-100 border-b">
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-32">
No.
Nr.
</th>
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700">
Project Name
{t('projects.projectName')}
</th>
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-40">
WP
</th>
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
City
{t('projects.city')}
</th>
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-40">
Address
{t('projects.address')}
</th>
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
Plot
{t('projects.plot')}
</th>
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-24">
Finish
{t('projects.finishDate')}
</th>
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-12">
Type
Typ
</th>
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-24">
Status
</th>
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
Actions
Akcje
</th>
</tr>
</thead>
@@ -282,7 +283,7 @@ export default function ProjectListPage() {
size="sm"
className="text-xs px-2 py-1"
>
View
Wyświetl
</Button>
</Link>
</td>