Compare commits

..

7 Commits

Author SHA1 Message Date
sss3978 80fc37222d Merge 73fb48e3bb into 3ada075b1a 2026-04-23 10:19:24 +00:00
sss3978 73fb48e3bb Update index.test.ts 2026-04-23 19:19:20 +09:00
sss3978 d30132197e Update client/src/i18n/translations/ja.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-22 23:00:56 +09:00
sss3978 9bf4220054 Update ja.ts 2026-04-22 21:59:16 +09:00
sss3978 0d0ab5080c Update supportedLanguages.ts 2026-04-22 21:56:52 +09:00
sss3978 1084d40685 Update TranslationContext.tsx 2026-04-22 21:55:47 +09:00
sss3978 75ef928264 Create ja.ts 2026-04-22 21:54:10 +09:00
22 changed files with 2459 additions and 183 deletions
+1 -1
View File
@@ -23,4 +23,4 @@ jobs:
- name: Publish to GitHub wiki
uses: Andrew-Chen-Wang/github-wiki-action@v5
with:
strategy: clone
strategy: init
+2 -2
View File
@@ -1,5 +1,5 @@
apiVersion: v2
name: trek
version: 3.0.2
version: 2.9.14
description: Minimal Helm chart for TREK app
appVersion: "3.0.2"
appVersion: "2.9.14"
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "trek-client",
"version": "3.0.2",
"version": "2.9.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "trek-client",
"version": "3.0.2",
"version": "2.9.14",
"dependencies": {
"@react-pdf/renderer": "^4.3.2",
"axios": "^1.6.7",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "trek-client",
"version": "3.0.2",
"version": "2.9.14",
"private": true,
"type": "module",
"scripts": {
+3 -2
View File
@@ -15,6 +15,7 @@ import ar from './translations/ar'
import br from './translations/br'
import cs from './translations/cs'
import pl from './translations/pl'
import ja from './translations/ja'
import { SUPPORTED_LANGUAGES, SupportedLanguageCode } from './supportedLanguages'
export { SUPPORTED_LANGUAGES }
@@ -23,7 +24,7 @@ type TranslationStrings = Record<string, string | { name: string; category: stri
// Keyed by SupportedLanguageCode so TypeScript enforces all languages have a translation.
const translations: Record<SupportedLanguageCode, TranslationStrings> = {
de, en, es, fr, hu, it, ru, zh, 'zh-TW': zhTw, nl, id, ar, br, cs, pl,
de, en, es, fr, hu, it, ru, zh, 'zh-TW': zhTw, nl, id, ar, br, cs, pl, ja,
}
// Derived from SUPPORTED_LANGUAGES — add new languages there, not here.
@@ -38,7 +39,7 @@ export function getLocaleForLanguage(language: string): string {
export function getIntlLanguage(language: string): string {
if (language === 'br') return 'pt-BR'
return ['de', 'es', 'fr', 'hu', 'it', 'ru', 'zh', 'zh-TW', 'nl', 'ar', 'cs', 'pl', 'id'].includes(language) ? language : 'en'
return ['de', 'es', 'fr', 'hu', 'it', 'ru', 'zh', 'zh-TW', 'nl', 'ar', 'cs', 'pl', 'id', 'ja' ].includes(language) ? language : 'en'
}
export function isRtlLanguage(language: string): boolean {
+2 -1
View File
@@ -12,8 +12,9 @@ export const SUPPORTED_LANGUAGES = [
{ value: 'zh', label: '简体中文', locale: 'zh-CN' },
{ value: 'zh-TW', label: '繁體中文', locale: 'zh-TW' },
{ value: 'it', label: 'Italiano', locale: 'it-IT' },
{ value: 'ar', label: 'العربية', locale: 'ar-SA' },
{ value: 'ar', label: 'العربية', locale: 'ar-SA' },
{ value: 'id', label: 'Bahasa Indonesia', locale: 'id-ID' },
{ value: 'ja', label: '日本語', locale: 'ja-JP' },
] as const
export type SupportedLanguageCode = typeof SUPPORTED_LANGUAGES[number]['value']
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -91,7 +91,7 @@ describe('isRtlLanguage', () => {
describe('SUPPORTED_LANGUAGES', () => {
it('FE-COMP-I18N-009: contains expected entries with value/label shape', () => {
expect(Array.isArray(SUPPORTED_LANGUAGES)).toBe(true)
expect(SUPPORTED_LANGUAGES).toHaveLength(15)
expect(SUPPORTED_LANGUAGES).toHaveLength(16)
expect(SUPPORTED_LANGUAGES).toContainEqual(expect.objectContaining({ value: 'en', label: 'English' }))
expect(SUPPORTED_LANGUAGES).toContainEqual(expect.objectContaining({ value: 'ar', label: 'العربية' }))
})
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "trek-server",
"version": "3.0.2",
"version": "2.9.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "trek-server",
"version": "3.0.2",
"version": "2.9.14",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.28.0",
"archiver": "^6.0.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "trek-server",
"version": "3.0.2",
"version": "2.9.14",
"main": "src/index.ts",
"scripts": {
"start": "node --import tsx src/index.ts",
-64
View File
@@ -2043,70 +2043,6 @@ function runMigrations(db: Database.Database): void {
db.exec('CREATE INDEX IF NOT EXISTS idx_journey_entry_photos_entry ON journey_entry_photos(entry_id)');
db.exec('CREATE INDEX IF NOT EXISTS idx_journey_entry_photos_photo ON journey_entry_photos(journey_photo_id)');
},
// Migration 122: Correct stale day_id / end_day_id on non-transport
// reservations. Migration 110 only backfilled transport types; tours,
// restaurants, events and "other" bookings kept a stale day_id from
// older code paths that often defaulted to the first day of the trip.
// Starting with v3.0.0 the planner renders reservations by day_id
// instead of reservation_time, so those stale rows show up on the
// wrong day. This migration nulls out day_id / end_day_id values that
// don't match the reservation's time and then backfills them from
// reservation_time / reservation_end_time.
() => {
db.exec(`
UPDATE reservations
SET day_id = NULL
WHERE reservation_time IS NOT NULL
AND day_id IS NOT NULL
AND type != 'hotel'
AND NOT EXISTS (
SELECT 1 FROM days d
WHERE d.id = reservations.day_id
AND d.date = substr(reservations.reservation_time, 1, 10)
)
`);
db.exec(`
UPDATE reservations
SET end_day_id = NULL
WHERE reservation_end_time IS NOT NULL
AND end_day_id IS NOT NULL
AND type != 'hotel'
AND NOT EXISTS (
SELECT 1 FROM days d
WHERE d.id = reservations.end_day_id
AND d.date = substr(reservations.reservation_end_time, 1, 10)
)
`);
db.exec(`
UPDATE reservations
SET day_id = (
SELECT d.id FROM days d
WHERE d.trip_id = reservations.trip_id
AND d.date = substr(reservations.reservation_time, 1, 10)
LIMIT 1
)
WHERE type != 'hotel'
AND reservation_time IS NOT NULL
AND day_id IS NULL
`);
db.exec(`
UPDATE reservations
SET end_day_id = (
SELECT d.id FROM days d
WHERE d.trip_id = reservations.trip_id
AND d.date = substr(reservations.reservation_end_time, 1, 10)
LIMIT 1
)
WHERE type != 'hotel'
AND reservation_end_time IS NOT NULL
AND end_day_id IS NULL
AND substr(reservations.reservation_end_time, 1, 10)
!= substr(reservations.reservation_time, 1, 10)
`);
},
];
if (currentVersion < migrations.length) {
+1 -1
View File
@@ -143,7 +143,7 @@ export async function discover(issuer: string, discoveryUrl?: string | null): Pr
// Validate that the discovery doc's issuer matches the operator-configured
// one. A MITM or compromised doc could otherwise supply a crafted issuer
// that passes jwt.verify() because we used doc.issuer as the expected value.
if (doc.issuer && doc.issuer.replace(/\/+$/, '') !== issuer) {
if (doc.issuer && doc.issuer !== issuer) {
throw new Error(`OIDC discovery issuer mismatch: expected "${issuer}", got "${doc.issuer}"`);
}
doc._issuer = url;
+7 -67
View File
@@ -43,24 +43,6 @@ function loadEndpoints(reservationId: number): ReservationEndpoint[] {
).all(reservationId) as ReservationEndpoint[];
}
// Resolve the day row whose date matches the date portion of an ISO-ish
// timestamp. Used to keep `day_id` / `end_day_id` in sync with
// `reservation_time` / `reservation_end_time` so non-transport bookings
// (tours, restaurants, events, ...) end up on the right day in the UI,
// which now filters by day_id instead of reservation_time.
function resolveDayIdFromTime(
tripId: string | number,
time: string | null | undefined,
): number | null {
if (!time) return null;
const datePart = time.slice(0, 10);
if (!/^\d{4}-\d{2}-\d{2}$/.test(datePart)) return null;
const row = db
.prepare('SELECT id FROM days WHERE trip_id = ? AND date = ? LIMIT 1')
.get(tripId, datePart) as { id: number } | undefined;
return row?.id ?? null;
}
const saveEndpoints = db.transaction((reservationId: number, endpoints: EndpointInput[]) => {
db.prepare('DELETE FROM reservation_endpoints WHERE reservation_id = ?').run(reservationId);
const insert = db.prepare(`
@@ -178,26 +160,13 @@ export function createReservation(tripId: string | number, data: CreateReservati
}
}
// Derive day_id / end_day_id from reservation_time when the client
// didn't explicitly set them (non-hotel bookings only — hotels store
// their date range on the linked day_accommodation).
const resolvedType = type || 'other';
let resolvedDayId: number | null = day_id ?? null;
if (resolvedDayId == null && resolvedType !== 'hotel' && reservation_time) {
resolvedDayId = resolveDayIdFromTime(tripId, reservation_time);
}
let resolvedEndDayId: number | null = end_day_id ?? null;
if (resolvedEndDayId == null && resolvedType !== 'hotel' && reservation_end_time) {
resolvedEndDayId = resolveDayIdFromTime(tripId, reservation_end_time);
}
const result = db.prepare(`
INSERT INTO reservations (trip_id, day_id, end_day_id, place_id, assignment_id, title, reservation_time, reservation_end_time, location, confirmation_number, notes, status, type, accommodation_id, metadata, needs_review)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
tripId,
resolvedDayId,
resolvedEndDayId,
day_id || null,
end_day_id ?? null,
place_id || null,
assignment_id || null,
title,
@@ -207,7 +176,7 @@ export function createReservation(tripId: string | number, data: CreateReservati
confirmation_number || null,
notes || null,
status || 'pending',
resolvedType,
type || 'other',
resolvedAccommodationId,
metadata ? JSON.stringify(metadata) : null,
needs_review ? 1 : 0
@@ -321,35 +290,6 @@ export function updateReservation(id: string | number, tripId: string | number,
}
}
const resolvedType = (type ?? current.type) || 'other';
const nextReservationTime = resolvedType === 'hotel'
? null
: (reservation_time !== undefined ? (reservation_time || null) : current.reservation_time);
const nextReservationEndTime = resolvedType === 'hotel'
? null
: (reservation_end_time !== undefined ? (reservation_end_time || null) : current.reservation_end_time);
// day_id / end_day_id: honour an explicit value from the client,
// otherwise derive from the (possibly updated) reservation_time so the
// planner renders the booking on the correct day.
let nextDayId: number | null;
if (day_id !== undefined) {
nextDayId = day_id || null;
} else if (reservation_time !== undefined && resolvedType !== 'hotel') {
nextDayId = resolveDayIdFromTime(tripId, nextReservationTime);
} else {
nextDayId = current.day_id ?? null;
}
let nextEndDayId: number | null;
if (end_day_id !== undefined) {
nextEndDayId = end_day_id ?? null;
} else if (reservation_end_time !== undefined && resolvedType !== 'hotel') {
nextEndDayId = resolveDayIdFromTime(tripId, nextReservationEndTime);
} else {
nextEndDayId = (current as any).end_day_id ?? null;
}
db.prepare(`
UPDATE reservations SET
title = COALESCE(?, title),
@@ -370,13 +310,13 @@ export function updateReservation(id: string | number, tripId: string | number,
WHERE id = ?
`).run(
title || null,
nextReservationTime,
nextReservationEndTime,
(type ?? current.type) === 'hotel' ? null : (reservation_time !== undefined ? (reservation_time || null) : current.reservation_time),
(type ?? current.type) === 'hotel' ? null : (reservation_end_time !== undefined ? (reservation_end_time || null) : current.reservation_end_time),
location !== undefined ? (location || null) : current.location,
confirmation_number !== undefined ? (confirmation_number || null) : current.confirmation_number,
notes !== undefined ? (notes || null) : current.notes,
nextDayId,
nextEndDayId,
day_id !== undefined ? (day_id || null) : current.day_id,
end_day_id !== undefined ? (end_day_id ?? null) : (current as any).end_day_id ?? null,
place_id !== undefined ? (place_id || null) : current.place_id,
assignment_id !== undefined ? (assignment_id || null) : current.assignment_id,
status || null,
@@ -84,9 +84,8 @@ describe('GET /api/system-notices/active', () => {
it('returns empty array for non-first-login user with no applicable notices', async () => {
const { user } = createUser(testDb);
// login_count > 1 means firstLogin condition does not match for any notice;
// first_seen_version >= 3.0.0 means existingUserBeforeVersion('3.0.0') also does not match
testDb.prepare('UPDATE users SET login_count = 5, first_seen_version = ? WHERE id = ?').run('3.0.0', user.id);
// login_count > 1 means firstLogin condition does not match for any notice
testDb.prepare('UPDATE users SET login_count = 5 WHERE id = ?').run(user.id);
const res = await request(app)
.get('/api/system-notices/active')
.set('Cookie', authCookie(user.id));
@@ -123,7 +122,7 @@ describe('GET /api/system-notices/active', () => {
SYSTEM_NOTICES.push(TEST_NOTICE);
try {
const { user } = createUser(testDb);
testDb.prepare('UPDATE users SET login_count = 5, first_seen_version = ? WHERE id = ?').run('3.0.0', user.id);
testDb.prepare('UPDATE users SET login_count = 5 WHERE id = ?').run(user.id);
const res = await request(app)
.get('/api/system-notices/active')
+7 -7
View File
@@ -48,7 +48,7 @@ Verified in `server/src/config.ts` (line 107):
## HTTPS / Proxy
These three variables work together behind a TLS-terminating reverse proxy. See [Reverse-Proxy](Reverse-Proxy) for the full explanation.
These three variables work together behind a TLS-terminating reverse proxy. See [Reverse-Proxy] for the full explanation.
| Variable | Description | Default |
|---|---|---|
@@ -62,7 +62,7 @@ These three variables work together behind a TLS-terminating reverse proxy. See
## OIDC / SSO
For setup instructions, see [OIDC-SSO](OIDC-SSO).
For setup instructions, see [OIDC-SSO].
| Variable | Description | Default |
|---|---|---|
@@ -110,7 +110,7 @@ Both variables must be set together. If either is omitted, the account is create
## MCP
For setup instructions, see [MCP-Overview](MCP-Overview).
For setup instructions, see [MCP-Overview].
| Variable | Description | Default |
|---|---|---|
@@ -129,7 +129,7 @@ For setup instructions, see [MCP-Overview](MCP-Overview).
## Related Pages
- [Reverse-Proxy](Reverse-Proxy) — HTTPS proxy setup and the `FORCE_HTTPS` / `TRUST_PROXY` / `COOKIE_SECURE` trio
- [OIDC-SSO](OIDC-SSO) — complete OIDC configuration guide
- [MCP-Overview](MCP-Overview) — MCP server setup and rate limiting
- [Encryption-Key-Rotation](Encryption-Key-Rotation) — rotating the `ENCRYPTION_KEY` without losing data
- [Reverse-Proxy] — HTTPS proxy setup and the `FORCE_HTTPS` / `TRUST_PROXY` / `COOKIE_SECURE` trio
- [OIDC-SSO] — complete OIDC configuration guide
- [MCP-Overview] — MCP server setup and rate limiting
- [Encryption-Key-Rotation] — rotating the `ENCRYPTION_KEY` without losing data
+5 -5
View File
@@ -93,7 +93,7 @@ ALLOWED_ORIGINS=https://trek.example.com
APP_URL=https://trek.example.com
```
Uncomment and fill in the OIDC, initial setup, or MCP variables as needed. For a full description of every variable, see [Environment-Variables](Environment-Variables).
Uncomment and fill in the OIDC, initial setup, or MCP variables as needed. For a full description of every variable, see [Environment-Variables].
## Start TREK
@@ -111,10 +111,10 @@ docker compose logs -f
This compose file is designed for deployments where a reverse proxy (nginx, Caddy, Traefik) terminates TLS in front of TREK. To enable HTTPS redirects and secure cookies, uncomment `FORCE_HTTPS=true` and `TRUST_PROXY=1`.
See [Reverse-Proxy](Reverse-Proxy) for complete proxy configuration examples.
See [Reverse-Proxy] for complete proxy configuration examples.
## Next Steps
- [Environment-Variables](Environment-Variables) — full variable reference
- [Reverse-Proxy](Reverse-Proxy) — HTTPS configuration
- [Updating](Updating) — how to pull a new image
- [Environment-Variables] — full variable reference
- [Reverse-Proxy] — HTTPS configuration
- [Updating] — how to pull a new image
+6 -6
View File
@@ -32,7 +32,7 @@ Pass additional `-e` flags for timezone and CORS/email link support:
-e ALLOWED_ORIGINS=https://trek.example.com \
```
See [Environment-Variables](Environment-Variables) for the full list.
See [Environment-Variables] for the full list.
## Volume Reference
@@ -66,11 +66,11 @@ docker logs trek
## Limitations of `docker run`
A bare `docker run` command has no built-in secret management and is harder to reproduce after a system reboot. For production, see [Install-Docker-Compose](Install-Docker-Compose), which adds security hardening (`read_only`, `cap_drop`, `cap_add`, `no-new-privileges`, `tmpfs`) and makes it easy to manage environment variables through a `.env` file.
A bare `docker run` command has no built-in secret management and is harder to reproduce after a system reboot. For production, see [Install-Docker-Compose], which adds security hardening (`read_only`, `cap_drop`, `cap_add`, `no-new-privileges`, `tmpfs`) and makes it easy to manage environment variables through a `.env` file.
## Next Steps
- [Reverse-Proxy](Reverse-Proxy) — HTTPS is required for PWA install and the `trek_session` cookie `secure` flag
- [Install-Docker-Compose](Install-Docker-Compose) — recommended for production
- [Environment-Variables](Environment-Variables) — full list of configurable variables
- [Updating](Updating) — how to pull a new image without losing data
- [Reverse-Proxy] — HTTPS is required for PWA install and the `trek_session` cookie `secure` flag
- [Install-Docker-Compose] — recommended for production
- [Environment-Variables] — full list of configurable variables
- [Updating] — how to pull a new image without losing data
+2 -2
View File
@@ -191,5 +191,5 @@ See the [`charts/README.md`](https://github.com/mauriceboe/TREK/blob/main/charts
## Next Steps
- [Environment-Variables](Environment-Variables) — full variable reference
- [Reverse-Proxy](Reverse-Proxy) — proxy configuration for non-Kubernetes deployments
- [Environment-Variables] — full variable reference
- [Reverse-Proxy] — proxy configuration for non-Kubernetes deployments
+2 -2
View File
@@ -69,5 +69,5 @@ On first boot, TREK automatically creates an admin account. The credentials are
## Next Steps
- [Environment-Variables](Environment-Variables) — complete variable reference
- [Updating](Updating) — how to pull a new image on Unraid
- [Environment-Variables] — complete variable reference
- [Updating] — how to pull a new image on Unraid
+4 -4
View File
@@ -60,7 +60,7 @@ You will be prompted to change the password on first login.
## Next Steps
- [Install-Docker-Compose](Install-Docker-Compose) — production setup with security hardening
- [Reverse-Proxy](Reverse-Proxy) — put TREK behind HTTPS (required for PWA install and secure cookies)
- [Environment-Variables](Environment-Variables) — full configuration reference
- [Admin-Panel-Overview](Admin-Panel-Overview) — explore what the admin panel can do
- [Install-Docker-Compose] — production setup with security hardening
- [Reverse-Proxy] — put TREK behind HTTPS (required for PWA install and secure cookies)
- [Environment-Variables] — full configuration reference
- [Admin-Panel-Overview] — explore what the admin panel can do
+3 -3
View File
@@ -98,9 +98,9 @@ Four variables control how TREK behaves behind a proxy. They work as a group:
If you access TREK directly on `http://<host>:3000` without a proxy, leave `FORCE_HTTPS` unset and do not set `TRUST_PROXY`.
See [Environment-Variables](Environment-Variables) for full documentation of these and all other variables.
See [Environment-Variables] for full documentation of these and all other variables.
## Next Steps
- [Environment-Variables](Environment-Variables) — full variable reference including OIDC
- [Install-Docker-Compose](Install-Docker-Compose) — production compose file with proxy-ready env vars
- [Environment-Variables] — full variable reference including OIDC
- [Install-Docker-Compose] — production compose file with proxy-ready env vars
+5 -5
View File
@@ -4,7 +4,7 @@ How to update TREK to a newer version without losing data.
## Before You Update
Back up your data first. Go to Admin Panel → Backups and create a manual backup, or copy your `./data` and `./uploads` directories to a safe location. See [Backups](Backups) for details.
Back up your data first. Go to Admin Panel → Backups and create a manual backup, or copy your `./data` and `./uploads` directories to a safe location. See [Backups] for details.
## Docker Compose (Recommended)
@@ -42,7 +42,7 @@ TREK runs any pending database migrations automatically at startup. No manual mi
If you are upgrading from a version that predates the dedicated `ENCRYPTION_KEY` (i.e. you have no `ENCRYPTION_KEY` environment variable set), TREK automatically falls back to `./data/.jwt_secret` on startup and immediately promotes it to `./data/.encryption_key`. No manual steps are required — the transition is handled at first boot after the upgrade.
If you want to rotate to a new key at any point (not required for a normal update), see [Encryption-Key-Rotation](Encryption-Key-Rotation) for the full procedure.
If you want to rotate to a new key at any point (not required for a normal update), see [Encryption-Key-Rotation] for the full procedure.
## Unraid
@@ -50,6 +50,6 @@ In the Unraid Docker tab, click the TREK container and select **Update**. Unraid
## Next Steps
- [Backups](Backups) — schedule automatic backups so you always have a restore point before updates
- [Encryption-Key-Rotation](Encryption-Key-Rotation) — if you need to rotate or migrate the encryption key
- [Install-Docker-Compose](Install-Docker-Compose) — switch to Compose for easier future updates
- [Backups] — schedule automatic backups so you always have a restore point before updates
- [Encryption-Key-Rotation] — if you need to rotate or migrate the encryption key
- [Install-Docker-Compose] — switch to Compose for easier future updates