@jjlmoya/utils-travel 1.1.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 (58) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +185 -0
  3. package/src/category/i18n/es.ts +187 -0
  4. package/src/category/i18n/fr.ts +100 -0
  5. package/src/category/index.ts +12 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +15 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +22 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +278 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +8 -0
  17. package/src/tests/locale_completeness.test.ts +21 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +8 -0
  20. package/src/tests/seo_length.test.ts +8 -0
  21. package/src/tests/tool_validation.test.ts +17 -0
  22. package/src/tool/luggage-calculator/bibliography.astro +14 -0
  23. package/src/tool/luggage-calculator/component.astro +560 -0
  24. package/src/tool/luggage-calculator/i18n/en.ts +617 -0
  25. package/src/tool/luggage-calculator/i18n/es.ts +617 -0
  26. package/src/tool/luggage-calculator/i18n/fr.ts +549 -0
  27. package/src/tool/luggage-calculator/index.ts +53 -0
  28. package/src/tool/luggage-calculator/seo.astro +14 -0
  29. package/src/tool/mini-adventures/bibliography.astro +14 -0
  30. package/src/tool/mini-adventures/component.astro +665 -0
  31. package/src/tool/mini-adventures/i18n/en.ts +161 -0
  32. package/src/tool/mini-adventures/i18n/es.ts +286 -0
  33. package/src/tool/mini-adventures/i18n/fr.ts +144 -0
  34. package/src/tool/mini-adventures/index.ts +70 -0
  35. package/src/tool/mini-adventures/seo.astro +14 -0
  36. package/src/tool/optimal-routes/bibliography.astro +14 -0
  37. package/src/tool/optimal-routes/component.astro +439 -0
  38. package/src/tool/optimal-routes/i18n/en.ts +42 -0
  39. package/src/tool/optimal-routes/i18n/es.ts +52 -0
  40. package/src/tool/optimal-routes/index.ts +63 -0
  41. package/src/tool/optimal-routes/lib/RouteManager.ts +181 -0
  42. package/src/tool/optimal-routes/seo.astro +14 -0
  43. package/src/tool/suitcase-checklist/bibliography.astro +14 -0
  44. package/src/tool/suitcase-checklist/component.astro +710 -0
  45. package/src/tool/suitcase-checklist/i18n/en.ts +261 -0
  46. package/src/tool/suitcase-checklist/i18n/es.ts +261 -0
  47. package/src/tool/suitcase-checklist/i18n/fr.ts +259 -0
  48. package/src/tool/suitcase-checklist/index.ts +75 -0
  49. package/src/tool/suitcase-checklist/seo.astro +14 -0
  50. package/src/tool/tip-calculator/bibliography.astro +14 -0
  51. package/src/tool/tip-calculator/component.astro +683 -0
  52. package/src/tool/tip-calculator/i18n/en.ts +264 -0
  53. package/src/tool/tip-calculator/i18n/es.ts +264 -0
  54. package/src/tool/tip-calculator/i18n/fr.ts +264 -0
  55. package/src/tool/tip-calculator/index.ts +53 -0
  56. package/src/tool/tip-calculator/seo.astro +14 -0
  57. package/src/tools.ts +13 -0
  58. package/src/types.ts +73 -0
@@ -0,0 +1,264 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { TipCalculatorUI } from '../index';
4
+
5
+ const slug = 'calculateur-pourboire-international';
6
+ const title = 'Calculateur de Pourboire International';
7
+ const description = 'Gérez vos pourboires en toute confiance lors de vos voyages. Guide complet des pourcentages et de l\'étiquette par pays.';
8
+
9
+ const ui: TipCalculatorUI = {
10
+ title: "Calculateur de Pourboires",
11
+ badge: "Outil International",
12
+ billLabel: "Montant de l'addition",
13
+ countryLabel: "Pays / Coutume locale",
14
+ tipLabel: "Pourcentage du pourboire",
15
+ splitLabel: "Partager l'addition",
16
+ summaryTip: "Pourboire Total",
17
+ summarySubtotal: "Sous-total",
18
+ summaryTotalPerson: "Total par Personne",
19
+ protocolTitle: "Protocole",
20
+ placeholderAmount: "0.00",
21
+ placeholderFilter: "Rechercher un pays...",
22
+ placeholderSelect: "Sélectionnez un pays",
23
+ countries: [
24
+ {
25
+ id: "usa",
26
+ name: "États-Unis et Canada",
27
+ min: 18,
28
+ max: 25,
29
+ default: 20,
30
+ culture: "Le pourboire est essentiel. Prévoir entre 18% et 25%. Ne pas laisser de pourboire est considéré como une faute grave.",
31
+ },
32
+ {
33
+ id: "esp",
34
+ name: "Espagne",
35
+ min: 0,
36
+ max: 10,
37
+ default: 5,
38
+ culture: "Optionnel. Il est courant d'arrondir ou de laisser 5 à 10% pour un bon service.",
39
+ },
40
+ {
41
+ id: "jpn",
42
+ name: "Japon",
43
+ min: 0,
44
+ max: 0,
45
+ default: 0,
46
+ culture: "Pas de pourboire. Cela peut être perçu comme impoli ou offensant.",
47
+ },
48
+ {
49
+ id: "ita",
50
+ name: "Italie",
51
+ min: 0,
52
+ max: 10,
53
+ default: 0,
54
+ culture: "Le 'coperto' est un frais pour le service de table, le pain et le couvert. Un pourboire de 5-10% est apprécié mais optionnel.",
55
+ },
56
+ {
57
+ id: "fra",
58
+ name: "France",
59
+ min: 0,
60
+ max: 10,
61
+ default: 5,
62
+ culture: "Le service est légalement inclus. On laisse un petit extra (pourboire) pour un service exceptionnel.",
63
+ },
64
+ {
65
+ id: "gbr",
66
+ name: "Royaume-Uni",
67
+ min: 0,
68
+ max: 12.5,
69
+ default: 10,
70
+ culture: "Prévoyez environ 10% si le service n'est pas déjà inclus dans l'addition.",
71
+ },
72
+ {
73
+ id: "mex",
74
+ name: "Mexique",
75
+ min: 10,
76
+ max: 15,
77
+ default: 12,
78
+ culture: "Un pourboire de 10-15% ('propina') est attendu. C'est crucial pour les travailleurs du secteur.",
79
+ },
80
+ {
81
+ id: "arg",
82
+ name: "Argentine",
83
+ min: 0,
84
+ max: 10,
85
+ default: 10,
86
+ culture: "La norme est de 10%. On le laisse presque toujours si le service était correct.",
87
+ },
88
+ {
89
+ id: "bra",
90
+ name: "Brésil",
91
+ min: 0,
92
+ max: 10,
93
+ default: 10,
94
+ culture: "Souvent, 10% de 'Serviço' sont inclus dans l'addition. C'est optionnel mais la plupart le paient.",
95
+ },
96
+ {
97
+ id: "deu",
98
+ name: "Allemagne",
99
+ min: 5,
100
+ max: 10,
101
+ default: 7,
102
+ culture: "Il est courant d'arrondir ou d'ajouter 5-10%. Annoncez le montant total incluant le pourboire au serveur.",
103
+ },
104
+ {
105
+ id: "tur",
106
+ name: "Turquie",
107
+ min: 5,
108
+ max: 10,
109
+ default: 10,
110
+ culture: "Dans les zones touristiques, 10% en espèces sont attendus. Rarement payable par carte.",
111
+ },
112
+ {
113
+ id: "aus",
114
+ name: "Australie",
115
+ min: 0,
116
+ max: 10,
117
+ default: 0,
118
+ culture: "Pas attendu grâce aux salaires minimums élevés. On peut laisser 10% dans les restaurants gastronomiques.",
119
+ },
120
+ {
121
+ id: "chn",
122
+ name: "Chine",
123
+ min: 0,
124
+ max: 0,
125
+ default: 0,
126
+ culture: "Historiquement, aucun pourboire. Dans certains endroits, c'est même interdit.",
127
+ },
128
+ {
129
+ id: "prt",
130
+ name: "Portugal",
131
+ min: 0,
132
+ max: 10,
133
+ default: 5,
134
+ culture: "L'usage est de laisser la monnaie ou d'arrondir. 5-10% dans les zones touristiques.",
135
+ },
136
+ {
137
+ id: "grc",
138
+ name: "Grèce",
139
+ min: 0,
140
+ max: 10,
141
+ default: 5,
142
+ culture: "Arrondir l'addition suffit généralement. 5-10% supplémentaires sont très appréciés.",
143
+ },
144
+ ]
145
+ };
146
+
147
+ const seo: ToolLocaleContent<TipCalculatorUI>['seo'] = [
148
+ {
149
+ type: "title",
150
+ text: "Guide International du Pourboire : Étiquette et Pourcentages 2026",
151
+ level: 2,
152
+ },
153
+ {
154
+ type: "summary",
155
+ title: "Apprenez à donner un pourboire comme un local partout dans le monde",
156
+ items: [
157
+ "Guide complet sur le pourcentage de pourboire attendu dans les principales destinations touristiques.",
158
+ "Comment calculer rapidement le pourboire à partir du sous-total ou du total incluant les taxes.",
159
+ "Différence entre frais de service, couvert (coperto) et pourboire volontaire.",
160
+ "Règles d'étiquette pour les situations spéciales : taxis, hôtels et guides touristiques.",
161
+ ],
162
+ },
163
+ {
164
+ type: "paragraph",
165
+ html: "L'un des moments les plus stressants de tout voyage international survient lorsque l'addition arrive à table. Notre <strong>calculateur de pourboire international</strong> est conçu pour éliminer cette incertitude.",
166
+ }
167
+ ];
168
+
169
+ const faq: ToolLocaleContent<TipCalculatorUI>['faq'] = [
170
+ {
171
+ question: "Le pourboire est-il obligatoire aux États-Unis ?",
172
+ answer: "Légalement non, mais socialement oui. Prévoyez entre 18% et 25%.",
173
+ },
174
+ {
175
+ question: "Dans quels pays le pourboire est-il offensant ?",
176
+ answer: "Principalement au Japon et en Corée du Sud. Un bon service est considéré como un devoir.",
177
+ },
178
+ {
179
+ question: "Dois-je donner un pourboire si le service était mauvais ?",
180
+ answer: "Dans les pays où c'est optionnel, le plus correct est de ne rien laisser.",
181
+ },
182
+ {
183
+ question: "Le pourboire est-il calculé avant ou après taxes ?",
184
+ answer: "En Amérique du Nord, il est calculé sur le sous-total. En Europe, il est calculé sur le total.",
185
+ },
186
+ ];
187
+
188
+ const bibliography: ToolLocaleContent<TipCalculatorUI>['bibliography'] = [
189
+ {
190
+ name: "Condé Nast Traveler: Tipping Etiquette Guide",
191
+ url: "https://www.cntraveler.com/stories/2008-11-11/etiquette-101-tipping-guide",
192
+ },
193
+ {
194
+ name: "Lonely Planet: Tipping and Etiquette in Europe",
195
+ url: "https://www.lonelyplanet.com/articles/tipping-customs-europe",
196
+ },
197
+ {
198
+ name: "Western Union: Global Tipping Guide",
199
+ url: "https://www.westernunion.com/blog/en/global-tipping-guide/",
200
+ },
201
+ ];
202
+
203
+ const howTo: ToolLocaleContent<TipCalculatorUI>['howTo'] = [
204
+ {
205
+ name: "Saisissez le montant",
206
+ text: "Entrez le montant total de votre facture avant le pourboire.",
207
+ },
208
+ {
209
+ name: "Sélectionnez le pays",
210
+ text: "Choisissez votre destination pour charger automatiquement la recommandation.",
211
+ },
212
+ {
213
+ name: "Ajustez et partagez",
214
+ text: "Indiquez le nombre de personnes pour diviser la note.",
215
+ },
216
+ ];
217
+
218
+ const faqSchema: WithContext<FAQPage> = {
219
+ '@context': 'https://schema.org',
220
+ '@type': 'FAQPage',
221
+ mainEntity: faq.map((item) => ({
222
+ '@type': 'Question',
223
+ name: item.question,
224
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
225
+ })),
226
+ };
227
+
228
+ const howToSchema: WithContext<HowTo> = {
229
+ '@context': 'https://schema.org',
230
+ '@type': 'HowTo',
231
+ name: title,
232
+ description: description,
233
+ step: howTo.map((step) => ({
234
+ '@type': 'HowToStep',
235
+ name: step.name,
236
+ text: step.text,
237
+ })),
238
+ };
239
+
240
+ const appSchema: WithContext<SoftwareApplication> = {
241
+ '@context': 'https://schema.org',
242
+ '@type': 'SoftwareApplication',
243
+ name: title,
244
+ description: description,
245
+ applicationCategory: 'FinanceApplication',
246
+ operatingSystem: 'All',
247
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
248
+ inLanguage: 'fr',
249
+ };
250
+
251
+ export const content: ToolLocaleContent<TipCalculatorUI> = {
252
+ slug,
253
+ title,
254
+ description,
255
+ ui,
256
+ seo,
257
+ faq,
258
+ faqTitle: "Questions Fréquentes",
259
+ bibliography,
260
+ bibliographyTitle: "Sources et Références",
261
+ howTo,
262
+ howToTitle: "Comment utiliser le calculateur",
263
+ schemas: [faqSchema, howToSchema, appSchema],
264
+ };
@@ -0,0 +1,53 @@
1
+ import type { TravelToolEntry, ToolDefinition } from '../../types';
2
+ import TipCalculator from './component.astro';
3
+ import TipCalculatorSEO from './seo.astro';
4
+ import TipCalculatorBibliography from './bibliography.astro';
5
+
6
+ export interface TipCountry {
7
+ id: string;
8
+ name: string;
9
+ min: number;
10
+ max: number;
11
+ default: number;
12
+ culture: string;
13
+ }
14
+
15
+ export interface TipCalculatorUI {
16
+ [key: string]: unknown;
17
+ title: string;
18
+ badge: string;
19
+ billLabel: string;
20
+ countryLabel: string;
21
+ tipLabel: string;
22
+ splitLabel: string;
23
+ summaryTip: string;
24
+ summarySubtotal: string;
25
+ summaryTotalPerson: string;
26
+ protocolTitle: string;
27
+ placeholderAmount: string;
28
+ placeholderFilter: string;
29
+ placeholderSelect: string;
30
+ countries: TipCountry[];
31
+ }
32
+
33
+ export const tipCalculator: TravelToolEntry<TipCalculatorUI> = {
34
+ id: 'tip-calculator',
35
+ icons: {
36
+ bg: 'mdi:cash-multiple',
37
+ fg: 'mdi:earth',
38
+ },
39
+ i18n: {
40
+ es: () => import('./i18n/es').then((m) => m.content),
41
+ en: () => import('./i18n/en').then((m) => m.content),
42
+ fr: () => import('./i18n/fr').then((m) => m.content),
43
+ },
44
+ };
45
+
46
+ export { TipCalculator, TipCalculatorSEO, TipCalculatorBibliography };
47
+
48
+ export const TIP_CALCULATOR_TOOL: ToolDefinition = {
49
+ entry: tipCalculator,
50
+ Component: TipCalculator,
51
+ SEOComponent: TipCalculatorSEO,
52
+ BibliographyComponent: TipCalculatorBibliography,
53
+ };
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { SEORenderer } from "@jjlmoya/utils-shared";
3
+ import { tipCalculator } from "./index";
4
+ import type { KnownLocale } from "../../types";
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = "es" } = Astro.props;
11
+ const content = await tipCalculator.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SEORenderer content={{ locale, sections: content.seo }} />}
package/src/tools.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { LUGGAGE_CALCULATOR_TOOL } from './tool/luggage-calculator';
2
+ import { TIP_CALCULATOR_TOOL } from './tool/tip-calculator';
3
+ import { SUITCASE_CHECKLIST_TOOL } from './tool/suitcase-checklist';
4
+ import { MINI_ADVENTURES_TOOL } from './tool/mini-adventures';
5
+ import type { ToolDefinition } from './types';
6
+
7
+ export const ALL_TOOLS: ToolDefinition[] = [
8
+ LUGGAGE_CALCULATOR_TOOL,
9
+ TIP_CALCULATOR_TOOL,
10
+ SUITCASE_CHECKLIST_TOOL,
11
+ MINI_ADVENTURES_TOOL,
12
+ ];
13
+
package/src/types.ts ADDED
@@ -0,0 +1,73 @@
1
+ import type { SEOSection } from '@jjlmoya/utils-shared';
2
+ import type { WithContext, Thing } from 'schema-dts';
3
+
4
+ export type { SEOSection };
5
+
6
+ export type KnownLocale =
7
+ | 'ar' | 'da' | 'de' | 'en' | 'es' | 'fi'
8
+ | 'fr' | 'it' | 'ja' | 'ko' | 'nb' | 'nl'
9
+ | 'pl' | 'pt' | 'ru' | 'sv' | 'tr' | 'zh';
10
+
11
+ export interface FAQItem {
12
+ question: string;
13
+ answer: string;
14
+ }
15
+
16
+ export interface BibliographyEntry {
17
+ name: string;
18
+ url: string;
19
+ }
20
+
21
+ export interface HowToStep {
22
+ name: string;
23
+ text: string;
24
+ }
25
+
26
+ export interface ToolLocaleContent<TUI extends Record<string, unknown> = Record<string, unknown>> {
27
+ slug: string;
28
+ title: string;
29
+ description: string;
30
+ ui: TUI;
31
+ seo: SEOSection[];
32
+ faqTitle?: string;
33
+ faq: FAQItem[];
34
+ bibliographyTitle?: string;
35
+ bibliography: BibliographyEntry[];
36
+ howTo: HowToStep[];
37
+ schemas: WithContext<Thing>[];
38
+ [key: string]: unknown;
39
+ }
40
+
41
+ export interface CategoryLocaleContent {
42
+ slug: string;
43
+ title: string;
44
+ description: string;
45
+ seo: SEOSection[];
46
+ }
47
+
48
+ export type LocaleLoader<T> = () => Promise<T>;
49
+
50
+ export type LocaleMap<T> = Partial<Record<KnownLocale, LocaleLoader<T>>>;
51
+
52
+ export interface TravelToolEntry<TUI extends Record<string, unknown> = Record<string, unknown>> {
53
+ id: string;
54
+ icons: {
55
+ bg: string;
56
+ fg: string;
57
+ };
58
+ i18n: LocaleMap<ToolLocaleContent<TUI>>;
59
+ }
60
+
61
+ export interface TravelCategoryEntry {
62
+ icon: string;
63
+ tools: TravelToolEntry[];
64
+ i18n: LocaleMap<CategoryLocaleContent>;
65
+ }
66
+
67
+ export interface ToolDefinition {
68
+ entry: TravelToolEntry;
69
+ Component: unknown;
70
+ SEOComponent: unknown;
71
+ BibliographyComponent: unknown;
72
+ }
73
+