@jjlmoya/utils-travel 1.26.0 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/scripts/validate-icons.mjs +57 -0
- package/src/entries.ts +2 -1
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/jet-lag-recovery-planner/bibliography.astro +6 -0
- package/src/tool/jet-lag-recovery-planner/bibliography.ts +7 -0
- package/src/tool/jet-lag-recovery-planner/component.astro +177 -0
- package/src/tool/jet-lag-recovery-planner/entry.ts +69 -0
- package/src/tool/jet-lag-recovery-planner/i18n/content.ts +155 -0
- package/src/tool/jet-lag-recovery-planner/i18n/de.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/en.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/es.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/fr.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/id.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/it.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/ja.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/ko.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/nl.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/pl.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/pt.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/ru.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/sv.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/tr.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/i18n/zh.ts +2 -0
- package/src/tool/jet-lag-recovery-planner/index.ts +12 -0
- package/src/tool/jet-lag-recovery-planner/jet-lag-recovery-planner.css +505 -0
- package/src/tool/jet-lag-recovery-planner/logic.test.ts +49 -0
- package/src/tool/jet-lag-recovery-planner/logic.ts +102 -0
- package/src/tool/jet-lag-recovery-planner/seo.astro +14 -0
- package/src/tool/mini-adventures/component.astro +1 -2
- package/src/tool/mini-adventures/i18n/de.ts +1 -1
- package/src/tool/mini-adventures/i18n/en.ts +1 -1
- package/src/tool/mini-adventures/i18n/es.ts +1 -1
- package/src/tool/mini-adventures/i18n/fr.ts +1 -1
- package/src/tool/mini-adventures/i18n/id.ts +1 -1
- package/src/tool/mini-adventures/i18n/it.ts +1 -1
- package/src/tool/mini-adventures/i18n/ja.ts +1 -1
- package/src/tool/mini-adventures/i18n/ko.ts +1 -1
- package/src/tool/mini-adventures/i18n/nl.ts +1 -1
- package/src/tool/mini-adventures/i18n/pl.ts +1 -1
- package/src/tool/mini-adventures/i18n/pt.ts +1 -1
- package/src/tool/mini-adventures/i18n/ru.ts +1 -1
- package/src/tool/mini-adventures/i18n/sv.ts +1 -1
- package/src/tool/mini-adventures/i18n/tr.ts +1 -1
- package/src/tool/mini-adventures/i18n/zh.ts +1 -1
- package/src/tools.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jjlmoya/utils-travel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.28.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"postinstall": "node scripts/postinstall.mjs",
|
|
37
37
|
"predev": "node scripts/postinstall.mjs",
|
|
38
38
|
"prestart": "node scripts/postinstall.mjs",
|
|
39
|
-
"prebuild": "node scripts/postinstall.mjs",
|
|
39
|
+
"prebuild": "node scripts/postinstall.mjs && node scripts/validate-icons.mjs",
|
|
40
40
|
"qa": "npm run lint && npm run test && npm run build",
|
|
41
41
|
"cf:dry-run": "npm run build && wrangler deploy --dry-run",
|
|
42
42
|
"cf:preview": "npm run build && wrangler deploy --config wrangler.staging.jsonc",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
5
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const packageJsonPath = require.resolve('@iconify-json/mdi/package.json');
|
|
10
|
+
const packageRoot = dirname(packageJsonPath);
|
|
11
|
+
const iconSet = JSON.parse(readFileSync(join(packageRoot, 'icons.json'), 'utf8'));
|
|
12
|
+
const availableIcons = new Set([
|
|
13
|
+
...Object.keys(iconSet.icons ?? {}),
|
|
14
|
+
...Object.keys(iconSet.aliases ?? {}),
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
const sourceRoot = resolve(repoRoot, 'src');
|
|
19
|
+
const extensions = new Set(['.astro', '.js', '.mjs', '.ts', '.tsx']);
|
|
20
|
+
const iconPattern = /\bmdi:([a-z0-9-]+)\b/g;
|
|
21
|
+
const failures = [];
|
|
22
|
+
let references = 0;
|
|
23
|
+
|
|
24
|
+
function walk(directory) {
|
|
25
|
+
const entries = readdirSync(directory, { withFileTypes: true });
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const path = join(directory, entry.name);
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
if (entry.name !== 'node_modules' && entry.name !== 'dist' && entry.name !== 'tests') {
|
|
30
|
+
walk(path);
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!extensions.has(path.slice(path.lastIndexOf('.')))) continue;
|
|
35
|
+
|
|
36
|
+
const source = readFileSync(path, 'utf8');
|
|
37
|
+
for (const match of source.matchAll(iconPattern)) {
|
|
38
|
+
references += 1;
|
|
39
|
+
const iconName = match[1];
|
|
40
|
+
if (availableIcons.has(iconName)) continue;
|
|
41
|
+
|
|
42
|
+
const line = source.slice(0, match.index).split('\n').length;
|
|
43
|
+
failures.push(`${relative(repoRoot, path)}:${line} — mdi:${iconName}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
walk(sourceRoot);
|
|
49
|
+
|
|
50
|
+
if (failures.length > 0) {
|
|
51
|
+
console.error('Invalid MDI icons found:');
|
|
52
|
+
for (const failure of failures) console.error(`- ${failure}`);
|
|
53
|
+
console.error(`Checked ${references} MDI icon references against @iconify-json/mdi.`);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
} else {
|
|
56
|
+
console.log(`MDI icon validation passed: ${references} references checked.`);
|
|
57
|
+
}
|
package/src/entries.ts
CHANGED
|
@@ -5,5 +5,6 @@ import { tipCalculator } from './tool/tip-calculator/entry';
|
|
|
5
5
|
import { schengenCalculator } from './tool/schengen-calculator/entry';
|
|
6
6
|
import { fuelCostCalculator } from './tool/fuel-cost-calculator/entry';
|
|
7
7
|
import { tripExpenseSplitter } from './tool/trip-expense-splitter/entry';
|
|
8
|
+
import { jetLagRecoveryPlanner } from './tool/jet-lag-recovery-planner/entry';
|
|
8
9
|
|
|
9
|
-
export const ALL_ENTRIES = [luggageCalculator, miniAdventures, suitcaseChecklist, tipCalculator, schengenCalculator, fuelCostCalculator, tripExpenseSplitter];
|
|
10
|
+
export const ALL_ENTRIES = [luggageCalculator, miniAdventures, suitcaseChecklist, tipCalculator, schengenCalculator, fuelCostCalculator, tripExpenseSplitter, jetLagRecoveryPlanner];
|
|
@@ -4,8 +4,8 @@ import { travelCategory } from '../category';
|
|
|
4
4
|
|
|
5
5
|
describe('Tool Validation Suite', () => {
|
|
6
6
|
describe('Library Registration', () => {
|
|
7
|
-
it('should have
|
|
8
|
-
expect(ALL_TOOLS.length).toBe(
|
|
7
|
+
it('should have 8 tools in ALL_TOOLS', () => {
|
|
8
|
+
expect(ALL_TOOLS.length).toBe(8);
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
it('travelCategory should be defined', () => {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BibliographyEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const bibliography: BibliographyEntry[] = [
|
|
4
|
+
{ name: 'CDC Yellow Book: Jet Lag Disorder', url: 'https://www.cdc.gov/yellow-book/hcp/travel-air-sea/jet-lag-disorder.html' },
|
|
5
|
+
{ name: 'NHS: Jet lag', url: 'https://www.nhs.uk/conditions/jet-lag/' },
|
|
6
|
+
{ name: 'American Academy of Sleep Medicine: Circadian Rhythm Sleep-Wake Disorders', url: 'https://jcsm.aasm.org/doi/10.5664/jcsm.4758' },
|
|
7
|
+
];
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Icon } from 'astro-icon/components';
|
|
3
|
+
import type { JetLagRecoveryPlannerUI } from './entry';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
ui: JetLagRecoveryPlannerUI;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { ui } = Astro.props;
|
|
10
|
+
const offsets = Array.from({ length: 27 }, (_, index) => index - 12);
|
|
11
|
+
const uiJson = JSON.stringify(ui).replace(/</g, '\\u003c');
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<div class="jet-lag-planner" data-ui={uiJson}>
|
|
15
|
+
<section class="jl-card jl-workspace" aria-label={ui.visualRail}>
|
|
16
|
+
<div class="jl-hero-copy">
|
|
17
|
+
<span class="jl-kicker"><Icon name="mdi:weather-night" /> {ui.visualRail}</span>
|
|
18
|
+
<p>{ui.intro}</p>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="jl-journey" aria-hidden="true">
|
|
21
|
+
<div class="jl-journey-node jl-night"><Icon name="mdi:weather-night" /><span>{ui.visualSource}</span></div>
|
|
22
|
+
<div class="jl-journey-line"><Icon name="mdi:airplane" /></div>
|
|
23
|
+
<div class="jl-journey-node jl-day"><Icon name="mdi:white-balance-sunny" /><span>{ui.visualTarget}</span></div>
|
|
24
|
+
</div>
|
|
25
|
+
<form id="jl-form" class="jl-form">
|
|
26
|
+
<div class="jl-form-heading">
|
|
27
|
+
<span class="jl-step">01</span>
|
|
28
|
+
<p>{ui.timeFormatHint}</p>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="jl-form-grid">
|
|
31
|
+
<label class="jl-field">
|
|
32
|
+
<span>{ui.originLabel}</span>
|
|
33
|
+
<select id="jl-origin" name="originOffset">
|
|
34
|
+
{offsets.map((offset) => <option value={offset} selected={offset === 1}>{ui.offsetOptions[String(offset)]}</option>)}
|
|
35
|
+
</select>
|
|
36
|
+
<small>{ui.offsetHint}</small>
|
|
37
|
+
</label>
|
|
38
|
+
<label class="jl-field">
|
|
39
|
+
<span>{ui.destinationLabel}</span>
|
|
40
|
+
<select id="jl-destination" name="destinationOffset">
|
|
41
|
+
{offsets.map((offset) => <option value={offset} selected={offset === 0}>{ui.offsetOptions[String(offset)]}</option>)}
|
|
42
|
+
</select>
|
|
43
|
+
<small>{ui.offsetHint}</small>
|
|
44
|
+
</label>
|
|
45
|
+
<label class="jl-field">
|
|
46
|
+
<span>{ui.departureLabel}</span>
|
|
47
|
+
<input id="jl-departure" name="departure" type="datetime-local" value={ui.defaultDeparture} required />
|
|
48
|
+
</label>
|
|
49
|
+
<label class="jl-field">
|
|
50
|
+
<span>{ui.arrivalLabel}</span>
|
|
51
|
+
<input id="jl-arrival" name="arrival" type="datetime-local" value={ui.defaultArrival} required />
|
|
52
|
+
</label>
|
|
53
|
+
<label class="jl-field">
|
|
54
|
+
<span>{ui.sleepLabel}</span>
|
|
55
|
+
<input id="jl-sleep" name="sleepTime" type="time" value="23:00" required />
|
|
56
|
+
</label>
|
|
57
|
+
<label class="jl-field">
|
|
58
|
+
<span>{ui.wakeLabel}</span>
|
|
59
|
+
<input id="jl-wake" name="wakeTime" type="time" value="07:00" required />
|
|
60
|
+
</label>
|
|
61
|
+
<label class="jl-field jl-days-field">
|
|
62
|
+
<span>{ui.daysLabel}</span>
|
|
63
|
+
<input id="jl-days" name="days" type="number" min="1" max="14" value="7" required />
|
|
64
|
+
</label>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="jl-actions">
|
|
67
|
+
<button class="jl-primary" type="submit"><Icon name="mdi:calendar-check" /> {ui.calculate}</button>
|
|
68
|
+
<button class="jl-secondary" type="button" id="jl-reset"><Icon name="mdi:refresh" /> {ui.reset}</button>
|
|
69
|
+
</div>
|
|
70
|
+
</form>
|
|
71
|
+
|
|
72
|
+
<div class="jl-result" aria-live="polite" aria-atomic="true">
|
|
73
|
+
<div class="jl-result-top">
|
|
74
|
+
<div>
|
|
75
|
+
<span class="jl-step">02</span>
|
|
76
|
+
<h3>{ui.resultHeading}</h3>
|
|
77
|
+
</div>
|
|
78
|
+
<span id="jl-direction" class="jl-direction"></span>
|
|
79
|
+
</div>
|
|
80
|
+
<p id="jl-result-intro" class="jl-result-intro">{ui.resultIntro}</p>
|
|
81
|
+
<div class="jl-metrics">
|
|
82
|
+
<div><span>{ui.timeDifference}</span><strong id="jl-difference">—</strong></div>
|
|
83
|
+
<div><span>{ui.travelDuration}</span><strong id="jl-duration">—</strong></div>
|
|
84
|
+
<div><span>{ui.dailyShift}</span><strong id="jl-shift">—</strong></div>
|
|
85
|
+
</div>
|
|
86
|
+
<div id="jl-status" class="jl-status"></div>
|
|
87
|
+
<div id="jl-days-list" class="jl-days-list"></div>
|
|
88
|
+
<p class="jl-note"><Icon name="mdi:information-outline" /> {ui.daylightNote}</p>
|
|
89
|
+
<p class="jl-safety"><Icon name="mdi:shield-check-outline" /> {ui.safetyNote}</p>
|
|
90
|
+
</div>
|
|
91
|
+
</section>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<script>
|
|
95
|
+
import { calculateJetLagPlan } from './logic';
|
|
96
|
+
|
|
97
|
+
const escapeHtml = (value: string) => value.replace(/[&<>\"']/g, (character) => {
|
|
98
|
+
const replacements: Record<string, string> = { '&': '&', '<': '<', '>': '>', '\"': '"', "'": ''' };
|
|
99
|
+
return replacements[character] ?? character;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const getField = (root: HTMLElement, id: string) => root.querySelector<HTMLInputElement | HTMLSelectElement>('#' + id);
|
|
103
|
+
|
|
104
|
+
const readValue = (root: HTMLElement, id: string, fallback: string) => getField(root, id)?.value ?? fallback;
|
|
105
|
+
|
|
106
|
+
const readInputs = (root: HTMLElement) => ({
|
|
107
|
+
originOffset: Number(readValue(root, 'jl-origin', '0')),
|
|
108
|
+
destinationOffset: Number(readValue(root, 'jl-destination', '0')),
|
|
109
|
+
departure: readValue(root, 'jl-departure', ''),
|
|
110
|
+
arrival: readValue(root, 'jl-arrival', ''),
|
|
111
|
+
sleepTime: readValue(root, 'jl-sleep', '23:00'),
|
|
112
|
+
wakeTime: readValue(root, 'jl-wake', '07:00'),
|
|
113
|
+
days: Number(readValue(root, 'jl-days', '7')),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const getDirectionLabel = (ui: Record<string, string>, direction: string) => {
|
|
117
|
+
if (direction === 'east') return ui.eastLabel;
|
|
118
|
+
if (direction === 'west') return ui.westLabel;
|
|
119
|
+
return ui.sameLabel;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const getLightLabel = (ui: Record<string, string>, light: string) => {
|
|
123
|
+
if (light === 'morning') return ui.lightMorning;
|
|
124
|
+
if (light === 'afternoon') return ui.lightAfternoon;
|
|
125
|
+
return ui.lightNone;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const renderDays = (root: HTMLElement, ui: Record<string, string>, plan: ReturnType<typeof calculateJetLagPlan>) => {
|
|
129
|
+
const daysList = root.querySelector<HTMLElement>('#jl-days-list');
|
|
130
|
+
if (!daysList) return;
|
|
131
|
+
daysList.innerHTML = plan.days.map((day) => {
|
|
132
|
+
const light = getLightLabel(ui, day.light);
|
|
133
|
+
const labels = [ui.sleep, ui.wake, ui.breakfast, ui.lunch, ui.dinner];
|
|
134
|
+
const times = [day.sleep, day.wake, day.breakfast, day.lunch, day.dinner];
|
|
135
|
+
const icons = ['S', 'W', 'M', 'M', 'M'];
|
|
136
|
+
const schedule = times.map((time, index) => '<div><span>' + icons[index] + '</span><small>' + escapeHtml(labels[index]) + '</small><b>' + time + '</b></div>').join('');
|
|
137
|
+
return '<article class="jl-day-card"><div class="jl-day-title"><span>' + escapeHtml(ui.dayLabel) + ' ' + day.day + '</span><strong>' + day.shiftHours + ' h</strong></div><div class="jl-schedule">' + schedule + '</div><p class="jl-light"><span class="jl-light-dot"></span>' + escapeHtml(light) + '</p></article>';
|
|
138
|
+
}).join('');
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const renderPlan = (root: HTMLElement, ui: Record<string, string>) => {
|
|
142
|
+
const plan = calculateJetLagPlan(readInputs(root));
|
|
143
|
+
const direction = root.querySelector<HTMLElement>('#jl-direction')!;
|
|
144
|
+
const difference = root.querySelector<HTMLElement>('#jl-difference')!;
|
|
145
|
+
const duration = root.querySelector<HTMLElement>('#jl-duration')!;
|
|
146
|
+
const shift = root.querySelector<HTMLElement>('#jl-shift')!;
|
|
147
|
+
const status = root.querySelector<HTMLElement>('#jl-status')!;
|
|
148
|
+
direction.textContent = getDirectionLabel(ui, plan.direction);
|
|
149
|
+
direction.dataset.direction = plan.direction;
|
|
150
|
+
difference.textContent = (plan.offsetDifference > 0 ? '+' : '') + plan.offsetDifference + ' h';
|
|
151
|
+
duration.textContent = plan.travelHours === null ? '—' : plan.travelHours + ' h';
|
|
152
|
+
shift.textContent = plan.dailyShift === 0 ? '0 h' : plan.dailyShift + ' h/day';
|
|
153
|
+
status.textContent = plan.targetReached ? ui.targetReached : ui.targetPending + ' ' + plan.remainingHours + ' h';
|
|
154
|
+
status.dataset.complete = String(plan.targetReached);
|
|
155
|
+
renderDays(root, ui, plan);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const setupPlanner = (root: HTMLElement) => {
|
|
159
|
+
const ui = JSON.parse(root.dataset.ui ?? '{}') as Record<string, string>;
|
|
160
|
+
const form = root.querySelector<HTMLFormElement>('#jl-form');
|
|
161
|
+
form?.addEventListener('submit', (event) => { event.preventDefault(); renderPlan(root, ui); });
|
|
162
|
+
form?.querySelectorAll('input, select').forEach((input) => input.addEventListener('change', () => renderPlan(root, ui)));
|
|
163
|
+
root.querySelector('#jl-reset')?.addEventListener('click', () => {
|
|
164
|
+
(getField(root, 'jl-origin') as HTMLSelectElement).value = '1';
|
|
165
|
+
(getField(root, 'jl-destination') as HTMLSelectElement).value = '0';
|
|
166
|
+
(getField(root, 'jl-departure') as HTMLInputElement).value = ui.defaultDeparture;
|
|
167
|
+
(getField(root, 'jl-arrival') as HTMLInputElement).value = ui.defaultArrival;
|
|
168
|
+
(getField(root, 'jl-sleep') as HTMLInputElement).value = '23:00';
|
|
169
|
+
(getField(root, 'jl-wake') as HTMLInputElement).value = '07:00';
|
|
170
|
+
(getField(root, 'jl-days') as HTMLInputElement).value = '7';
|
|
171
|
+
renderPlan(root, ui);
|
|
172
|
+
});
|
|
173
|
+
renderPlan(root, ui);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
document.querySelectorAll<HTMLElement>('.jet-lag-planner').forEach(setupPlanner);
|
|
177
|
+
</script>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { TravelToolEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export interface JetLagRecoveryPlannerUI {
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
title: string;
|
|
6
|
+
intro: string;
|
|
7
|
+
originLabel: string;
|
|
8
|
+
destinationLabel: string;
|
|
9
|
+
offsetHint: string;
|
|
10
|
+
departureLabel: string;
|
|
11
|
+
arrivalLabel: string;
|
|
12
|
+
sleepLabel: string;
|
|
13
|
+
wakeLabel: string;
|
|
14
|
+
daysLabel: string;
|
|
15
|
+
calculate: string;
|
|
16
|
+
reset: string;
|
|
17
|
+
visualSource: string;
|
|
18
|
+
visualTarget: string;
|
|
19
|
+
visualRail: string;
|
|
20
|
+
resultHeading: string;
|
|
21
|
+
resultIntro: string;
|
|
22
|
+
eastLabel: string;
|
|
23
|
+
westLabel: string;
|
|
24
|
+
sameLabel: string;
|
|
25
|
+
timeDifference: string;
|
|
26
|
+
travelDuration: string;
|
|
27
|
+
dailyShift: string;
|
|
28
|
+
targetReached: string;
|
|
29
|
+
targetPending: string;
|
|
30
|
+
dayLabel: string;
|
|
31
|
+
sleep: string;
|
|
32
|
+
wake: string;
|
|
33
|
+
breakfast: string;
|
|
34
|
+
lunch: string;
|
|
35
|
+
dinner: string;
|
|
36
|
+
lightMorning: string;
|
|
37
|
+
lightAfternoon: string;
|
|
38
|
+
lightNone: string;
|
|
39
|
+
daylightNote: string;
|
|
40
|
+
safetyNote: string;
|
|
41
|
+
offsetOptions: Record<string, string>;
|
|
42
|
+
defaultDeparture: string;
|
|
43
|
+
defaultArrival: string;
|
|
44
|
+
timeFormatHint: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const jetLagRecoveryPlanner: TravelToolEntry<JetLagRecoveryPlannerUI> = {
|
|
48
|
+
id: 'jet-lag-recovery-planner',
|
|
49
|
+
icons: { bg: 'mdi:weather-night', fg: 'mdi:airplane-clock' },
|
|
50
|
+
i18n: {
|
|
51
|
+
de: () => import('./i18n/de').then((m) => m.content),
|
|
52
|
+
en: () => import('./i18n/en').then((m) => m.content),
|
|
53
|
+
es: () => import('./i18n/es').then((m) => m.content),
|
|
54
|
+
fr: () => import('./i18n/fr').then((m) => m.content),
|
|
55
|
+
id: () => import('./i18n/id').then((m) => m.content),
|
|
56
|
+
it: () => import('./i18n/it').then((m) => m.content),
|
|
57
|
+
ja: () => import('./i18n/ja').then((m) => m.content),
|
|
58
|
+
ko: () => import('./i18n/ko').then((m) => m.content),
|
|
59
|
+
nl: () => import('./i18n/nl').then((m) => m.content),
|
|
60
|
+
pl: () => import('./i18n/pl').then((m) => m.content),
|
|
61
|
+
pt: () => import('./i18n/pt').then((m) => m.content),
|
|
62
|
+
ru: () => import('./i18n/ru').then((m) => m.content),
|
|
63
|
+
sv: () => import('./i18n/sv').then((m) => m.content),
|
|
64
|
+
tr: () => import('./i18n/tr').then((m) => m.content),
|
|
65
|
+
zh: () => import('./i18n/zh').then((m) => m.content),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type { ToolLocaleContent } from '../../types';
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { JetLagRecoveryPlannerUI } from '../entry';
|
|
4
|
+
import { bibliography } from '../bibliography';
|
|
5
|
+
|
|
6
|
+
type Localized = {
|
|
7
|
+
slug: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description: string;
|
|
10
|
+
intro: string;
|
|
11
|
+
seoTitle: string;
|
|
12
|
+
seoOne: string;
|
|
13
|
+
seoTwo: string;
|
|
14
|
+
labels: Partial<JetLagRecoveryPlannerUI>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const offsetOptions = Object.fromEntries(Array.from({ length: 27 }, (_, index) => {
|
|
18
|
+
const value = index - 12;
|
|
19
|
+
let label = 'UTC' + value;
|
|
20
|
+
if (value === 0) label = 'UTC±0';
|
|
21
|
+
if (value > 0) label = 'UTC+' + value;
|
|
22
|
+
return [String(value), label];
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const english: JetLagRecoveryPlannerUI = {
|
|
26
|
+
title: 'Jet Lag Recovery Planner',
|
|
27
|
+
intro: 'Turn your travel times and usual routine into a calm arrival plan. Move your sleep and meal anchors gradually instead of guessing at the first night.',
|
|
28
|
+
originLabel: 'Origin UTC offset',
|
|
29
|
+
destinationLabel: 'Destination UTC offset',
|
|
30
|
+
offsetHint: 'Use the standard UTC offset for each place.',
|
|
31
|
+
departureLabel: 'Departure local date and time',
|
|
32
|
+
arrivalLabel: 'Arrival local date and time',
|
|
33
|
+
sleepLabel: 'Usual sleep time',
|
|
34
|
+
wakeLabel: 'Usual wake time',
|
|
35
|
+
daysLabel: 'Days available to adjust',
|
|
36
|
+
calculate: 'Build my plan',
|
|
37
|
+
reset: 'Reset',
|
|
38
|
+
visualSource: 'Origin routine',
|
|
39
|
+
visualTarget: 'Destination rhythm',
|
|
40
|
+
visualRail: 'A gentler landing',
|
|
41
|
+
resultHeading: 'Your arrival rhythm',
|
|
42
|
+
resultIntro: 'The first card starts from your familiar routine expressed in destination time, then moves toward your normal clock at a steady pace.',
|
|
43
|
+
eastLabel: 'Eastward shift',
|
|
44
|
+
westLabel: 'Westward shift',
|
|
45
|
+
sameLabel: 'No time-zone shift',
|
|
46
|
+
timeDifference: 'Time-zone difference',
|
|
47
|
+
travelDuration: 'Travel time',
|
|
48
|
+
dailyShift: 'Daily adjustment',
|
|
49
|
+
targetReached: 'The selected window reaches the destination routine.',
|
|
50
|
+
targetPending: 'The selected window leaves about',
|
|
51
|
+
dayLabel: 'Day',
|
|
52
|
+
sleep: 'Sleep',
|
|
53
|
+
wake: 'Wake',
|
|
54
|
+
breakfast: 'Breakfast',
|
|
55
|
+
lunch: 'Lunch',
|
|
56
|
+
dinner: 'Dinner',
|
|
57
|
+
lightMorning: 'Plan outdoor daylight after waking; keep late light gentle.',
|
|
58
|
+
lightAfternoon: 'Plan outdoor daylight in the afternoon; keep early light gentle.',
|
|
59
|
+
lightNone: 'Keep your familiar rhythm and use daylight naturally.',
|
|
60
|
+
daylightNote: 'Light cues are broad planning prompts, not a medical light-therapy prescription.',
|
|
61
|
+
safetyNote: 'Do not drive or make safety-critical decisions when sleepy. Ask a clinician about persistent or severe symptoms.',
|
|
62
|
+
timeFormatHint: 'Times are shown in 24-hour format.',
|
|
63
|
+
offsetOptions,
|
|
64
|
+
defaultDeparture: '2026-09-05T10:00',
|
|
65
|
+
defaultArrival: '2026-09-05T20:00',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const englishFaq = [
|
|
69
|
+
{ question: 'What does this jet lag planner calculate?', answer: 'It compares the UTC offsets you enter, expresses your usual sleep and wake times in destination time, and spreads the change across the days you choose. It also places simple meal anchors and a broad daylight prompt.' },
|
|
70
|
+
{ question: 'Does it use live flight or sunrise data?', answer: 'No. Everything runs in your browser from the times and UTC offsets you provide. It does not look up flights, locations, sunrise, weather, or medical information.' },
|
|
71
|
+
{ question: 'Why can a time appear on the previous or next day?', answer: 'A time-zone conversion can cross midnight. The plan wraps the clock to a readable local time; use the day card order as the sequence to follow.' },
|
|
72
|
+
{ question: 'Is this medical advice?', answer: 'No. It is a planning aid with broad daylight prompts. Do not use it to change medication or make safety-critical decisions.' },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const englishHowTo = [
|
|
76
|
+
{ name: 'Set the offsets', text: 'Choose the standard UTC offset for your origin and destination.' },
|
|
77
|
+
{ name: 'Add your routine', text: 'Enter local travel times plus your usual sleep and wake times.' },
|
|
78
|
+
{ name: 'Follow the cards', text: 'Choose the days available and use the sleep, meal, and daylight anchors as a practical arrival rhythm.' },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const safetyNotes: Record<string, string> = {
|
|
82
|
+
de: 'Fahre nicht und triff keine sicherheitskritischen Entscheidungen, wenn du müde bist. Bei starken oder anhaltenden Beschwerden solltest du ärztlichen Rat einholen.',
|
|
83
|
+
es: 'No conduzcas ni tomes decisiones importantes para tu seguridad si tienes sueño. Consulta a un profesional sanitario si los síntomas son intensos o persisten.',
|
|
84
|
+
fr: 'Ne conduisez pas et ne prenez pas de décisions qui engagent votre sécurité lorsque vous avez sommeil. Consultez un professionnel de santé en cas de symptômes importants ou persistants.',
|
|
85
|
+
it: 'Non guidare e non prendere decisioni importanti per la sicurezza quando hai sonno. Chiedi consiglio a un professionista sanitario se i sintomi sono forti o persistono.',
|
|
86
|
+
pt: 'Não conduza nem tome decisões importantes para a sua segurança quando estiver sonolento. Procure aconselhamento clínico se os sintomas forem fortes ou persistirem.',
|
|
87
|
+
nl: 'Rijd niet en neem geen veiligheidskritische beslissingen als je slaperig bent. Vraag medisch advies bij ernstige of aanhoudende klachten.',
|
|
88
|
+
sv: 'Kör inte och fatta inga säkerhetskritiska beslut när du är sömnig. Kontakta vården vid kraftiga eller ihållande symtom.',
|
|
89
|
+
id: 'Jangan mengemudi atau mengambil keputusan penting terkait keselamatan saat mengantuk. Konsultasikan kepada tenaga medis jika gejalanya berat atau menetap.',
|
|
90
|
+
tr: 'Uykuluyken araç kullanmayın veya güvenlik açısından kritik kararlar vermeyin. Şiddetli ya da kalıcı belirtiler için bir sağlık uzmanına danışın.',
|
|
91
|
+
pl: 'Nie prowadź i nie podejmuj decyzji ważnych dla bezpieczeństwa, gdy jesteś senny. Przy silnych lub utrzymujących się objawach skonsultuj się z lekarzem.',
|
|
92
|
+
ru: 'Не садитесь за руль и не принимайте важные для безопасности решения в сонном состоянии. При сильных или длительных симптомах обратитесь к врачу.',
|
|
93
|
+
ja: '眠いときは運転や安全に関わる判断をしないでください。強い症状や長引く症状は医療専門家に相談してください。',
|
|
94
|
+
ko: '졸릴 때 운전하거나 안전과 관련된 중요한 결정을 내리지 마세요. 증상이 심하거나 오래가면 전문가에게 상담하세요.',
|
|
95
|
+
zh: '困倦时不要驾驶,也不要做影响安全的重要决定。如果症状严重或持续,请咨询医疗专业人士。',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const nativeLabels: Record<string, Partial<JetLagRecoveryPlannerUI>> = {
|
|
99
|
+
ja: { visualSource: '出発地のリズム', visualTarget: '到着地のリズム', visualRail: 'ゆっくり整える', offsetHint: '各場所の標準UTCオフセットを使います。', eastLabel: '東向きの調整', westLabel: '西向きの調整', sameLabel: '時差なし', timeDifference: '時差', travelDuration: '移動時間', dailyShift: '一日の調整', targetReached: '選んだ期間で到着地のリズムに近づきます。', targetPending: '選んだ期間では約', lightMorning: '起床後に屋外の日光を取り入れ、夜遅くの明るい光を控えます。', lightAfternoon: '午後に屋外の日光を取り入れ、早朝の強い光を控えます。', lightNone: '普段のリズムを保ち、自然な日光を利用します。', daylightNote: '日光の目安は一般的な計画であり、医療目的の処方ではありません。', safetyNote: safetyNotes.ja, timeFormatHint: '時刻は24時間表記です。' },
|
|
100
|
+
ko: { visualSource: '출발지 리듬', visualTarget: '도착지 리듬', visualRail: '천천히 맞추기', offsetHint: '각 장소의 표준 UTC 오프셋을 사용하세요.', eastLabel: '동쪽 방향 조정', westLabel: '서쪽 방향 조정', sameLabel: '시차 없음', timeDifference: '시간대 차이', travelDuration: '여행 시간', dailyShift: '하루 조정량', targetReached: '선택한 기간에 도착지 리듬에 도달합니다.', targetPending: '선택한 기간 후 약', lightMorning: '기상 후 야외 햇빛을 계획하고 늦은 시간의 강한 빛은 줄이세요.', lightAfternoon: '오후에 야외 햇빛을 계획하고 이른 시간의 강한 빛은 줄이세요.', lightNone: '평소 리듬을 유지하고 자연스럽게 햇빛을 이용하세요.', daylightNote: '햇빛 단서는 일반적인 계획이며 의료용 광선 치료 처방이 아닙니다.', safetyNote: safetyNotes.ko, timeFormatHint: '시간은 24시간 형식으로 표시됩니다.' },
|
|
101
|
+
ru: { visualSource: 'Режим отправления', visualTarget: 'Ритм назначения', visualRail: 'Мягкая адаптация', offsetHint: 'Используйте стандартное смещение UTC для каждого места.', eastLabel: 'Переход на восток', westLabel: 'Переход на запад', sameLabel: 'Без смены пояса', timeDifference: 'Разница часовых поясов', travelDuration: 'Время поездки', dailyShift: 'Изменение в день', targetReached: 'За выбранное время режим приблизится к времени назначения.', targetPending: 'После выбранного периода останется около', lightMorning: 'После подъёма запланируйте дневной свет на улице, а поздний яркий свет ограничьте.', lightAfternoon: 'Запланируйте дневной свет после обеда, а ранний яркий свет ограничьте.', lightNone: 'Сохраните привычный режим и используйте дневной свет естественно.', daylightNote: 'Подсказки о свете являются общим планом, а не назначением светотерапии.', safetyNote: safetyNotes.ru, timeFormatHint: 'Время показано в 24-часовом формате.' },
|
|
102
|
+
zh: { visualSource: '出发地作息', visualTarget: '目的地节奏', visualRail: '温和着陆', offsetHint: '请使用每个地点的标准 UTC 偏移。', eastLabel: '向东调整', westLabel: '向西调整', sameLabel: '没有时差变化', timeDifference: '时差', travelDuration: '旅行时间', dailyShift: '每日调整', targetReached: '所选时间可以接近目的地作息。', targetPending: '所选时间后还会剩下约', lightMorning: '起床后安排户外日光,夜间较晚时间减少强光。', lightAfternoon: '下午安排户外日光,清晨减少强光。', lightNone: '保持日常节奏,自然利用日光。', daylightNote: '日光提示是概括性的旅行计划,不是医学光疗处方。', safetyNote: safetyNotes.zh, timeFormatHint: '时间采用24小时制。' },
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const nativeFaq: Record<string, typeof englishFaq> = {
|
|
106
|
+
ja: [{ question: 'このプランナーは何を計算しますか?', answer: 'UTCオフセットを比較し、普段の睡眠と起床を到着地の時刻に変換して、選んだ日数に分けて調整します。' }, { question: 'フライトや日の出の最新データを使いますか?', answer: 'いいえ。入力した時刻だけをブラウザー内で計算します。' }, { question: '時刻が深夜をまたぐのはなぜですか?', answer: '時差によって前日または翌日の時刻になることがあります。カードの順番に沿って利用してください。' }, { question: '医療アドバイスですか?', answer: 'いいえ。旅行計画の目安です。強い症状や長引く症状は専門家に相談してください。' }],
|
|
107
|
+
ko: [{ question: '무엇을 계산하나요?', answer: 'UTC 오프셋을 비교하고 평소 수면과 기상 시간을 도착지 시간으로 바꿔 선택한 일수에 걸쳐 조정합니다.' }, { question: '실시간 항공편이나 일출 정보를 사용하나요?', answer: '아니요. 입력한 시간만 브라우저에서 계산합니다.' }, { question: '왜 자정을 넘는 시간이 나오나요?', answer: '시간대 이동으로 전날이나 다음 날의 시간이 될 수 있습니다. 카드 순서대로 사용하세요.' }, { question: '의료 조언인가요?', answer: '아니요. 여행 계획의 참고 도구입니다. 증상이 심하거나 지속되면 전문가에게 상담하세요.' }],
|
|
108
|
+
ru: [{ question: 'Что рассчитывает планировщик?', answer: 'Он сравнивает смещения UTC, переводит сон и подъём во время назначения и распределяет адаптацию по выбранным дням.' }, { question: 'Используются ли данные рейсов или восхода?', answer: 'Нет. Всё рассчитывается в браузере по введённым данным.' }, { question: 'Почему время переходит через полночь?', answer: 'Смена пояса может изменить дату. Карточки показывают местное время в порядке плана.' }, { question: 'Это медицинский совет?', answer: 'Нет. Это ориентир для поездки, а при длительных симптомах нужен врач.' }],
|
|
109
|
+
zh: [{ question: '这个计划器计算什么?', answer: '它比较 UTC 偏移,把睡眠和起床时间换算到目的地,并在所选天数内完成调整。' }, { question: '会使用实时航班或日出数据吗?', answer: '不会。所有计算都在浏览器中依据你的输入完成。' }, { question: '为什么时间会跨过午夜?', answer: '时区变化可能让时间落在前一天或后一天,请按计划卡片顺序使用。' }, { question: '这是医疗建议吗?', answer: '不是。这是旅行规划参考,症状严重或持续时请咨询专业人士。' }],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const nativeHowTo: Record<string, typeof englishHowTo> = {
|
|
113
|
+
ja: [{ name: '時差を選ぶ', text: '出発地と到着地のUTCオフセットを選択します。' }, { name: '生活リズムを入力', text: '旅行の時刻と普段の睡眠・起床時刻を入力します。' }, { name: 'カードを確認', text: '日数を選び、睡眠・食事・日光の目安に沿って調整します。' }],
|
|
114
|
+
ko: [{ name: '시간대 선택', text: '출발지와 도착지의 UTC 오프셋을 선택합니다.' }, { name: '리듬 입력', text: '여행 시간과 평소 수면·기상 시간을 입력합니다.' }, { name: '카드 확인', text: '조정 일수를 정하고 수면, 식사, 햇빛 단서를 활용합니다.' }],
|
|
115
|
+
ru: [{ name: 'Выберите смещения', text: 'Укажите смещение UTC для места отправления и назначения.' }, { name: 'Введите режим', text: 'Добавьте время поездки и обычные часы сна и подъёма.' }, { name: 'Следуйте карточкам', text: 'Выберите число дней и используйте ориентиры сна, питания и света.' }],
|
|
116
|
+
zh: [{ name: '选择时差', text: '选择出发地和目的地的 UTC 偏移。' }, { name: '输入作息', text: '填写旅行时间以及平时的睡眠和起床时间。' }, { name: '查看卡片', text: '选择调整天数,并参考睡眠、用餐和日光提示。' }],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const localized: Record<string, Localized> = {
|
|
120
|
+
de: { slug: 'jetlag-erholungsplaner', title: 'Jetlag Erholungsplaner', description: 'Erstelle einen Tagesplan für Schlaf, Tageslicht und Mahlzeiten nach einem Zeitzonenwechsel.', intro: 'Mache aus Reisezeiten und deinem gewohnten Rhythmus einen ruhigen Ankunftsplan. Verschiebe Schlaf und Mahlzeiten schrittweise statt die erste Nacht zu erraten.', seoTitle: 'Sanfter am Ziel ankommen', seoOne: 'Jetlag entsteht, wenn die innere Uhr nicht zum lokalen Tag passt. Dieser Planer macht aus deinem eigenen Schlafrhythmus eine praktische Folge für das Ziel: Schlaf, Aufstehen, Mahlzeiten und Tageslicht. So kannst du die Anpassung vor der Reise sehen, statt müde eine Regel im Kopf zu behalten.', seoTwo: 'Gib die UTC-Versätze, lokale Reisezeiten und deinen üblichen Rhythmus ein. Das Tool rechnet die Zeiten in die Zielzeit um und verschiebt sie entsprechend der Reiserichtung. Mahlzeiten liegen relativ zum Aufstehen. Alles läuft ohne Flug-Datenbank oder Konto im Browser.', labels: { originLabel: 'UTC-Versatz am Abflugort', destinationLabel: 'UTC-Versatz am Zielort', departureLabel: 'Lokale Abreisezeit', arrivalLabel: 'Lokale Ankunftszeit', sleepLabel: 'Gewohnte Schlafenszeit', wakeLabel: 'Gewohnte Aufstehzeit', daysLabel: 'Verfügbare Anpassungstage', calculate: 'Plan erstellen', reset: 'Zurücksetzen', resultHeading: 'Dein Ankunftsrhythmus', resultIntro: 'Die Karten starten mit deinem gewohnten Rhythmus in der Zielzeit und führen ihn gleichmäßig zur normalen Uhrzeit.', timeFormatHint: 'Die Zeiten werden im 24-Stunden-Format angezeigt.', dayLabel: 'Tag', sleep: 'Schlaf', wake: 'Aufstehen', breakfast: 'Frühstück', lunch: 'Mittagessen', dinner: 'Abendessen' } },
|
|
121
|
+
es: { slug: 'plan-recuperacion-jet-lag', title: 'Planificador para Recuperar el Jet Lag', description: 'Crea un plan diario de sueño, luz y comidas para adaptarte a un cambio de huso horario.', intro: 'Convierte tus horarios de viaje y tu rutina habitual en un plan de llegada tranquilo. Desplaza el sueño y las comidas poco a poco, sin improvisar la primera noche.', seoTitle: 'Aterriza con más calma después de cambiar de huso horario', seoOne: 'El jet lag aparece cuando el reloj interno y el día local dejan de coincidir. Este planificador transforma la rutina que ya tienes en una secuencia práctica para el destino: sueño, despertar, comidas y una pauta amplia de luz natural. Sirve para preparar el viaje porque hace visible el ajuste cuando aún puedes organizarlo.', seoTwo: 'Introduce los husos UTC y las horas locales de salida y llegada, junto con tu horario habitual. El plan expresa sueño y despertar en la hora de destino y los mueve gradualmente según la dirección del viaje. Las comidas se colocan respecto al despertar. No necesita vuelos ni cuentas.', labels: { originLabel: 'Huso UTC de origen', destinationLabel: 'Huso UTC de destino', departureLabel: 'Fecha y hora local de salida', arrivalLabel: 'Fecha y hora local de llegada', sleepLabel: 'Hora habitual de dormir', wakeLabel: 'Hora habitual de levantarse', daysLabel: 'Días disponibles para adaptarte', calculate: 'Crear mi plan', reset: 'Reiniciar', resultHeading: 'Tu ritmo de llegada', resultIntro: 'La primera tarjeta parte de tu rutina habitual expresada en la hora de destino y la acerca poco a poco a tu horario normal.', timeFormatHint: 'Las horas se muestran en formato de 24 horas.', dayLabel: 'Día', sleep: 'Dormir', wake: 'Despertar', breakfast: 'Desayuno', lunch: 'Comida', dinner: 'Cena' } },
|
|
122
|
+
fr: { slug: 'plan-recuperation-decalage-horaire', title: 'Planificateur de récupération du décalage horaire', description: 'Créez un rythme de sommeil, de lumière et de repas après un changement de fuseau.', intro: 'Transformez vos horaires de voyage et votre routine en un plan d\'arrivée serein. Décalez progressivement sommeil et repas au lieu d\'improviser.', seoTitle: 'Atterrissez plus doucement après un changement de fuseau', seoOne: 'Le décalage horaire apparaît lorsque l\'horloge interne ne correspond plus au jour local. Ce planificateur transforme votre routine en une séquence pratique pour la destination: sommeil, réveil, repas et lumière naturelle. Vous voyez l\'ajustement avant le voyage, lorsque vous pouvez encore le préparer.', seoTwo: 'Saisissez les décalages UTC, les heures locales du trajet et vos heures habituelles. L\'outil exprime la routine dans l\'heure d\'arrivée et la déplace progressivement selon le sens du voyage. Les repas suivent le réveil. Aucun compte ni aucune base de vols n\'est nécessaire.', labels: { originLabel: 'Décalage UTC de départ', destinationLabel: 'Décalage UTC de destination', departureLabel: 'Date et heure locales du départ', arrivalLabel: 'Date et heure locales de l\'arrivée', sleepLabel: 'Heure habituelle du coucher', wakeLabel: 'Heure habituelle du réveil', daysLabel: 'Jours disponibles', calculate: 'Créer mon plan', reset: 'Réinitialiser', resultHeading: 'Votre rythme d\'arrivée', resultIntro: 'La première carte part de votre rythme habituel dans l\'heure de destination et le rapproche de votre horaire normal.', timeFormatHint: 'Les heures sont affichées au format 24 heures.', dayLabel: 'Jour', sleep: 'Sommeil', wake: 'Réveil', breakfast: 'Petit-déjeuner', lunch: 'Déjeuner', dinner: 'Dîner' } },
|
|
123
|
+
it: { slug: 'piano-recupero-jet-lag', title: 'Pianificatore per recuperare il jet lag', description: 'Crea un ritmo quotidiano di sonno, luce e pasti dopo un cambio di fuso orario.', intro: 'Trasforma gli orari del viaggio e la tua routine in un piano di arrivo tranquillo. Sposta sonno e pasti gradualmente, senza improvvisare.', seoTitle: 'Un arrivo più dolce dopo il cambio di fuso', seoOne: `Il jet lag nasce quando l'orologio interno non coincide con la giornata locale. Questo pianificatore trasforma la tua routine in una sequenza pratica per la destinazione: sonno, risveglio, pasti e luce naturale. Puoi vedere l'adattamento prima di partire, invece di ricordare regole mentre sei stanco.`, seoTwo: `Inserisci i fusi UTC, gli orari locali del viaggio e le tue ore abituali. Lo strumento esprime la routine nell'ora di arrivo e la sposta gradualmente nella direzione del viaggio. I pasti sono calcolati rispetto al risveglio. Non servono account o dati di volo.`, labels: { originLabel: 'Fuso UTC di partenza', destinationLabel: 'Fuso UTC di destinazione', departureLabel: 'Data e ora locali della partenza', arrivalLabel: `Data e ora locali dell'arrivo`, sleepLabel: 'Ora abituale per dormire', wakeLabel: 'Ora abituale del risveglio', daysLabel: 'Giorni disponibili', calculate: 'Crea il piano', reset: 'Azzera', resultHeading: `Il tuo ritmo all'arrivo`, resultIntro: `La prima scheda parte dalla tua routine nell'ora di destinazione e la avvicina all'orario abituale.`, timeFormatHint: 'Gli orari sono mostrati nel formato 24 ore.', dayLabel: 'Giorno', sleep: 'Sonno', wake: 'Risveglio', breakfast: 'Colazione', lunch: 'Pranzo', dinner: 'Cena' } },
|
|
124
|
+
pt: { slug: 'plano-recuperacao-jet-lag', title: 'Planeador de recuperação do jet lag', description: 'Crie um ritmo diário de sono, luz e refeições para se adaptar a uma mudança de fuso horário.', intro: 'Transforme os horários da viagem e a sua rotina num plano de chegada tranquilo. Ajuste sono e refeições aos poucos, sem adivinhar.', seoTitle: 'Chegue com mais calma depois de mudar de fuso', seoOne: 'O jet lag surge quando o relógio interno deixa de coincidir com o dia local. Este planeador transforma a rotina que já conhece numa sequência prática para o destino: sono, despertar, refeições e luz natural. É útil antes da viagem porque torna o ajuste visível quando ainda pode preparar-se.', seoTwo: 'Indique os fusos UTC, os horários locais e as suas horas habituais. O planeador converte a rotina para a hora do destino e ajusta-a progressivamente. As refeições são colocadas em relação ao despertar. Funciona no navegador sem conta ou base de dados de voos.', labels: { originLabel: 'Fuso UTC de partida', destinationLabel: 'Fuso UTC do destino', departureLabel: 'Data e hora locais da partida', arrivalLabel: 'Data e hora locais da chegada', sleepLabel: 'Hora habitual de dormir', wakeLabel: 'Hora habitual de acordar', daysLabel: 'Dias disponíveis', calculate: 'Criar o meu plano', reset: 'Repor', resultHeading: 'O seu ritmo à chegada', resultIntro: 'O primeiro cartão começa na sua rotina na hora do destino e aproxima-a gradualmente do horário normal.', timeFormatHint: 'As horas são apresentadas no formato de 24 horas.', dayLabel: 'Dia', sleep: 'Sono', wake: 'Despertar', breakfast: 'Pequeno-almoço', lunch: 'Almoço', dinner: 'Jantar' } },
|
|
125
|
+
nl: { slug: 'jetlag-herstelplanner', title: 'Jetlag herstelplanner', description: 'Maak een dagschema voor slaap, daglicht en maaltijden na een verandering van tijdzone.', intro: 'Zet je reistijden en vaste ritme om in een rustige aankomstplanning. Verschuif slaap en maaltijden geleidelijk in plaats van te gokken.', seoTitle: 'Rustiger landen na een tijdzonewissel', seoOne: 'Jetlag ontstaat wanneer je biologische klok niet overeenkomt met de lokale dag. Deze planner zet je eigen routine om in een praktische reeks voor je bestemming: slapen, opstaan, maaltijden en daglicht. Zo zie je vooraf hoe je kunt aanpassen zonder een ingewikkelde regel te onthouden als je moe bent.', seoTwo: 'Vul UTC-verschuivingen, lokale reistijden en je gewone ritme in. De planner zet slaap en opstaan om naar bestemmingstijd en verschuift ze stap voor stap. Maaltijden volgen het wakker worden. Er is geen account of vluchtendatabase nodig.', labels: { originLabel: 'UTC-verschuiving vertrek', destinationLabel: 'UTC-verschuiving bestemming', departureLabel: 'Lokale datum en tijd van vertrek', arrivalLabel: 'Lokale datum en tijd van aankomst', sleepLabel: 'Gewone bedtijd', wakeLabel: 'Gewone wektijd', daysLabel: 'Beschikbare aanpassingsdagen', calculate: 'Plan maken', reset: 'Wissen', resultHeading: 'Je ritme bij aankomst', resultIntro: 'De eerste kaart vertrekt van je gewone ritme in bestemmingstijd en schuift geleidelijk naar je normale klok.', timeFormatHint: 'Tijden worden in 24-uursnotatie getoond.', dayLabel: 'Dag', sleep: 'Slaap', wake: 'Opstaan', breakfast: 'Ontbijt', lunch: 'Lunch', dinner: 'Avondeten' } },
|
|
126
|
+
sv: { slug: 'planerare-aterhamtning-jetlag', title: 'Planerare för återhämtning från jetlag', description: 'Skapa en daglig rytm för sömn, dagsljus och måltider efter ett byte av tidszon.', intro: 'Gör om restider och din vanliga rutin till en lugn ankomstplan. Flytta sömn och måltider stegvis i stället för att gissa.', seoTitle: 'Landa mjukare efter ett byte av tidszon', seoOne: 'Jetlag uppstår när kroppens klocka inte stämmer med den lokala dagen. Planeraren gör din rutin till en praktisk följd för destinationen med sömn, uppstigning, måltider och dagsljus. Du kan se anpassningen före resan och planera lugnare.', seoTwo: 'Ange UTC-förskjutningar, lokala restider och dina vanliga sömntider. Verktyget flyttar rutinen gradvis enligt resans riktning och placerar måltider efter uppstigning. Allt räknas i webbläsaren utan flygdatabas.', labels: { originLabel: 'UTC-förskjutning vid avresa', destinationLabel: 'UTC-förskjutning vid destination', calculate: 'Skapa min plan', reset: 'Återställ', resultHeading: 'Din rytm vid ankomst', dayLabel: 'Dag', sleep: 'Sömn', wake: 'Uppstigning', breakfast: 'Frukost', lunch: 'Lunch', dinner: 'Middag', timeFormatHint: 'Tider visas i 24-timmarsformat.' } },
|
|
127
|
+
id: { slug: 'perencana-pemulihan-jet-lag', title: 'Perencana Pemulihan Jet Lag', description: 'Buat ritme harian tidur, cahaya siang, dan waktu makan setelah perubahan zona waktu.', intro: 'Ubah jadwal perjalanan dan rutinitas Anda menjadi rencana kedatangan yang tenang. Geser tidur dan makan secara bertahap.', seoTitle: 'Mendarat lebih nyaman setelah berganti zona waktu', seoOne: 'Jet lag terjadi ketika jam tubuh tidak cocok dengan hari setempat. Perencana ini menyusun tidur, bangun, makan, dan cahaya siang ke dalam urutan yang mudah digunakan. Anda dapat melihat penyesuaian sebelum perjalanan sehingga tidak perlu menebak saat lelah.', seoTwo: 'Masukkan zona UTC, waktu perjalanan setempat, dan rutinitas Anda. Waktu tidur digeser bertahap sesuai arah perjalanan, sedangkan makan mengikuti waktu bangun. Semua berjalan di browser tanpa akun atau data penerbangan.', labels: { originLabel: 'Zona UTC asal', destinationLabel: 'Zona UTC tujuan', calculate: 'Buat rencana', reset: 'Atur ulang', resultHeading: 'Ritme saat tiba', dayLabel: 'Hari', sleep: 'Tidur', wake: 'Bangun', breakfast: 'Sarapan', lunch: 'Makan siang', dinner: 'Makan malam' } },
|
|
128
|
+
tr: { slug: 'jet-lag-toparlanma-plani', title: 'Jet Lag Toparlanma Planlayıcısı', description: 'Saat dilimi değişikliğine uyum için uyku, gün ışığı ve öğünlerden oluşan günlük plan hazırlayın.', intro: 'Seyahat saatlerinizi ve alışılmış rutininizi sakin bir varış planına dönüştürün. Uykuyu ve öğünleri kademeli olarak kaydırın.', seoTitle: 'Saat dilimi değişiminden sonra daha kolay uyum sağlayın', seoOne: 'Jet lag, biyolojik saatin yerel günle uyuşmamasıyla ortaya çıkar. Bu planlayıcı kendi rutininizi uyku, uyanma, öğün ve gün ışığı ipuçlarına dönüştürür. Böylece yorgunken kural hatırlamak yerine uyumu önceden görebilirsiniz.', seoTwo: 'UTC farklarını, yerel seyahat saatlerini ve alışılmış rutini girin. Uyku ve uyanma saatleri seyahat yönüne göre aşamalı değişir; öğünler uyanma saatine bağlanır. Uçuş veritabanı gerekmez.', labels: { originLabel: 'Başlangıç UTC farkı', destinationLabel: 'Varış UTC farkı', calculate: 'Planımı oluştur', reset: 'Sıfırla', resultHeading: 'Varış ritminiz', dayLabel: 'Gün', sleep: 'Uyku', wake: 'Uyanma', breakfast: 'Kahvaltı', lunch: 'Öğle yemeği', dinner: 'Akşam yemeği' } },
|
|
129
|
+
pl: { slug: 'planer-regeneracji-po-jet-lagu', title: 'Planer regeneracji po jet lagu', description: 'Utwórz dzienny rytm snu, światła dziennego i posiłków po zmianie strefy czasowej.', intro: 'Zamień godziny podróży i zwykły rytm dnia w spokojny plan po przylocie. Przesuwaj sen i posiłki stopniowo.', seoTitle: 'Łagodniejszy powrót do rytmu po zmianie strefy', seoOne: 'Jet lag pojawia się, gdy zegar biologiczny nie pasuje do lokalnego dnia. Ten planer zamienia Twój rytm w praktyczną sekwencję: sen, pobudka, posiłki i światło dzienne. Dzięki temu możesz zobaczyć adaptację przed podróżą.', seoTwo: 'Podaj przesunięcia UTC, lokalne godziny podróży i zwykły rytm. Planer przelicza godziny na czas celu i przesuwa je zgodnie z kierunkiem podróży. Posiłki są związane z pobudką. Nie potrzebujesz konta ani danych lotu.', labels: { originLabel: 'UTC wyjazdu', destinationLabel: 'UTC celu', calculate: 'Utwórz plan', reset: 'Wyczyść', resultHeading: 'Twój rytm po przylocie', dayLabel: 'Dzień', sleep: 'Sen', wake: 'Pobudka', breakfast: 'Śniadanie', lunch: 'Obiad', dinner: 'Kolacja' } },
|
|
130
|
+
ru: { slug: 'plan-vosstanovleniya-posle-dzhet-laga', title: 'План восстановления после джетлага', description: 'Составьте план сна, дневного света и питания после смены часового пояса.', intro: 'Превратите время поездки и привычный режим в спокойный план адаптации. Сдвигайте сон и приёмы пищи постепенно.', seoTitle: 'Мягче адаптируйтесь после смены часового пояса', seoOne: 'Джетлаг возникает, когда внутренние часы не совпадают с местным днём. Планировщик превращает привычный режим в последовательность для места назначения: сон, подъём, питание и дневной свет. Вы видите изменение заранее, а не пытаетесь вспомнить правило в усталости.', seoTwo: 'Укажите смещения UTC, местное время поездки и обычный режим. Планировщик переводит сон и подъём во время назначения и постепенно меняет их по направлению путешествия. Питание привязано к подъёму. База рейсов не нужна.', labels: { originLabel: 'UTC отправления', destinationLabel: 'UTC назначения', calculate: 'Создать план', reset: 'Сбросить', resultHeading: 'Режим после прибытия', dayLabel: 'День', sleep: 'Сон', wake: 'Подъём', breakfast: 'Завтрак', lunch: 'Обед', dinner: 'Ужин' } },
|
|
131
|
+
ja: { slug: 'jet-lag-recovery-planner', title: '時差ぼけ回復プランナー', description: '時差のある旅行後の睡眠、日光、食事の調整を日ごとに計画します。', intro: '旅行の時刻と普段の生活リズムを到着後の計画に変えます。睡眠と食事を少しずつ動かします。', seoTitle: '時差のある旅の到着後プランを作る', seoOne: '時差ぼけは体内時計と現地の日中がずれることで起こります。このプランナーは睡眠、起床、食事、日光の目安を到着地の時間に並べます。旅行前に調整の流れを確認でき、疲れてからルールを思い出す必要がありません。', seoTwo: '出発地と到着地のUTC、旅行の時刻、普段の睡眠と起床を入力します。旅行の向きに合わせて数日かけて時刻を移動します。食事は起床を基準に配置され、フライト検索は不要です。', labels: { originLabel: '出発地のUTCオフセット', destinationLabel: '到着地のUTCオフセット', calculate: 'プランを作る', reset: 'リセット', resultHeading: '到着後のリズム', dayLabel: '日目', sleep: '睡眠', wake: '起床', breakfast: '朝食', lunch: '昼食', dinner: '夕食' } },
|
|
132
|
+
ko: { slug: 'jet-lag-recovery-planner', title: '시차 적응 회복 플래너', description: '시간대가 바뀐 뒤 수면, 햇빛, 식사 리듬을 하루 단위로 계획합니다.', intro: '여행 시간과 평소 생활 리듬을 도착 후 계획으로 바꿉니다. 첫날에 추측하지 않도록 시간을 조금씩 옮깁니다.', seoTitle: '시간대가 바뀐 여행을 위한 적응 계획', seoOne: '시차 피로는 몸의 시계와 현지 하루가 어긋날 때 생깁니다. 이 플래너는 수면, 기상, 식사와 햇빛 단서를 도착지 시간에 맞춰 배열합니다. 여행 전에 조정 과정을 확인할 수 있어 피곤할 때 복잡한 규칙을 기억할 필요가 없습니다.', seoTwo: '출발지와 도착지 UTC, 현지 여행 시간, 평소 수면을 입력합니다. 이동 방향에 맞춰 수면과 기상 시간을 며칠에 걸쳐 옮기고, 식사는 기상 시간 기준으로 배치합니다. 항공편 검색은 필요하지 않습니다.', labels: { originLabel: '출발지 UTC 오프셋', destinationLabel: '도착지 UTC 오프셋', calculate: '계획 만들기', reset: '초기화', resultHeading: '도착 후 리듬', dayLabel: '일차', sleep: '수면', wake: '기상', breakfast: '아침', lunch: '점심', dinner: '저녁' } },
|
|
133
|
+
zh: { slug: 'jet-lag-recovery-planner', title: '时差恢复计划器', description: '根据日常作息制定睡眠、日光与用餐的逐日调整计划。', intro: '把旅行时间和日常节奏变成轻松的抵达计划。逐步移动睡眠与用餐时间,减少第一晚的猜测。', seoTitle: '为跨时区旅行安排更平稳的抵达节奏', seoOne: '时差反应通常来自身体时钟与当地日程不一致。这个计划器会把睡眠、起床、用餐和日光提示排列到目的地时间中。你可以在出发前看到调整过程,而不必在疲惫时记住复杂规则。', seoTwo: '输入出发地和目的地的UTC偏移、旅行时间以及平时作息。计划器按旅行方向逐步移动睡眠和起床时间,用餐以起床为基准。它在浏览器中运行,不需要航班数据库。', labels: { originLabel: '出发地 UTC 偏移', destinationLabel: '目的地 UTC 偏移', calculate: '生成我的计划', reset: '重置', resultHeading: '抵达后的节奏', dayLabel: '第', sleep: '睡眠', wake: '起床', breakfast: '早餐', lunch: '午餐', dinner: '晚餐' } },
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const localFaq = (locale: string) => {
|
|
137
|
+
if (locale === 'es') return [{ question: '¿Qué calcula?', answer: 'Compara los husos UTC, convierte sueño y despertar a la hora de destino y reparte el ajuste entre los días elegidos.' }, { question: '¿Usa datos en tiempo real?', answer: 'No. Todo funciona en el navegador con tus propios horarios.' }, { question: '¿Por qué una hora cruza la medianoche?', answer: 'El cambio de huso puede mover una hora al día anterior o siguiente; sigue el orden de las tarjetas.' }, { question: '¿Es consejo médico?', answer: 'No. Es una ayuda de planificación de viajes.' }];
|
|
138
|
+
return nativeFaq[locale] ?? englishFaq;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const localHowTo = (locale: string) => {
|
|
142
|
+
if (locale === 'es') return [{ name: 'Indica los husos', text: 'Elige el huso UTC estándar de tu origen y destino.' }, { name: 'Añade tu rutina', text: 'Introduce los horarios del viaje y tus horas habituales.' }, { name: 'Sigue las tarjetas', text: 'Usa los anclajes de sueño, comidas y luz como guía.' }];
|
|
143
|
+
return nativeHowTo[locale] ?? englishHowTo;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const createContent = (locale: string): ToolLocaleContent<JetLagRecoveryPlannerUI> => {
|
|
147
|
+
const selected = locale === 'en' ? { slug: 'jet-lag-recovery-planner', title: english.title, description: 'Build a simple day-by-day sleep, daylight, and meal rhythm for a time-zone change using your own travel details.', intro: english.intro, seoTitle: 'Plan a softer landing after a time-zone change', seoOne: 'Jet lag happens when your internal body clock and the local day disagree. This planner turns the routine you already know into a practical sequence for the destination: sleep and wake anchors, meal times, and a broad daylight cue. It is useful before a trip because you can see the adjustment rather than trying to remember a rule while tired.', seoTwo: 'Enter the UTC offsets, local travel times, and your usual sleep and wake times. The planner expresses your routine in destination time and moves it gradually in the direction of travel. Meals are placed relative to waking, so the cards stay easy to follow when a schedule crosses midnight. No flight database or account is needed.', labels: english }: localized[locale] ?? localized.en;
|
|
148
|
+
const ui = { ...english, ...selected.labels, ...(nativeLabels[locale] ?? {}), title: selected.title, intro: selected.intro };
|
|
149
|
+
const faq = localFaq(locale);
|
|
150
|
+
const howTo = localHowTo(locale);
|
|
151
|
+
const faqSchema: WithContext<FAQPage> = { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
152
|
+
const howToSchema: WithContext<HowTo> = { '@context': 'https://schema.org', '@type': 'HowTo', name: selected.title, description: selected.description, step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })) };
|
|
153
|
+
const appSchema: WithContext<SoftwareApplication> = { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: selected.title, description: selected.description, applicationCategory: 'TravelApplication', operatingSystem: 'All', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' }, inLanguage: locale };
|
|
154
|
+
return { slug: selected.slug, title: selected.title, description: selected.description, ui, seo: [{ type: 'title', text: selected.seoTitle, level: 2 }, { type: 'paragraph', html: selected.seoOne }, { type: 'title', text: selected.seoTitle, level: 2 }, { type: 'paragraph', html: selected.seoTwo }, { type: 'tip', title: locale === 'en' ? 'Keep the limits in view': selected.seoTitle, html: safetyNotes[locale] ?? english.safetyNote }], faq, bibliography, howTo, howToTitle: locale === 'en' ? 'How to use the jet lag planner': selected.title, schemas: [appSchema, faqSchema, howToSchema] };
|
|
155
|
+
};
|