@jjlmoya/utils-motor 1.2.0 → 1.3.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.
Files changed (66) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +3 -1
  3. package/src/entries.ts +7 -1
  4. package/src/index.ts +10 -0
  5. package/src/tests/locale_completeness.test.ts +1 -1
  6. package/src/tests/seo_translation_completeness.test.ts +69 -0
  7. package/src/tests/tool_validation.test.ts +1 -1
  8. package/src/tool/carMotorcycleGearRatioCalculator/bibliography.astro +6 -0
  9. package/src/tool/carMotorcycleGearRatioCalculator/bibliography.ts +12 -0
  10. package/src/tool/carMotorcycleGearRatioCalculator/car-motorcycle-gear-ratio-rpm-speed-calculator.css +668 -0
  11. package/src/tool/carMotorcycleGearRatioCalculator/component.astro +137 -0
  12. package/src/tool/carMotorcycleGearRatioCalculator/controller.ts +209 -0
  13. package/src/tool/carMotorcycleGearRatioCalculator/dom-views.ts +121 -0
  14. package/src/tool/carMotorcycleGearRatioCalculator/entry.ts +30 -0
  15. package/src/tool/carMotorcycleGearRatioCalculator/evaluator.ts +28 -0
  16. package/src/tool/carMotorcycleGearRatioCalculator/i18n/de.ts +162 -0
  17. package/src/tool/carMotorcycleGearRatioCalculator/i18n/en.ts +162 -0
  18. package/src/tool/carMotorcycleGearRatioCalculator/i18n/es.ts +162 -0
  19. package/src/tool/carMotorcycleGearRatioCalculator/i18n/fr.ts +162 -0
  20. package/src/tool/carMotorcycleGearRatioCalculator/i18n/id.ts +162 -0
  21. package/src/tool/carMotorcycleGearRatioCalculator/i18n/it.ts +162 -0
  22. package/src/tool/carMotorcycleGearRatioCalculator/i18n/ja.ts +162 -0
  23. package/src/tool/carMotorcycleGearRatioCalculator/i18n/ko.ts +162 -0
  24. package/src/tool/carMotorcycleGearRatioCalculator/i18n/nl.ts +162 -0
  25. package/src/tool/carMotorcycleGearRatioCalculator/i18n/pl.ts +162 -0
  26. package/src/tool/carMotorcycleGearRatioCalculator/i18n/pt.ts +162 -0
  27. package/src/tool/carMotorcycleGearRatioCalculator/i18n/ru.ts +162 -0
  28. package/src/tool/carMotorcycleGearRatioCalculator/i18n/sv.ts +162 -0
  29. package/src/tool/carMotorcycleGearRatioCalculator/i18n/tr.ts +162 -0
  30. package/src/tool/carMotorcycleGearRatioCalculator/i18n/zh.ts +162 -0
  31. package/src/tool/carMotorcycleGearRatioCalculator/index.ts +10 -0
  32. package/src/tool/carMotorcycleGearRatioCalculator/logic.test.ts +51 -0
  33. package/src/tool/carMotorcycleGearRatioCalculator/logic.ts +91 -0
  34. package/src/tool/carMotorcycleGearRatioCalculator/seo.astro +15 -0
  35. package/src/tool/carMotorcycleGearRatioCalculator/storage.ts +46 -0
  36. package/src/tool/carMotorcycleGearRatioCalculator/ui.ts +50 -0
  37. package/src/tool/tirePressureConverter/bibliography.astro +6 -0
  38. package/src/tool/tirePressureConverter/bibliography.ts +12 -0
  39. package/src/tool/tirePressureConverter/component.astro +82 -0
  40. package/src/tool/tirePressureConverter/controller.ts +120 -0
  41. package/src/tool/tirePressureConverter/dom-views.ts +31 -0
  42. package/src/tool/tirePressureConverter/entry.ts +30 -0
  43. package/src/tool/tirePressureConverter/evaluator.ts +8 -0
  44. package/src/tool/tirePressureConverter/i18n/de.ts +96 -0
  45. package/src/tool/tirePressureConverter/i18n/en.ts +96 -0
  46. package/src/tool/tirePressureConverter/i18n/es.ts +96 -0
  47. package/src/tool/tirePressureConverter/i18n/fr.ts +96 -0
  48. package/src/tool/tirePressureConverter/i18n/id.ts +95 -0
  49. package/src/tool/tirePressureConverter/i18n/it.ts +95 -0
  50. package/src/tool/tirePressureConverter/i18n/ja.ts +95 -0
  51. package/src/tool/tirePressureConverter/i18n/ko.ts +95 -0
  52. package/src/tool/tirePressureConverter/i18n/nl.ts +95 -0
  53. package/src/tool/tirePressureConverter/i18n/pl.ts +95 -0
  54. package/src/tool/tirePressureConverter/i18n/pt.ts +95 -0
  55. package/src/tool/tirePressureConverter/i18n/ru.ts +95 -0
  56. package/src/tool/tirePressureConverter/i18n/sv.ts +95 -0
  57. package/src/tool/tirePressureConverter/i18n/tr.ts +95 -0
  58. package/src/tool/tirePressureConverter/i18n/zh.ts +95 -0
  59. package/src/tool/tirePressureConverter/index.ts +10 -0
  60. package/src/tool/tirePressureConverter/logic.test.ts +37 -0
  61. package/src/tool/tirePressureConverter/logic.ts +47 -0
  62. package/src/tool/tirePressureConverter/seo.astro +15 -0
  63. package/src/tool/tirePressureConverter/storage.ts +27 -0
  64. package/src/tool/tirePressureConverter/tire-pressure-converter-psi-bar-kpa-kgf-cm2.css +293 -0
  65. package/src/tool/tirePressureConverter/ui.ts +20 -0
  66. package/src/tools.ts +3 -1
@@ -0,0 +1,46 @@
1
+ import type { TransmissionInputs, UnitSystem } from './logic';
2
+
3
+ const STORAGE_KEY = 'car-motorcycle-gear-ratio-rpm-speed-calculator';
4
+
5
+ export interface StoredSettings extends Omit<TransmissionInputs, 'unitSystem'> {
6
+ unitSystem: UnitSystem;
7
+ vehicleProfile?: 'car' | 'motorcycle';
8
+ }
9
+
10
+ export function loadSettings(): Partial<StoredSettings> {
11
+ try {
12
+ const value = window.localStorage.getItem(STORAGE_KEY);
13
+ if (!value) return {};
14
+ const parsed: unknown = JSON.parse(value);
15
+ return isStoredSettings(parsed) ? parsed : {};
16
+ } catch {
17
+ return {};
18
+ }
19
+ }
20
+
21
+ export function saveSettings(settings: StoredSettings): void {
22
+ try {
23
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
24
+ } catch {
25
+ }
26
+ }
27
+
28
+ function isStoredSettings(value: unknown): value is StoredSettings {
29
+ if (!value || typeof value !== 'object') return false;
30
+ const candidate = value as Partial<StoredSettings>;
31
+ if (!isUnitSystem(candidate.unitSystem)) return false;
32
+ if (!isVehicleProfile(candidate.vehicleProfile)) return false;
33
+ return isNumberArray(candidate.gearRatios);
34
+ }
35
+
36
+ function isUnitSystem(value: unknown): value is UnitSystem {
37
+ return value === 'metric' || value === 'imperial';
38
+ }
39
+
40
+ function isVehicleProfile(value: unknown): value is 'car' | 'motorcycle' | undefined {
41
+ return value === undefined || value === 'car' || value === 'motorcycle';
42
+ }
43
+
44
+ function isNumberArray(value: unknown): value is number[] {
45
+ return Array.isArray(value) && value.every((ratio) => typeof ratio === 'number');
46
+ }
@@ -0,0 +1,50 @@
1
+ export interface CarMotorcycleGearRatioCalculatorUI extends Record<string, string> {
2
+ unitsLabel: string;
3
+ metricLabel: string;
4
+ imperialLabel: string;
5
+ vehicleProfileLabel: string;
6
+ carLabel: string;
7
+ motorcycleLabel: string;
8
+ carPresetLabel: string;
9
+ motorcyclePresetLabel: string;
10
+ engineRpmLabel: string;
11
+ redlineLabel: string;
12
+ primaryRatioLabel: string;
13
+ finalDriveLabel: string;
14
+ wheelDiameterLabel: string;
15
+ targetSpeedLabel: string;
16
+ gearStackLabel: string;
17
+ gearRatioHint: string;
18
+ gearLabel: string;
19
+ transmissionSceneLabel: string;
20
+ motionHintLabel: string;
21
+ motionHintText: string;
22
+ engineLabel: string;
23
+ gearboxLabel: string;
24
+ finalDriveShortLabel: string;
25
+ wheelLabel: string;
26
+ speedAtRpmLabel: string;
27
+ rpmAtTargetLabel: string;
28
+ overallRatioLabel: string;
29
+ wheelRpmLabel: string;
30
+ targetSpeedMarkerLabel: string;
31
+ selectedGearLabel: string;
32
+ decisionLabel: string;
33
+ decisionSpeedLabel: string;
34
+ decisionCruiseLabel: string;
35
+ decisionHelper: string;
36
+ chooseGearHint: string;
37
+ editInputsHint: string;
38
+ calculationStatusLabel: string;
39
+ readyMessage: string;
40
+ highRpmMessage: string;
41
+ lowRpmMessage: string;
42
+ invalidMessage: string;
43
+ resetLabel: string;
44
+ educationalNotice: string;
45
+ ratioUnit: string;
46
+ speedUnitMetric: string;
47
+ speedUnitImperial: string;
48
+ wheelUnitMetric: string;
49
+ wheelUnitImperial: string;
50
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { bibliographyEntries } from './bibliography';
4
+ ---
5
+
6
+ <SharedBibliography links={bibliographyEntries} />
@@ -0,0 +1,12 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliographyEntries: BibliographyEntry[] = [
4
+ {
5
+ name: 'NHTSA Tire Safety Ratings and Awareness',
6
+ url: 'https://www.nhtsa.gov/vehicle-safety/tires',
7
+ },
8
+ {
9
+ name: 'Michelin Tire Pressure Guide',
10
+ url: 'https://www.michelinman.com/auto/auto-tips-and-advice/tire-pressure',
11
+ },
12
+ ];
@@ -0,0 +1,82 @@
1
+ ---
2
+ import type { TirePressureConverterUI } from './ui';
3
+ import { convertPressure, formatPressure, pressureNeedleAngle, type PressureConversion } from './logic';
4
+
5
+ interface Props {
6
+ ui: Record<string, string>;
7
+ }
8
+
9
+ const { ui: rawUi } = Astro.props;
10
+ const ui = rawUi as TirePressureConverterUI;
11
+ const initialConversion = convertPressure(32, 'psi') as PressureConversion;
12
+ const initialAngle = pressureNeedleAngle(initialConversion.psi);
13
+ const initialFill = Math.min(Math.max(initialConversion.psi / 60, 0), 1) * 100;
14
+ ---
15
+
16
+ <div class="tire-pressure-tool" data-tire-pressure-tool>
17
+ <div class="pressure-workbench">
18
+ <section class="pressure-input-panel" aria-labelledby="pressure-reading-label">
19
+ <div class="pressure-workbench-intro">
20
+ <p class="pressure-kicker">{ui.flowLabel}</p>
21
+ <p>{ui.flowHint}</p>
22
+ </div>
23
+ <div class="pressure-field">
24
+ <label id="pressure-reading-label" for="pressure-reading">{ui.readingLabel}</label>
25
+ <div class="pressure-input">
26
+ <input id="pressure-reading" data-pressure-input type="number" inputmode="decimal" min="0" step="any" value="32" placeholder={ui.inputPlaceholder} aria-describedby="pressure-reading-hint" />
27
+ <span data-selected-unit>PSI</span>
28
+ </div>
29
+ <p class="pressure-field-hint" id="pressure-reading-hint">{ui.readingHint}</p>
30
+ </div>
31
+ <fieldset class="pressure-field">
32
+ <legend>{ui.sourceUnitLabel}</legend>
33
+ <div class="pressure-unit-row">
34
+ <button type="button" data-unit="psi" aria-pressed="true">{ui.psiLabel}</button>
35
+ <button type="button" data-unit="bar" aria-pressed="false">{ui.barLabel}</button>
36
+ <button type="button" data-unit="kpa" aria-pressed="false">{ui.kpaLabel}</button>
37
+ <button type="button" data-unit="kgfcm2" aria-pressed="false">{ui.kgfcm2Label}</button>
38
+ </div>
39
+ </fieldset>
40
+ <fieldset class="pressure-field">
41
+ <legend>{ui.axleLabel}</legend>
42
+ <div class="pressure-axle-row">
43
+ <button type="button" data-axle="front" aria-pressed="false">{ui.frontAxle}</button>
44
+ <button type="button" data-axle="rear" aria-pressed="false">{ui.rearAxle}</button>
45
+ <button type="button" data-axle="all" aria-pressed="true">{ui.allAxles}</button>
46
+ </div>
47
+ </fieldset>
48
+ <p class="pressure-safety">{ui.safetyNote}</p>
49
+ </section>
50
+ <section class="pressure-result-panel" aria-live="polite">
51
+ <div class="pressure-scene" data-pressure-scene>
52
+ <svg class="pressure-scene-svg" viewBox="0 0 360 250" role="img" aria-label={ui.emptyMessage}>
53
+ <path class="pressure-scene-arc" d="M 48 188 A 132 132 0 0 1 312 188" pathLength="100" />
54
+ <path class="pressure-scene-arc pressure-scene-arc-active" d="M 48 188 A 132 132 0 0 1 312 188" pathLength="100" style={`stroke-dasharray:${initialFill} 100`} />
55
+ <circle class="pressure-scene-hub" cx="180" cy="188" r="18" />
56
+ <line class="pressure-scene-needle" x1="180" y1="188" x2="180" y2="66" transform={`rotate(${initialAngle} 180 188)`} />
57
+ <text class="pressure-scene-value" x="180" y="145" text-anchor="middle">{formatPressure(initialConversion.values.psi, 'psi')}</text>
58
+ <text class="pressure-scene-unit" x="180" y="164" text-anchor="middle">PSI</text>
59
+ <text class="pressure-scene-mark" x="42" y="214">0</text>
60
+ <text class="pressure-scene-mark" x="180" y="42" text-anchor="middle">30</text>
61
+ <text class="pressure-scene-mark" x="318" y="214" text-anchor="end">60</text>
62
+ </svg>
63
+ </div>
64
+ <div>
65
+ <p class="pressure-conversion-label">{ui.conversionLabel}</p>
66
+ <div class="pressure-values" data-pressure-values>
67
+ <div class="pressure-value"><span>{ui.psiLabel}</span><strong>{formatPressure(initialConversion.values.psi, 'psi')}</strong></div>
68
+ <div class="pressure-value"><span>{ui.barLabel}</span><strong>{formatPressure(initialConversion.values.bar, 'bar')}</strong></div>
69
+ <div class="pressure-value"><span>{ui.kpaLabel}</span><strong>{formatPressure(initialConversion.values.kpa, 'kpa')}</strong></div>
70
+ <div class="pressure-value"><span>{ui.kgfcm2Label}</span><strong>{formatPressure(initialConversion.values.kgfcm2, 'kgfcm2')}</strong></div>
71
+ </div>
72
+ </div>
73
+ <p class="pressure-status" data-pressure-status>{ui.readyMessage} {ui.allAxles}</p>
74
+ </section>
75
+ </div>
76
+ </div>
77
+
78
+ <script is:inline type="application/json" data-pressure-ui set:html={JSON.stringify(ui)}></script>
79
+ <script>
80
+ import { mountTirePressureConverter } from './controller';
81
+ mountTirePressureConverter();
82
+ </script>
@@ -0,0 +1,120 @@
1
+ import { convertPressure, isPressureUnit, parsePressureInput, type PressureUnit } from './logic';
2
+ import { renderConversionValues, renderPressureScene } from './dom-views';
3
+ import { evaluatePressure } from './evaluator';
4
+ import { loadPressureState, savePressureState, type SavedPressureState } from './storage';
5
+ import type { TirePressureConverterUI } from './ui';
6
+
7
+ type Axle = SavedPressureState['axle'];
8
+
9
+ interface RuntimeElements {
10
+ input: HTMLInputElement;
11
+ scene: HTMLElement;
12
+ values: HTMLElement;
13
+ status: HTMLElement;
14
+ selectedUnit: HTMLElement;
15
+ }
16
+
17
+ interface UpdateContext {
18
+ root: HTMLElement;
19
+ elements: RuntimeElements;
20
+ ui: TirePressureConverterUI;
21
+ }
22
+
23
+ interface RuntimeContext {
24
+ root: HTMLElement;
25
+ elements: RuntimeElements;
26
+ ui: TirePressureConverterUI;
27
+ }
28
+
29
+ export function mountTirePressureConverter(): void {
30
+ const context = getContext();
31
+ if (!context) return;
32
+ const { root, elements, ui } = context;
33
+ const state = loadPressureState();
34
+ const initialUnit = getInitialUnit(state?.sourceUnit);
35
+ const initialAxle = getInitialAxle(state?.axle);
36
+ elements.input.value = state?.input ?? elements.input.value;
37
+ setActiveButton(root, 'unit', initialUnit);
38
+ setActiveButton(root, 'axle', initialAxle);
39
+ const update = createUpdater({ root, elements, ui });
40
+ bindChoiceButtons(root, 'unit', update);
41
+ bindChoiceButtons(root, 'axle', update);
42
+ elements.input.addEventListener('input', update);
43
+ update();
44
+ }
45
+
46
+ function getContext(): RuntimeContext | null {
47
+ const root = document.querySelector<HTMLElement>('[data-tire-pressure-tool]');
48
+ const uiScript = document.querySelector<HTMLScriptElement>('[data-pressure-ui]');
49
+ if (!root || !uiScript) return null;
50
+ const elements = getElements(root);
51
+ if (!elements) return null;
52
+ const ui = JSON.parse(uiScript.textContent ?? '{}') as TirePressureConverterUI;
53
+ return { root, elements, ui };
54
+ }
55
+
56
+ function getInitialUnit(value: string | undefined): PressureUnit {
57
+ return value && isPressureUnit(value) ? value : 'psi';
58
+ }
59
+
60
+ function getInitialAxle(value: Axle | undefined): Axle {
61
+ return value ?? 'all';
62
+ }
63
+
64
+ function getElements(root: HTMLElement): RuntimeElements | null {
65
+ const input = root.querySelector<HTMLInputElement>('[data-pressure-input]');
66
+ const scene = root.querySelector<HTMLElement>('[data-pressure-scene]');
67
+ const values = root.querySelector<HTMLElement>('[data-pressure-values]');
68
+ const status = root.querySelector<HTMLElement>('[data-pressure-status]');
69
+ const selectedUnit = root.querySelector<HTMLElement>('[data-selected-unit]');
70
+ if (!input || !scene || !values || !status || !selectedUnit) return null;
71
+ return { input, scene, values, status, selectedUnit };
72
+ }
73
+
74
+ function createUpdater({ root, elements, ui }: UpdateContext): () => void {
75
+ return (): void => {
76
+ const sourceUnit = getActiveUnit(root);
77
+ const axle = getActiveAxle(root);
78
+ const inputValue = parsePressureInput(elements.input.value);
79
+ const conversion = inputValue === null ? null : convertPressure(inputValue, sourceUnit);
80
+ const labelMap = { psi: ui.psiLabel, bar: ui.barLabel, kpa: ui.kpaLabel, kgfcm2: ui.kgfcm2Label };
81
+ elements.selectedUnit.textContent = labelMap[sourceUnit];
82
+ renderPressureScene(elements.scene, conversion, ui.emptyMessage);
83
+ renderConversionValues(elements.values, conversion, labelMap);
84
+ const evaluation = evaluatePressure(elements.input.value, sourceUnit);
85
+ elements.status.textContent = evaluation === 'ready' ? `${ui.readyMessage} ${axleLabel(axle, ui)}` : ui.emptyMessage;
86
+ elements.status.dataset.state = evaluation;
87
+ savePressureState({ input: elements.input.value, sourceUnit, axle });
88
+ };
89
+ }
90
+
91
+ function bindChoiceButtons(root: HTMLElement, group: 'unit' | 'axle', update: () => void): void {
92
+ root.querySelectorAll<HTMLElement>(`[data-${group}]`).forEach((button) => button.addEventListener('click', () => {
93
+ setActiveButton(root, group, button.dataset[group] ?? '');
94
+ update();
95
+ }));
96
+ }
97
+
98
+ function setActiveButton(root: HTMLElement, group: 'unit' | 'axle', value: string): void {
99
+ root.querySelectorAll<HTMLElement>(`[data-${group}]`).forEach((button) => {
100
+ const active = button.dataset[group] === value;
101
+ button.classList.toggle('is-active', active);
102
+ button.setAttribute('aria-pressed', String(active));
103
+ });
104
+ }
105
+
106
+ function getActiveUnit(root: HTMLElement): PressureUnit {
107
+ const value = root.querySelector<HTMLElement>('[data-unit].is-active')?.dataset.unit ?? 'psi';
108
+ return isPressureUnit(value) ? value : 'psi';
109
+ }
110
+
111
+ function getActiveAxle(root: HTMLElement): Axle {
112
+ const value = root.querySelector<HTMLElement>('[data-axle].is-active')?.dataset.axle;
113
+ return value === 'front' || value === 'rear' ? value : 'all';
114
+ }
115
+
116
+ function axleLabel(axle: Axle, ui: TirePressureConverterUI): string {
117
+ if (axle === 'front') return ui.frontAxle;
118
+ if (axle === 'rear') return ui.rearAxle;
119
+ return ui.allAxles;
120
+ }
@@ -0,0 +1,31 @@
1
+ import { formatPressure, pressureNeedleAngle, type PressureConversion, type PressureUnit } from './logic';
2
+
3
+ const UNIT_ORDER: PressureUnit[] = ['psi', 'bar', 'kpa', 'kgfcm2'];
4
+
5
+ export function renderPressureScene(container: HTMLElement, conversion: PressureConversion | null, label: string): void {
6
+ if (!conversion) {
7
+ container.innerHTML = `<div class="pressure-scene-empty">${label}</div>`;
8
+ return;
9
+ }
10
+ const angle = pressureNeedleAngle(conversion.psi);
11
+ const fill = Math.min(Math.max(conversion.psi / 60, 0), 1) * 100;
12
+ container.innerHTML = `<svg class="pressure-scene-svg" viewBox="0 0 360 250" role="img" aria-label="${label}">
13
+ <path class="pressure-scene-arc" d="M 48 188 A 132 132 0 0 1 312 188" pathLength="100" />
14
+ <path class="pressure-scene-arc pressure-scene-arc-active" d="M 48 188 A 132 132 0 0 1 312 188" pathLength="100" style="stroke-dasharray:${fill} 100" />
15
+ <circle class="pressure-scene-hub" cx="180" cy="188" r="18" />
16
+ <line class="pressure-scene-needle" x1="180" y1="188" x2="180" y2="66" transform="rotate(${angle} 180 188)" />
17
+ <text class="pressure-scene-value" x="180" y="145" text-anchor="middle">${formatPressure(conversion.values.psi, 'psi')}</text>
18
+ <text class="pressure-scene-unit" x="180" y="164" text-anchor="middle">PSI</text>
19
+ <text class="pressure-scene-mark" x="42" y="214">0</text>
20
+ <text class="pressure-scene-mark" x="180" y="42" text-anchor="middle">30</text>
21
+ <text class="pressure-scene-mark" x="318" y="214" text-anchor="end">60</text>
22
+ </svg>`;
23
+ }
24
+
25
+ export function renderConversionValues(container: HTMLElement, conversion: PressureConversion | null, labels: Record<PressureUnit, string>): void {
26
+ if (!conversion) {
27
+ container.innerHTML = '';
28
+ return;
29
+ }
30
+ container.innerHTML = UNIT_ORDER.map((unit) => `<div class="pressure-value"><span>${labels[unit]}</span><strong>${formatPressure(conversion.values[unit], unit)}</strong></div>`).join('');
31
+ }
@@ -0,0 +1,30 @@
1
+ import type { MotorToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { TirePressureConverterUI } from './ui';
3
+
4
+ export type { TirePressureConverterUI };
5
+ export type TirePressureConverterLocaleContent = ToolLocaleContent<TirePressureConverterUI>;
6
+
7
+ export const tirePressureConverter: MotorToolEntry<TirePressureConverterUI> = {
8
+ id: 'tire-pressure-converter',
9
+ icons: {
10
+ bg: 'mdi:tire',
11
+ fg: 'mdi:gauge',
12
+ },
13
+ i18n: {
14
+ de: () => import('./i18n/de').then((module) => module.content),
15
+ en: () => import('./i18n/en').then((module) => module.content),
16
+ es: () => import('./i18n/es').then((module) => module.content),
17
+ fr: () => import('./i18n/fr').then((module) => module.content),
18
+ 'id': () => import('./i18n/id').then((module) => module.content),
19
+ it: () => import('./i18n/it').then((module) => module.content),
20
+ ja: () => import('./i18n/ja').then((module) => module.content),
21
+ ko: () => import('./i18n/ko').then((module) => module.content),
22
+ nl: () => import('./i18n/nl').then((module) => module.content),
23
+ pl: () => import('./i18n/pl').then((module) => module.content),
24
+ pt: () => import('./i18n/pt').then((module) => module.content),
25
+ ru: () => import('./i18n/ru').then((module) => module.content),
26
+ sv: () => import('./i18n/sv').then((module) => module.content),
27
+ tr: () => import('./i18n/tr').then((module) => module.content),
28
+ zh: () => import('./i18n/zh').then((module) => module.content),
29
+ },
30
+ };
@@ -0,0 +1,8 @@
1
+ import { convertPressure, parsePressureInput, type PressureUnit } from './logic';
2
+
3
+ export type PressureEvaluation = 'empty' | 'ready';
4
+
5
+ export function evaluatePressure(input: string, unit: PressureUnit): PressureEvaluation {
6
+ const value = parsePressureInput(input);
7
+ return value !== null && convertPressure(value, unit) ? 'ready' : 'empty';
8
+ }
@@ -0,0 +1,96 @@
1
+ import type { WithContext, SoftwareApplication, FAQPage, HowTo } from 'schema-dts';
2
+ import type { SEOSection } from '@jjlmoya/utils-shared';
3
+ import type { TirePressureConverterLocaleContent } from '../entry';
4
+
5
+ const schemas: WithContext<SoftwareApplication | FAQPage | HowTo>[] = [
6
+ {
7
+ '@context': 'https://schema.org',
8
+ '@type': 'SoftwareApplication',
9
+ name: 'Reifendruck Umrechner',
10
+ applicationCategory: 'UtilitiesApplication',
11
+ operatingSystem: 'Web',
12
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
13
+ description: 'Rechnen Sie den Reifendruck zwischen PSI, Bar, kPa und kgf/cm² um.',
14
+ },
15
+ {
16
+ '@context': 'https://schema.org',
17
+ '@type': 'FAQPage',
18
+ mainEntity: [
19
+ {
20
+ '@type': 'Question',
21
+ name: 'Welchen Reifendruck sollte ich verwenden?',
22
+ acceptedAnswer: { '@type': 'Answer', text: 'Nutzen Sie den kalten Reifendruck laut Fahrzeughandbuch oder Aufkleber an der B-Säule. Der Umrechner ändert nur die Einheiten.' },
23
+ },
24
+ {
25
+ '@type': 'Question',
26
+ name: 'Warum unterscheiden sich Reifendrücke für Vorder- und Hinterachse?',
27
+ acceptedAnswer: { '@type': 'Answer', text: 'Die Gewichtsverteilung und die Achslast führen zu unterschiedlichen Vorgaben des Herstellers für vorne und hinten.' },
28
+ },
29
+ ],
30
+ },
31
+ {
32
+ '@context': 'https://schema.org',
33
+ '@type': 'HowTo',
34
+ name: 'Reifendruck umrechnen',
35
+ step: [
36
+ { '@type': 'HowToStep', name: 'Messwert eingeben', text: 'Geben Sie die Zahl Ihres Reifendruckprüfers ein.' },
37
+ { '@type': 'HowToStep', name: 'Quelleinheit wählen', text: 'Wählen Sie PSI, Bar, kPa oder kgf/cm².' },
38
+ { '@type': 'HowToStep', name: 'Umrechnung ablesen', text: 'Lesen Sie den entsprechenden Wert in der Ergebnisliste ab.' },
39
+ ],
40
+ },
41
+ ];
42
+
43
+ const seo: SEOSection[] = [
44
+ { type: 'title', level: 2, text: 'Reifendruck umrechnen ohne den physikalischen Wert zu verändern' },
45
+ { type: 'paragraph', html: 'Verwenden Sie diesen Reifendruck-Umrechner, wenn ein Druckprüfer oder eine Tankstelle eine andere Einheit anzeigt. Geben Sie einen Wert ein und erhalten Sie PSI, Bar, kPa und kgf/cm² auf einen Blick.' },
46
+ { type: 'title', text: 'So nutzen Sie den Umrechner', level: 3 },
47
+ { type: 'list', items: ['Geben Sie den vom Druckprüfer angezeigten Wert ein.', 'Wählen Sie die gedruckte Einheit neben dem Messwert.', 'Wählen Sie Vorderachse, Hinterachse oder Alle Achsen zur Übersicht.', 'Lesen Sie den äquivalenten Wert in der Ergebniszeile ab.'] },
48
+ { type: 'tip', title: 'Der Umrechner bestimmt nicht Ihren Solldruck', html: 'Suchen Sie den empfohlenen Kaltdruck im Fahrzeughandbuch oder auf dem Aufkleber am Türholm. Der Wert auf der Reifenflanke ist der Höchstdruck des Reifens, keine fahrzeugspezifische Empfehlung.' },
49
+ { type: 'title', text: 'Druckeinheiten auf einen Blick', level: 3 },
50
+ { type: 'table', headers: ['Einheit', 'Bedeutung', 'Exakte Beziehung'], rows: [['PSI', 'Pfund pro Quadratzoll', '1 psi'], ['bar', 'Barometrische Druckeinheit', '1 bar = 14.5037738 psi'], ['kPa', 'Kilopascal', '1 kPa = 0.145037738 psi'], ['kgf/cm²', 'Kilogramm-Kraft pro Quadratzentimeter', '1 kgf/cm² = 14.2233433 psi']] },
51
+ { type: 'diagnostic', variant: 'warning', title: 'Messbedingungen prüfen', badge: 'Kaltmessung', html: 'Der Reifendruck ändert sich mit der Temperatur. Prüfen Sie den Druck gemäß Herstellerangabe immer am kalten Reifen.' },
52
+ ];
53
+
54
+ export const content: TirePressureConverterLocaleContent = {
55
+ slug: 'reifendruck-umrechner-psi-bar-kpa-kgf-cm2',
56
+ title: 'Reifendruck Umrechner',
57
+ description: 'Rechnen Sie den Reifendruck zwischen PSI, Bar, kPa und kgf/cm² um mit Achszuordnung für Auto und Motorrad.',
58
+ ui: {
59
+ flowLabel: 'Druck-Umrechnungsbrücke',
60
+ flowHint: 'Geben Sie den Messwert ein, wählen Sie die Einheit und lesen Sie das Ergebnis ab.',
61
+ readingLabel: 'Messwert vom Prüfer',
62
+ readingHint: 'Geben Sie die Zahl Ihres Reifendruckprüfers ein.',
63
+ sourceUnitLabel: 'Mess-Einheit',
64
+ axleLabel: 'Achsennotiz',
65
+ frontAxle: 'Vorderachse',
66
+ rearAxle: 'Hinterachse',
67
+ allAxles: 'Alle Achsen',
68
+ conversionLabel: 'Äquivalenter Reifendruck',
69
+ psiLabel: 'PSI',
70
+ barLabel: 'bar',
71
+ kpaLabel: 'kPa',
72
+ kgfcm2Label: 'kgf/cm²',
73
+ readyMessage: 'Umgerechnet für',
74
+ emptyMessage: 'Geben Sie einen Reifendruckwert ein.',
75
+ safetyNote: 'Dieses Tool rechnet Einheiten um. Den empfohlenen Reifendruck finden Sie in Ihrer Betriebsanleitung.',
76
+ inputPlaceholder: 'z.B. 2.2',
77
+ },
78
+ seo,
79
+ faqTitle: 'Fragen zum Reifendruck-Umrechner',
80
+ faq: [
81
+ { question: 'Welchen Reifendruck soll ich einstellen?', answer: 'Nutzen Sie die Vorgabe für den kalten Reifen aus der Betriebsanleitung oder dem Türstempel. Dieses Tool rechnet nur die Einheit um.' },
82
+ { question: 'Warum haben Vorder- und Hinterachse oft unterschiedlichen Druck?', answer: 'Unterschiedliche Achslasten und die Gewichtsverteilung erfordern verschiedene Luftdrücke für vorne und hinten.' },
83
+ { question: 'Ist der Wert auf der Reifenflanke der empfohlene Druck?', answer: 'Nein. Der Aufdruck auf der Reifenflanke zeigt den maximal zulässigen Druck des Reifens an.' },
84
+ ],
85
+ bibliographyTitle: 'Quellen',
86
+ bibliography: [
87
+ { name: 'ADAC Reifendruck Ratgeber', url: 'https://www.adac.de' },
88
+ { name: 'Continental Reifendruck Tipps', url: 'https://www.continental-reifen.de' },
89
+ ],
90
+ howTo: [
91
+ { name: 'Messwert eingeben', text: 'Geben Sie die Zahl Ihres Reifendruckprüfers ein.' },
92
+ { name: 'Quelleinheit wählen', text: 'Wählen Sie PSI, Bar, kPa oder kgf/cm².' },
93
+ { name: 'Umrechnung ablesen', text: 'Lesen Sie den entsprechenden Wert in der Ergebnisliste ab.' },
94
+ ],
95
+ schemas,
96
+ };
@@ -0,0 +1,96 @@
1
+ import type { WithContext, SoftwareApplication, FAQPage, HowTo } from 'schema-dts';
2
+ import type { SEOSection } from '@jjlmoya/utils-shared';
3
+ import type { TirePressureConverterLocaleContent } from '../entry';
4
+
5
+ const schemas: WithContext<SoftwareApplication | FAQPage | HowTo>[] = [
6
+ {
7
+ '@context': 'https://schema.org',
8
+ '@type': 'SoftwareApplication',
9
+ name: 'Tire Pressure Converter',
10
+ applicationCategory: 'UtilitiesApplication',
11
+ operatingSystem: 'Web',
12
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
13
+ description: 'Convert tire pressure between PSI, bar, kPa and kgf/cm² without guessing a vehicle setting.',
14
+ },
15
+ {
16
+ '@context': 'https://schema.org',
17
+ '@type': 'FAQPage',
18
+ mainEntity: [
19
+ {
20
+ '@type': 'Question',
21
+ name: 'Which tire pressure should I use?',
22
+ acceptedAnswer: { '@type': 'Answer', text: 'Use the cold tire pressure on the vehicle information placard or in the owner manual. The converter only changes units.' },
23
+ },
24
+ {
25
+ '@type': 'Question',
26
+ name: 'Why can front and rear pressures differ?',
27
+ acceptedAnswer: { '@type': 'Answer', text: 'Vehicle weight distribution, axle loads and the manufacturer setup can make the front and rear recommendations different.' },
28
+ },
29
+ ],
30
+ },
31
+ {
32
+ '@context': 'https://schema.org',
33
+ '@type': 'HowTo',
34
+ name: 'Convert a tire pressure reading',
35
+ step: [
36
+ { '@type': 'HowToStep', name: 'Enter the reading', text: 'Enter the number shown by your gauge.' },
37
+ { '@type': 'HowToStep', name: 'Choose the source unit', text: 'Select PSI, bar, kPa or kgf/cm².' },
38
+ { '@type': 'HowToStep', name: 'Read the conversion', text: 'Use the matching value in the result strip.' },
39
+ ],
40
+ },
41
+ ];
42
+
43
+ const seo: SEOSection[] = [
44
+ { type: 'title', level: 2, text: 'Convert tire pressure without changing the physical reading' },
45
+ { type: 'paragraph', html: 'Use this tire pressure converter when a gauge, service station or vehicle placard uses a different unit. Enter one reading and get PSI, bar, kPa and kgf/cm² in one view.' },
46
+ { type: 'title', text: 'How to use the converter', level: 3 },
47
+ { type: 'list', items: ['Enter the pressure shown by the gauge.', 'Choose the unit printed beside that reading.', 'Select Front, Rear or All axles to keep your note organized.', 'Read the equivalent value in the conversion strip.'] },
48
+ { type: 'tip', title: 'The converter does not choose your target pressure', html: 'Find the recommended cold pressure on the vehicle information placard or in the owner manual. The number moulded on a tire sidewall is a maximum for the tire, not a vehicle-specific recommendation.' },
49
+ { type: 'title', text: 'Pressure units at a glance', level: 3 },
50
+ { type: 'table', headers: ['Unit', 'Meaning', 'Exact relationship'], rows: [['PSI', 'Pounds per square inch', '1 psi'], ['bar', 'Barometric pressure unit', '1 bar = 14.5037738 psi'], ['kPa', 'Kilopascal', '1 kPa = 0.145037738 psi'], ['kgf/cm²', 'Kilogram-force per square centimetre', '1 kgf/cm² = 14.2233433 psi']] },
51
+ { type: 'diagnostic', variant: 'warning', title: 'Check the condition of the reading', badge: 'Cold reading', html: 'Pressure changes with temperature. For a reliable comparison, follow the vehicle maker instructions for checking a cold tire and use a trustworthy gauge.' },
52
+ ];
53
+
54
+ export const content: TirePressureConverterLocaleContent = {
55
+ slug: 'tire-pressure-converter-psi-bar-kpa-kgf-cm2',
56
+ title: 'Tire Pressure Converter',
57
+ description: 'Convert PSI, bar, kPa and kgf/cm² for car and motorcycle tire readings, with clear axle notes and no invented pressure recommendations.',
58
+ ui: {
59
+ flowLabel: 'Pressure bridge',
60
+ flowHint: 'Enter the reading you have, choose its unit, then use the equivalent value wherever you need it.',
61
+ readingLabel: 'Your gauge reading',
62
+ readingHint: 'Use the number currently shown by your gauge or placard.',
63
+ sourceUnitLabel: 'Reading unit',
64
+ axleLabel: 'Axle note',
65
+ frontAxle: 'Front',
66
+ rearAxle: 'Rear',
67
+ allAxles: 'All axles',
68
+ conversionLabel: 'Equivalent pressure',
69
+ psiLabel: 'PSI',
70
+ barLabel: 'bar',
71
+ kpaLabel: 'kPa',
72
+ kgfcm2Label: 'kgf/cm²',
73
+ readyMessage: 'Converted for',
74
+ emptyMessage: 'Enter a pressure reading to see the bridge.',
75
+ safetyNote: 'This tool translates units. It cannot tell you the correct pressure for a specific vehicle, load or tire.',
76
+ inputPlaceholder: 'e.g. 32',
77
+ },
78
+ seo,
79
+ faqTitle: 'Tire pressure converter questions',
80
+ faq: [
81
+ { question: 'Which tire pressure should I use?', answer: 'Use the cold tire pressure on the vehicle information placard or in the owner manual. This tool only converts the unit.' },
82
+ { question: 'Why can front and rear pressures differ?', answer: 'Different axle loads and the vehicle manufacturer setup can make the front and rear recommendations different.' },
83
+ { question: 'Is tire sidewall pressure the recommended setting?', answer: 'No. The sidewall number is a tire limit. Use the vehicle placard or owner manual for the recommended cold pressure.' },
84
+ ],
85
+ bibliographyTitle: 'Sources',
86
+ bibliography: [
87
+ { name: 'NHTSA Tire Safety Ratings and Awareness', url: 'https://www.nhtsa.gov/vehicle-safety/tires' },
88
+ { name: 'Michelin Tire Pressure Guide', url: 'https://www.michelinman.com/auto/auto-tips-and-advice/tire-pressure' },
89
+ ],
90
+ howTo: [
91
+ { name: 'Enter the reading', text: 'Enter the number shown by your gauge.' },
92
+ { name: 'Choose the source unit', text: 'Select PSI, bar, kPa or kgf/cm².' },
93
+ { name: 'Read the conversion', text: 'Use the matching value in the result strip.' },
94
+ ],
95
+ schemas,
96
+ };