@jjlmoya/utils-travel 1.20.0 → 1.22.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-travel",
3
- "version": "1.20.0",
3
+ "version": "1.22.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -27,7 +27,7 @@
27
27
  "lint": "eslint src/ --max-warnings 0 && stylelint \"src/**/*.{css,astro}\"",
28
28
  "check": "astro check",
29
29
  "type-check": "astro check",
30
- "test": "vitest run",
30
+ "test": "vitest run --reporter=verbose --testTimeout=30000",
31
31
  "preversion": "npm run lint && npm run test && npm run build",
32
32
  "postversion": "git push && git push --tags",
33
33
  "patch": "npm version patch",
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
3
3
  export const content: CategoryLocaleContent = {
4
4
  slug: 'viajes',
5
5
  title: 'Herramientas y Calculadoras para Viajeros y Aventureros',
6
- description: 'Planifica tu próximo viaje con herramientas gratuitas online. Calculadoras de equipaje de aerolíneas, guías de propinas internacionales, checklists de maleta y generadores de retos diarios.',
6
+ description: 'Herramientas gratuitas para equipaje, propinas internacionales, listas de maleta y retos diarios de viaje.',
7
7
  seo: [
8
8
  {
9
9
  type: 'title',
@@ -184,4 +184,4 @@ export const content: CategoryLocaleContent = {
184
184
  ],
185
185
  };
186
186
 
187
-
187
+
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
3
3
  export const content: CategoryLocaleContent = {
4
4
  slug: 'voyages',
5
5
  title: 'Outils et Calculateurs pour Voyageurs et Aventuriers',
6
- description: 'Planifiez votre prochain voyage avec des outils gratuits en ligne. Calculateurs de bagages, guides de pourboires internationaux, checklists de valise et générateurs de défis quotidiens.',
6
+ description: 'Outils gratuits pour bagages, pourboires internationaux, listes de valise et défis quotidiens de voyage.',
7
7
  seo: [
8
8
  {
9
9
  type: 'title',
@@ -59,7 +59,7 @@ export const content: CategoryLocaleContent = {
59
59
  items: [
60
60
  '<strong>Économie sur le Vol :</strong> Évitez les frais supplémentaires pour excès de bagages grâce à une planification technique.',
61
61
  '<strong>Conscience Culturelle :</strong> Naviguez dans des environnements internationaux avec la confiance de connaître les normes locales.',
62
- '<strong>Voyages Sûrs :</strong> Des checklists qui garantissent que vous emportez tous vos documents et médicaments nécessaires.',
62
+ '<strong>Voyages Sûrs :</strong> Des checklists pour organiser vos documents et médicaments nécessaires.',
63
63
  '<strong>Gamification de l\'Exploration :</strong> Transformez vos trajets quotidiens en petites expéditions de découverte.',
64
64
  ],
65
65
  },
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
3
3
  export const content: CategoryLocaleContent = {
4
4
  slug: 'podroze',
5
5
  title: 'Narzędzia i kalkulatory dla podróżników i poszukiwaczy przygód',
6
- description: 'Zaplanuj swoją następną podróż za pomocą darmowych narzędzi online. Kalkulatory bagażu lotniczego, międzynarodowe przewodniki po napiwkach, listy kontrolne do pakowania i generatory codziennych wyzwań.',
6
+ description: 'Darmowe narzędzia do bagażu, międzynarodowych napiwków, list pakowania i codziennych wyzwań podróżnych.',
7
7
  seo: [
8
8
  {
9
9
  type: 'title',
@@ -59,7 +59,7 @@ export const content: CategoryLocaleContent = {
59
59
  items: [
60
60
  '<strong>Poupança em Voos:</strong> Evite custos extra por excesso de bagagem ou malas fora das medidas através de um planeamento técnico.',
61
61
  '<strong>Consciência Cultural:</strong> Navegue em ambientes internacionais com a confiança de conhecer as normas de etiqueta locais.',
62
- '<strong>Viagens Seguras:</strong> Checklists que garantem que leva toda a documentação e medicação necessária.',
62
+ '<strong>Viagens Seguras:</strong> Checklists para organizar a documentação e a medicação necessária.',
63
63
  '<strong>Gamificação da Exploração:</strong> Transforme os seus trajetos diários em pequenas expedições de descoberta.',
64
64
  ],
65
65
  },
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
3
3
  export const content: CategoryLocaleContent = {
4
4
  slug: 'puteshestviya',
5
5
  title: 'Инструменты и калькуляторы для путешественников и авантюристов',
6
- description: 'Планируйте свою следующую поездку с помощью бесплатных онлайн-инструментов. Калькуляторы багажа, международные гиды по чаевым, чек-листы для чемодана и генераторы ежедневных испытаний.',
6
+ description: 'Бесплатные инструменты для багажа, международных чаевых, списков вещей и ежедневных заданий путешественника.',
7
7
  seo: [
8
8
  {
9
9
  type: 'title',
@@ -1,24 +1,24 @@
1
- import type { TravelCategoryEntry } from '../types';
2
-
3
- export const travelCategory: TravelCategoryEntry = {
4
- icon: 'mdi:map-marker-path',
5
- tools: [],
6
- i18n: {
7
- de: () => import('./i18n/de').then((m) => m.content),
8
- en: () => import('./i18n/en').then((m) => m.content),
9
- es: () => import('./i18n/es').then((m) => m.content),
10
- fr: () => import('./i18n/fr').then((m) => m.content),
11
- id: () => import('./i18n/id').then((m) => m.content),
12
- it: () => import('./i18n/it').then((m) => m.content),
13
- ja: () => import('./i18n/ja').then((m) => m.content),
14
- ko: () => import('./i18n/ko').then((m) => m.content),
15
- nl: () => import('./i18n/nl').then((m) => m.content),
16
- pl: () => import('./i18n/pl').then((m) => m.content),
17
- pt: () => import('./i18n/pt').then((m) => m.content),
18
- ru: () => import('./i18n/ru').then((m) => m.content),
19
- sv: () => import('./i18n/sv').then((m) => m.content),
20
- tr: () => import('./i18n/tr').then((m) => m.content),
21
- zh: () => import('./i18n/zh').then((m) => m.content),
22
- },
23
- };
24
-
1
+ import type { TravelCategoryEntry } from '../types';
2
+
3
+ export const travelCategory: TravelCategoryEntry = {
4
+ icon: 'mdi:map-marker-path',
5
+ tools: [],
6
+ i18n: {
7
+ de: () => import('./i18n/de').then((m) => m.content),
8
+ en: () => import('./i18n/en').then((m) => m.content),
9
+ es: () => import('./i18n/es').then((m) => m.content),
10
+ fr: () => import('./i18n/fr').then((m) => m.content),
11
+ id: () => import('./i18n/id').then((m) => m.content),
12
+ it: () => import('./i18n/it').then((m) => m.content),
13
+ ja: () => import('./i18n/ja').then((m) => m.content),
14
+ ko: () => import('./i18n/ko').then((m) => m.content),
15
+ nl: () => import('./i18n/nl').then((m) => m.content),
16
+ pl: () => import('./i18n/pl').then((m) => m.content),
17
+ pt: () => import('./i18n/pt').then((m) => m.content),
18
+ ru: () => import('./i18n/ru').then((m) => m.content),
19
+ sv: () => import('./i18n/sv').then((m) => m.content),
20
+ tr: () => import('./i18n/tr').then((m) => m.content),
21
+ zh: () => import('./i18n/zh').then((m) => m.content),
22
+ },
23
+ };
24
+
@@ -12,4 +12,4 @@ const content = await travelCategory.i18n[locale]?.();
12
12
  ---
13
13
 
14
14
  {content && <SEORenderer content={{ locale, sections: content.seo }} />}
15
-
15
+
@@ -113,4 +113,4 @@ const { categoryTitle, tools = [] } = Astro.props;
113
113
  white-space: nowrap;
114
114
  }
115
115
  </style>
116
-
116
+
@@ -140,4 +140,4 @@ const otherLocales = Object.entries(localeUrls).filter(([l]) => l !== currentLoc
140
140
  box-shadow: inset -4px -2px 0 #1e293b;
141
141
  }
142
142
  </style>
143
-
143
+
package/src/data.ts CHANGED
@@ -8,4 +8,4 @@ export type {
8
8
  TravelToolEntry,
9
9
  TravelCategoryEntry,
10
10
  } from './types';
11
-
11
+
package/src/env.d.ts CHANGED
@@ -2,4 +2,4 @@ declare module '*.astro' {
2
2
  const Component: (_props: Record<string, unknown>) => unknown;
3
3
  export default Component;
4
4
  }
5
-
5
+
@@ -1,164 +1,164 @@
1
- ---
2
- import PreviewLayout from "../../layouts/PreviewLayout.astro";
3
- import PreviewNavSidebar from "../../components/PreviewNavSidebar.astro";
4
- import { ALL_TOOLS } from "../../index";
5
- import {
6
- UtilityHeader,
7
- FAQSection,
8
- Bibliography,
9
- SEORenderer,
10
- } from "@jjlmoya/utils-shared";
11
- import type { KnownLocale, ToolLocaleContent } from "../../types";
12
- import type { UtilitySEOContent } from "@jjlmoya/utils-shared";
13
-
14
- export async function getStaticPaths() {
15
- const paths = [];
16
-
17
- for (const { entry, Component: lazyComp } of ALL_TOOLS) {
18
- const { default: Component } = (await lazyComp()) as { default: unknown };
19
- const localeEntries = Object.entries(entry.i18n) as [
20
- KnownLocale,
21
- () => Promise<ToolLocaleContent>,
22
- ][];
23
- const localeContents = await Promise.all(
24
- localeEntries.map(async ([locale, loader]) => ({
25
- locale,
26
- content: await loader(),
27
- })),
28
- );
29
-
30
- const localeUrls = Object.fromEntries(
31
- localeContents.map(({ locale, content }) => [
32
- locale,
33
- `/${locale}/${content.slug}`,
34
- ]),
35
- ) as Partial<Record<KnownLocale, string>>;
36
-
37
- const firstLoader = entry.i18n.en ?? Object.values(entry.i18n)[0];
38
- const englishSlug = firstLoader ? (await firstLoader()).slug : entry.id;
39
-
40
- for (const { locale, content } of localeContents) {
41
- const allToolsNav = (
42
- await Promise.all(
43
- ALL_TOOLS.map(async ({ entry: navEntry }) => {
44
- const loader = navEntry.i18n[locale] ?? navEntry.i18n.en;
45
- if (!loader) return null;
46
- const navContent = await loader();
47
- return {
48
- id: navEntry.id,
49
- title: navContent.title,
50
- href: `/${locale}/${navContent.slug}`,
51
- isActive: navEntry.id === entry.id,
52
- };
53
- }),
54
- )
55
- ).filter(Boolean) as NavItem[];
56
- paths.push({
57
- params: { locale, slug: content.slug },
58
- props: { Component, locale, content, localeUrls, allToolsNav, englishSlug },
59
- });
60
- }
61
- }
62
-
63
- return paths;
64
- }
65
-
66
- interface NavItem {
67
- id: string;
68
- title: string;
69
- href: string;
70
- isActive?: boolean;
71
- }
72
-
73
- interface Props {
74
- Component: ToolComponent;
75
- locale: KnownLocale;
76
- content: ToolLocaleContent;
77
- localeUrls: Partial<Record<KnownLocale, string>>;
78
- allToolsNav: NavItem[];
79
- englishSlug: string;
80
- }
81
-
82
- type ToolComponent = (props: { ui: Record<string, unknown> }) => unknown;
83
-
84
- const { Component, locale, content, localeUrls, allToolsNav, englishSlug } = Astro.props as Props;
85
-
86
- // eslint-disable-next-line custom/no-css-comments
87
- const cssFiles = import.meta.glob("../../tool/**/*.css", { query: "?raw", import: "default" });
88
- const cssKey = Object.keys(cssFiles).find((k) => k.endsWith(`/${englishSlug}.css`));
89
- const toolCss = cssKey ? await cssFiles[cssKey]() as string : "";
90
-
91
- const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
92
-
93
- const words = content.title.split(" ");
94
- const titleHighlight = words[0] || "";
95
- const titleBase = words.slice(1).join(" ") || "";
96
- ---
97
-
98
- <PreviewLayout
99
- title={content.title}
100
- currentLocale={locale}
101
- localeUrls={localeUrls}
102
- hasSidebar={true}
103
- >
104
- <PreviewNavSidebar
105
- slot="sidebar"
106
- categoryTitle="Tools"
107
- tools={allToolsNav}
108
- />
109
- <Fragment slot="head">
110
- {toolCss ? <Fragment set:html={`<style is:inline>${toolCss}</style>`} /> : null}
111
- {
112
- ( content.schemas ?? []).map((schema: unknown) => (
113
- <script
114
- is:inline
115
- type="application/ld+json"
116
- set:html={JSON.stringify(schema)}
117
- />
118
- ))
119
- }
120
- </Fragment>
121
-
122
- <div class="tool-page">
123
- <UtilityHeader
124
- titleHighlight={titleHighlight}
125
- titleBase={titleBase}
126
- description={content.description}
127
- />
128
-
129
- <section class="section-tool">
130
- <Component ui={content.ui} />
131
- </section>
132
-
133
- <section class="section-seo">
134
- <SEORenderer content={seoContent} />
135
- </section>
136
-
137
- <section class="section-faq">
138
- <FAQSection items={content.faq} />
139
- </section>
140
-
141
- <section class="section-bibliography">
142
- <Bibliography links={content.bibliography} />
143
- </section>
144
- </div>
145
- </PreviewLayout>
146
-
147
- <style>
148
- .tool-page {
149
- display: flex;
150
- flex-direction: column;
151
- gap: 2rem;
152
- }
153
- .section-tool {
154
- max-width: 1200px;
155
- margin: 0 auto;
156
- width: 100%;
157
- }
158
- .section-seo,
159
- .section-faq,
160
- .section-bibliography {
161
- padding-top: 2rem;
162
- border-top: 1px solid var(--border-color);
163
- }
164
- </style>
1
+ ---
2
+ import PreviewLayout from "../../layouts/PreviewLayout.astro";
3
+ import PreviewNavSidebar from "../../components/PreviewNavSidebar.astro";
4
+ import { ALL_TOOLS } from "../../index";
5
+ import {
6
+ UtilityHeader,
7
+ FAQSection,
8
+ Bibliography,
9
+ SEORenderer,
10
+ } from "@jjlmoya/utils-shared";
11
+ import type { KnownLocale, ToolLocaleContent } from "../../types";
12
+ import type { UtilitySEOContent } from "@jjlmoya/utils-shared";
13
+
14
+ export async function getStaticPaths() {
15
+ const paths = [];
16
+
17
+ for (const { entry, Component: lazyComp } of ALL_TOOLS) {
18
+ const { default: Component } = (await lazyComp()) as { default: unknown };
19
+ const localeEntries = Object.entries(entry.i18n) as [
20
+ KnownLocale,
21
+ () => Promise<ToolLocaleContent>,
22
+ ][];
23
+ const localeContents = await Promise.all(
24
+ localeEntries.map(async ([locale, loader]) => ({
25
+ locale,
26
+ content: await loader(),
27
+ })),
28
+ );
29
+
30
+ const localeUrls = Object.fromEntries(
31
+ localeContents.map(({ locale, content }) => [
32
+ locale,
33
+ `/${locale}/${content.slug}`,
34
+ ]),
35
+ ) as Partial<Record<KnownLocale, string>>;
36
+
37
+ const firstLoader = entry.i18n.en ?? Object.values(entry.i18n)[0];
38
+ const englishSlug = firstLoader ? (await firstLoader()).slug : entry.id;
39
+
40
+ for (const { locale, content } of localeContents) {
41
+ const allToolsNav = (
42
+ await Promise.all(
43
+ ALL_TOOLS.map(async ({ entry: navEntry }) => {
44
+ const loader = navEntry.i18n[locale] ?? navEntry.i18n.en;
45
+ if (!loader) return null;
46
+ const navContent = await loader();
47
+ return {
48
+ id: navEntry.id,
49
+ title: navContent.title,
50
+ href: `/${locale}/${navContent.slug}`,
51
+ isActive: navEntry.id === entry.id,
52
+ };
53
+ }),
54
+ )
55
+ ).filter(Boolean) as NavItem[];
56
+ paths.push({
57
+ params: { locale, slug: content.slug },
58
+ props: { Component, locale, content, localeUrls, allToolsNav, englishSlug },
59
+ });
60
+ }
61
+ }
62
+
63
+ return paths;
64
+ }
65
+
66
+ interface NavItem {
67
+ id: string;
68
+ title: string;
69
+ href: string;
70
+ isActive?: boolean;
71
+ }
72
+
73
+ interface Props {
74
+ Component: ToolComponent;
75
+ locale: KnownLocale;
76
+ content: ToolLocaleContent;
77
+ localeUrls: Partial<Record<KnownLocale, string>>;
78
+ allToolsNav: NavItem[];
79
+ englishSlug: string;
80
+ }
81
+
82
+ type ToolComponent = (props: { ui: Record<string, unknown> }) => unknown;
83
+
84
+ const { Component, locale, content, localeUrls, allToolsNav, englishSlug } = Astro.props as Props;
85
+
86
+ // eslint-disable-next-line custom/no-css-comments
87
+ const cssFiles = import.meta.glob("../../tool/**/*.css", { query: "?raw", import: "default" });
88
+ const cssKey = Object.keys(cssFiles).find((k) => k.endsWith(`/${englishSlug}.css`));
89
+ const toolCss = cssKey ? await cssFiles[cssKey]() as string : "";
90
+
91
+ const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
92
+
93
+ const words = content.title.split(" ");
94
+ const titleHighlight = words[0] || "";
95
+ const titleBase = words.slice(1).join(" ") || "";
96
+ ---
97
+
98
+ <PreviewLayout
99
+ title={content.title}
100
+ currentLocale={locale}
101
+ localeUrls={localeUrls}
102
+ hasSidebar={true}
103
+ >
104
+ <PreviewNavSidebar
105
+ slot="sidebar"
106
+ categoryTitle="Tools"
107
+ tools={allToolsNav}
108
+ />
109
+ <Fragment slot="head">
110
+ {toolCss ? <Fragment set:html={`<style is:inline>${toolCss}</style>`} /> : null}
111
+ {
112
+ ( content.schemas ?? []).map((schema: unknown) => (
113
+ <script
114
+ is:inline
115
+ type="application/ld+json"
116
+ set:html={JSON.stringify(schema)}
117
+ />
118
+ ))
119
+ }
120
+ </Fragment>
121
+
122
+ <div class="tool-page">
123
+ <UtilityHeader
124
+ titleHighlight={titleHighlight}
125
+ titleBase={titleBase}
126
+ description={content.description}
127
+ />
128
+
129
+ <section class="section-tool">
130
+ <Component ui={content.ui} />
131
+ </section>
132
+
133
+ <section class="section-seo">
134
+ <SEORenderer content={seoContent} />
135
+ </section>
136
+
137
+ <section class="section-faq">
138
+ <FAQSection items={content.faq} />
139
+ </section>
140
+
141
+ <section class="section-bibliography">
142
+ <Bibliography links={content.bibliography} />
143
+ </section>
144
+ </div>
145
+ </PreviewLayout>
146
+
147
+ <style>
148
+ .tool-page {
149
+ display: flex;
150
+ flex-direction: column;
151
+ gap: 2rem;
152
+ }
153
+ .section-tool {
154
+ max-width: 1200px;
155
+ margin: 0 auto;
156
+ width: 100%;
157
+ }
158
+ .section-seo,
159
+ .section-faq,
160
+ .section-bibliography {
161
+ padding-top: 2rem;
162
+ border-top: 1px solid var(--border-color);
163
+ }
164
+ </style>