diff --git a/.gitignore b/.gitignore
index 2938463..1331fcd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,9 @@ dist
__pycache__
package-lock.json
.parcel-cache
+.pnpm-store/
+pnpm-lock.yaml
+.pytest_cache/
*.old
data/*.db*
diff --git a/Dockerfile b/Dockerfile
index 4ce9fa5..095aa68 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -32,6 +32,7 @@ ENV DATA_PATH=/data
ENV JETLOG_PORT=3000
ENV TOKEN_DURATION=7
ENV ENABLE_EXTERNAL_APIS=true
+ENV DEFAULT_LANGUAGE=en
ENV USE_IPV6=false
RUN mkdir -p ${APP_PATH}
diff --git a/README.md b/README.md
index 490944d..44a8ef0 100644
--- a/README.md
+++ b/README.md
@@ -40,10 +40,11 @@ services:
image: pbogre/jetlog:latest
volumes:
- /your/data/path:/data
- environment:
- JETLOG_PORT: 3000 # optional, default is 3000
- SECRET_KEY: yourLongAndRandomStringOfCharacters123!
- restart: unless-stopped
+ environment:
+ JETLOG_PORT: 3000 # optional, default is 3000
+ SECRET_KEY: yourLongAndRandomStringOfCharacters123!
+ DEFAULT_LANGUAGE: en # optional default UI language: en, zh-CN, zh-TW, ja, ko, fr, de, es
+ restart: unless-stopped
ports:
- 3000:3000
```
diff --git a/client/api.ts b/client/api.ts
index 0d17656..9510817 100644
--- a/client/api.ts
+++ b/client/api.ts
@@ -3,10 +3,15 @@ import TokenStorage from './storage/tokenStorage';
import { showToast } from './components/Toast';
const config = await fetch('./config').then((response) => response.json())
- .catch(() => ({ BASE_URL: '/', ENABLE_EXTERNAL_APIS: true }));
+ .catch(() => ({
+ BASE_URL: '/',
+ ENABLE_EXTERNAL_APIS: true,
+ DEFAULT_LANGUAGE: undefined,
+ }));
export const BASE_URL = config.BASE_URL == '/' ? '' : config.BASE_URL;
export const ENABLE_EXTERNAL_APIS = config.ENABLE_EXTERNAL_APIS;
+export const DEFAULT_LANGUAGE: string | undefined = config.DEFAULT_LANGUAGE ?? undefined;
// TODO improve this because there's a lot of repetition (get, post, delete are pretty much exactly the same)
// perhaps one method for each endpoint? i.e. API.getFlights(), ...
diff --git a/client/components/Toast.tsx b/client/components/Toast.tsx
index 76a3fec..7e63202 100644
--- a/client/components/Toast.tsx
+++ b/client/components/Toast.tsx
@@ -1,4 +1,5 @@
import React, { createContext, useContext, useState, useCallback, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
export type ToastType = 'success' | 'error' | 'info';
@@ -38,6 +39,8 @@ interface ToastItemProps {
}
function ToastItem({ toast, onRemove }: ToastItemProps) {
+ const { t } = useTranslation();
+
useEffect(() => {
const timer = setTimeout(() => {
onRemove(toast.id);
@@ -67,7 +70,7 @@ function ToastItem({ toast, onRemove }: ToastItemProps) {
diff --git a/client/components/flights/FlightDetail.tsx b/client/components/flights/FlightDetail.tsx
index 1baf558..9e9080b 100644
--- a/client/components/flights/FlightDetail.tsx
+++ b/client/components/flights/FlightDetail.tsx
@@ -2,6 +2,7 @@ import { useState, type FormEvent, lazy, Suspense } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { ArrowLeft, ArrowRight, Pencil, Trash2, X, Check } from 'lucide-react'
import { useQueryClient } from '@tanstack/react-query'
+import { useTranslation } from 'react-i18next'
import API from '@/api'
import {
@@ -40,6 +41,7 @@ const PURPOSES = ['', 'leisure', 'business', 'crew', 'other']
export function FlightDetail({ flightId }: FlightDetailProps) {
const navigate = useNavigate()
+ const { t } = useTranslation()
const metric = ConfigStorage.getSetting('metricUnits') !== 'false'
const localAirportTime = ConfigStorage.getSetting('localAirportTime') === 'true'
const { data: flight, isLoading } = useFlight(flightId)
@@ -113,7 +115,7 @@ export function FlightDetail({ flightId }: FlightDetailProps) {
}
const handleDelete = () => {
- if (!confirm('Delete this flight?')) return
+ if (!confirm(t('flightDetail.deleteConfirm'))) return
deleteMut.mutate(flightId, {
onSuccess: () => navigate('/flights'),
})
@@ -127,13 +129,13 @@ export function FlightDetail({ flightId }: FlightDetailProps) {
to="/flights"
className="board-label flex items-center gap-1.5 text-ink-muted hover:text-ink"
>
-
@@ -284,6 +290,8 @@ export function FlightDetail({ flightId }: FlightDetailProps) {
}
function AirportPanel({ title, airport }: { title: string; airport: any }) {
+ const { t } = useTranslation()
+
if (!airport || typeof airport === 'string') {
return (
- No data + {t('common.noData')}
) : (- No data + {t('common.noData')}
) : (No data
++ {t('common.noData')} +
) : (No data
++ {t('common.noData')} +
) : (- Type at least {minChars} characters + {t('common.minChars', { count: minChars })}
)} {loading && ( -Searching...
++ {t('common.searching')} +
)} {!loading && query.length >= minChars && options.length === 0 && ( -No results
++ {t('common.noResults')} +
)} {options.map((opt) => (- {showingCount > 0 ? `Showing ${start}–${end}` : 'No results'} + {showingCount > 0 + ? t('allFlights.showing', { start, end }) + : t('allFlights.noResults')}