@jjlmoya/utils-nautical 1.16.0 → 1.18.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 +4 -2
- package/src/category/i18n/de.ts +1 -1
- package/src/category/i18n/en.ts +1 -1
- package/src/category/i18n/es.ts +16 -16
- package/src/category/i18n/fr.ts +16 -16
- package/src/category/i18n/id.ts +1 -1
- package/src/category/i18n/it.ts +1 -1
- package/src/category/i18n/nl.ts +1 -1
- package/src/category/i18n/pl.ts +1 -1
- package/src/category/i18n/pt.ts +1 -1
- package/src/category/i18n/ru.ts +3 -3
- package/src/category/i18n/sv.ts +1 -1
- package/src/category/i18n/tr.ts +1 -1
- package/src/data.ts +0 -6
- package/src/layouts/PreviewLayout.astro +1 -0
- package/src/tests/diacritics_density.test.ts +118 -0
- package/src/tests/inverted_punctuation.test.ts +84 -0
- package/src/tests/no_en_dash.test.ts +70 -0
- package/src/tests/pagespeed_best_practices.test.ts +198 -0
- package/src/tests/script_density.test.ts +94 -0
- package/src/tool/endurance/component.astro +10 -10
- package/src/tool/endurance/i18n/es.ts +54 -54
- package/src/tool/endurance/i18n/fr.ts +70 -70
- package/src/tool/endurance/i18n/ru.ts +9 -9
- package/src/tool/nauticalConverter/component.astro +15 -15
- package/src/tool/nauticalConverter/i18n/es.ts +46 -46
- package/src/tool/nauticalConverter/i18n/fr.ts +53 -53
- package/src/tool/nauticalConverter/i18n/pl.ts +3 -3
- package/src/tool/nauticalConverter/i18n/ru.ts +8 -8
- package/src/tool/nauticalConverter/i18n/zh.ts +3 -3
- package/src/tool/sailArea/component.astro +13 -13
- package/src/tool/sailArea/i18n/es.ts +53 -53
- package/src/tool/sailArea/i18n/fr.ts +70 -70
- package/src/tool/sailArea/i18n/ru.ts +8 -8
- package/src/tool/sailArea/i18n/zh.ts +5 -5
- package/src/tool/speedConverter/i18n/es.ts +58 -58
- package/src/tool/speedConverter/i18n/fr.ts +75 -75
- package/src/tool/speedConverter/i18n/ru.ts +6 -6
- package/src/tool/speedConverter/i18n/zh.ts +5 -5
- package/src/tool/tideCalculator/component.astro +5 -5
- package/src/tool/tideCalculator/i18n/es.ts +39 -39
- package/src/tool/tideCalculator/i18n/fr.ts +43 -43
- package/src/tool/tideCalculator/i18n/ru.ts +6 -6
- package/src/tool/tideCalculator/i18n/zh.ts +1 -1
- package/src/tool/tideCalculator/tide-height-calculator.css +136 -5
- package/src/tool/underKeel/component.astro +7 -7
- package/src/tool/underKeel/i18n/es.ts +39 -39
- package/src/tool/underKeel/i18n/fr.ts +45 -45
- package/src/tool/underKeel/i18n/ru.ts +5 -5
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jjlmoya/utils-nautical",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.ts",
|
|
9
9
|
"./data": "./src/data.ts",
|
|
10
|
-
"./entries": "./src/entries.ts"
|
|
10
|
+
"./entries": "./src/entries.ts",
|
|
11
|
+
"./runtime/*": "./src/tool/*/index.ts",
|
|
12
|
+
"./category-seo": "./src/category/seo.astro"
|
|
11
13
|
},
|
|
12
14
|
"files": [
|
|
13
15
|
"src",
|
package/src/category/i18n/de.ts
CHANGED
|
@@ -13,7 +13,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'Die moderne Navigation hat mit dem Aufkommen digitaler Werkzeuge eine radikale Transformation erfahren. Ob auf einem kleinen Freizeitboot oder am Steuer einer Kreuzyacht
|
|
16
|
+
html: 'Die moderne Navigation hat mit dem Aufkommen digitaler Werkzeuge eine radikale Transformation erfahren. Ob auf einem kleinen Freizeitboot oder am Steuer einer Kreuzyacht - die Fähigkeit, Positions-, Wind- und Strömungsdaten sofort zu verarbeiten, ist nicht nur eine Frage des Komforts, sondern ein grundlegendes Sicherheitsprinzip auf See.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
package/src/category/i18n/en.ts
CHANGED
|
@@ -13,7 +13,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'Modern navigation has undergone a radical transformation with the advent of digital tools. Whether you are on a small recreational craft or at the helm of a cruising yacht, the ability to process position, wind and current data instantly is not just a matter of convenience
|
|
16
|
+
html: 'Modern navigation has undergone a radical transformation with the advent of digital tools. Whether you are on a small recreational craft or at the helm of a cruising yacht, the ability to process position, wind and current data instantly is not just a matter of convenience - it is a fundamental pillar of safety at sea.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
package/src/category/i18n/es.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import type { CategoryLocaleContent } from '../index';
|
|
2
2
|
|
|
3
3
|
const slug = 'vela-y-nautica';
|
|
4
|
-
const title = 'Vela y
|
|
4
|
+
const title = 'Vela y Náutica';
|
|
5
5
|
const description =
|
|
6
|
-
'Herramientas de
|
|
6
|
+
'Herramientas de cálculo náutico para navegantes: mareas, autonomía, superficie vélica, conversores de unidades y velocidad. Ideales para el estudio del PER y Patrón de Yate.';
|
|
7
7
|
|
|
8
8
|
const seo: CategoryLocaleContent['seo'] = [
|
|
9
9
|
{
|
|
10
10
|
type: 'title',
|
|
11
|
-
text: '¿Por
|
|
11
|
+
text: '¿Por qué usar herramientas digitales en navegación y vela?',
|
|
12
12
|
level: 2,
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'La
|
|
16
|
+
html: 'La navegación moderna ha experimentado una transformación radical con la llegada de las herramientas digitales. Ya sea que te encuentres en una pequeña embarcación de recreo o al mando de un velero de crucero, la capacidad de procesar datos de posición, viento y corriente de forma instantánea no es solo una cuestión de comodidad, sino un pilar fundamental de la seguridad en el mar.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
|
20
|
-
html: '
|
|
20
|
+
html: 'Históricamente, el marino dependía exclusivamente del sextante, el cronómetro de marina y las tablas de reducción para determinar su posición en el vasto océano. Hoy, aunque el romanticismo del sextante persiste, la realidad operativa exige una velocidad de respuesta que solo las herramientas de cálculo digital pueden proporcionar.',
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
type: 'title',
|
|
@@ -26,48 +26,48 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
type: 'title',
|
|
29
|
-
text: 'Geolocalización y
|
|
29
|
+
text: 'Geolocalización y Cartografía Digital de Alta Precisión',
|
|
30
30
|
level: 3,
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
type: 'paragraph',
|
|
34
|
-
html: 'La
|
|
34
|
+
html: 'La cartografía digital ha democratizado la navegación, permitiendo que incluso barcos pequeños tengan acceso a información que antes estaba limitada a buques comerciales de gran calado. Un patrón responsable utiliza las herramientas digitales para validar su juicio, no para sustituirlo.',
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
type: 'list',
|
|
38
38
|
items: [
|
|
39
|
-
'
|
|
40
|
-
'Compatibilidad entre dispositivos: Permite sincronizar la
|
|
39
|
+
'Precisión en la entrada de datos: Errores tipográficos mínimos pueden alejarte kilómetros de tu destino.',
|
|
40
|
+
'Compatibilidad entre dispositivos: Permite sincronizar la planificación en tablet con los sistemas del puente de mando.',
|
|
41
41
|
'Respaldo ante fallos de hardware: Las apps web ligeras son un seguro si el plotter de abordo falla.',
|
|
42
|
-
'
|
|
42
|
+
'Planificación de travesías (Passage Planning): Las calculadoras náuticas permiten estimar consumos y distancias con precisión matemática.',
|
|
43
43
|
],
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
type: 'title',
|
|
47
|
-
text: '
|
|
47
|
+
text: 'Meteorología Marina y Optimización de la Ruta',
|
|
48
48
|
level: 3,
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
type: 'paragraph',
|
|
52
|
-
html: 'El viento y el estado del mar son los verdaderos soberanos de la
|
|
52
|
+
html: 'El viento y el estado del mar son los verdaderos soberanos de la navegación a vela. La interpretación correcta de las escalas de viento (como la escala Beaufort) y su conversión a unidades prácticas como nudos es el día a día de cualquier navegante.',
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
type: 'tip',
|
|
56
56
|
title: 'La importancia de la redundancia y el Plan B',
|
|
57
|
-
html: 'Nunca
|
|
57
|
+
html: 'Nunca confíes toda tu navegación a un solo sistema electrónico. Mantén siempre una copia de seguridad de tus coordenadas críticas en papel. Las herramientas web son excelentes para la planificación previa, pero el buen marinero siempre tiene un plan de emergencia.',
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
type: 'title',
|
|
61
|
-
text: '
|
|
61
|
+
text: 'Formación Náutica y el uso de Calculadoras',
|
|
62
62
|
level: 2,
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
type: 'paragraph',
|
|
66
|
-
html: 'Incluso en los
|
|
66
|
+
html: 'Incluso en los exámenes oficiales para obtener los títulos náuticos (como el PER en España), el uso de cálculos sobre la carta sigue siendo obligatorio. Las herramientas digitales sirven como el compañero de estudio perfecto, permitiendo a los aspirantes verificar sus ejercicios de estima, cálculos de marea o correcciones de aguja magnética.',
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
type: 'paragraph',
|
|
70
|
-
html: 'Nuestra
|
|
70
|
+
html: 'Nuestra colección de utilidades busca ser ese puente entre la rigurosidad académica de las cartas de papel y la inmediatez de la navegación moderna. Queremos que cada patrón, aficionado o profesional, encuentre aquí el dato rápido y preciso que necesita antes de soltar amarras.',
|
|
71
71
|
},
|
|
72
72
|
];
|
|
73
73
|
|
package/src/category/i18n/fr.ts
CHANGED
|
@@ -3,58 +3,58 @@ import type { CategoryLocaleContent } from '../index';
|
|
|
3
3
|
const slug = 'voile-et-nautisme';
|
|
4
4
|
const title = 'Voile et Nautisme';
|
|
5
5
|
const description =
|
|
6
|
-
"Outils de calcul nautique pour les navigateurs
|
|
6
|
+
"Outils de calcul nautique pour les navigateurs: marées, autonomie, surface vélique, convertisseurs d'unités et de vitesse. Idéaux pour la préparation aux examens de plaisance.";
|
|
7
7
|
|
|
8
8
|
const seo: CategoryLocaleContent['seo'] = [
|
|
9
9
|
{
|
|
10
10
|
type: 'title',
|
|
11
|
-
text: 'Pourquoi utiliser des outils
|
|
11
|
+
text: 'Pourquoi utiliser des outils numériques en voile et navigation ?',
|
|
12
12
|
level: 2,
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: "La navigation moderne a connu une transformation radicale avec l'
|
|
16
|
+
html: "La navigation moderne a connu une transformation radicale avec l'avènement des outils numériques. Que vous soyez à bord d'une petite embarcation de plaisance ou aux commandes d'un voilier de croisière, la capacité de traiter des données de position, de vent et de courant instantanément est non seulement une question de commodité, mais un pilier fondamental de la sécurité en mer.",
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
|
20
|
-
html: "Historiquement, le marin
|
|
20
|
+
html: "Historiquement, le marin dépendait exclusivement du sextant, du chronomètre de marine et des tables de réduction pour déterminer sa position sur le vaste océan. Aujourd'hui, bien que le romantisme du sextant persiste, la réalité opérationnelle exige une rapidité de réponse que seuls les outils de calcul numériques peuvent fournir.",
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
type: 'title',
|
|
24
|
-
text: 'Aspects
|
|
24
|
+
text: 'Aspects clés pour le navigateur moderne',
|
|
25
25
|
level: 2,
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
type: 'title',
|
|
29
|
-
text: '
|
|
29
|
+
text: 'Géolocalisation et cartographie numérique de haute précision',
|
|
30
30
|
level: 3,
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
type: 'paragraph',
|
|
34
|
-
html: "La cartographie
|
|
34
|
+
html: "La cartographie numérique a démocratisé la navigation, permettant même aux petits bateaux d'accéder à des informations autrefois réservées aux grands navires commerciaux. Un skipper responsable utilise les outils numériques pour valider son jugement, non pour le remplacer.",
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
type: 'list',
|
|
38
38
|
items: [
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"Sauvegarde en cas de panne
|
|
42
|
-
"Planification de
|
|
39
|
+
"Précision de saisie des données: De petites erreurs typographiques peuvent vous éloigner de kilomètres de votre destination.",
|
|
40
|
+
"Compatibilité entre appareils: Synchronisez la planification sur tablette avec les instruments de bord.",
|
|
41
|
+
"Sauvegarde en cas de panne matérielle: Les applications web légères sont une assurance si le traceur de bord tombe en panne.",
|
|
42
|
+
"Planification de traversées: Les calculateurs nautiques permettent d'estimer avec précision les consommations et les distances.",
|
|
43
43
|
],
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
type: 'title',
|
|
47
|
-
text: '
|
|
47
|
+
text: 'Météorologie marine et optimisation de la route',
|
|
48
48
|
level: 3,
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
type: 'paragraph',
|
|
52
|
-
html: "Le vent et l'
|
|
52
|
+
html: "Le vent et l'état de la mer sont les véritables souverains de la navigation à voile. L'interprétation correcte des échelles de vent (comme l'échelle Beaufort) et leur conversion en unités pratiques comme les nœuds est le quotidien de tout navigateur.",
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
type: 'tip',
|
|
56
56
|
title: "L'importance de la redondance et du Plan B",
|
|
57
|
-
html: "Ne confiez jamais toute votre navigation
|
|
57
|
+
html: "Ne confiez jamais toute votre navigation à un seul système électronique. Conservez toujours une copie de sauvegarde de vos coordonnées critiques sur papier. Les outils web sont excellents pour la planification préalable, mais le bon marin a toujours un plan d'urgence.",
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
type: 'title',
|
|
@@ -63,11 +63,11 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
type: 'paragraph',
|
|
66
|
-
html: "
|
|
66
|
+
html: "Même dans les examens officiels pour l'obtention des titres nautiques, les calculs sur carte restent obligatoires. Les outils numériques servent de compagnon d'étude idéal, permettant aux candidats de vérifier leurs exercices d'estime, calculs de marée et corrections de compas.",
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
type: 'paragraph',
|
|
70
|
-
html: "Notre collection d'utilitaires vise
|
|
70
|
+
html: "Notre collection d'utilitaires vise à être ce pont entre la rigueur académique des cartes papier et l'immédiateté de la navigation moderne. Nous souhaitons que chaque skipper, amateur ou professionnel, trouve ici la donnée rapide et précise dont il a besoin avant de larguer les amarres.",
|
|
71
71
|
},
|
|
72
72
|
];
|
|
73
73
|
|
package/src/category/i18n/id.ts
CHANGED
|
@@ -13,7 +13,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'Navigasi modern telah mengalami transformasi radikal dengan hadirnya alat digital. Baik Anda berada di kapal rekreasi kecil maupun di kemudi kapal pesiar, kemampuan untuk memproses data posisi, angin, dan arus secara instan bukan hanya soal kenyamanan
|
|
16
|
+
html: 'Navigasi modern telah mengalami transformasi radikal dengan hadirnya alat digital. Baik Anda berada di kapal rekreasi kecil maupun di kemudi kapal pesiar, kemampuan untuk memproses data posisi, angin, dan arus secara instan bukan hanya soal kenyamanan - ini adalah pilar keselamatan fundamental di laut.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
package/src/category/i18n/it.ts
CHANGED
|
@@ -13,7 +13,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'La navigazione moderna ha subito una trasformazione radicale con l\'avvento degli strumenti digitali. Che si trovi su una piccola imbarcazione da diporto o al timone di uno yacht da crociera, la capacità di elaborare istantaneamente dati di posizione, vento e corrente non è solo una questione di comodità
|
|
16
|
+
html: 'La navigazione moderna ha subito una trasformazione radicale con l\'avvento degli strumenti digitali. Che si trovi su una piccola imbarcazione da diporto o al timone di uno yacht da crociera, la capacità di elaborare istantaneamente dati di posizione, vento e corrente non è solo una questione di comodità - è un pilastro fondamentale della sicurezza in mare.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
package/src/category/i18n/nl.ts
CHANGED
|
@@ -13,7 +13,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'Moderne navigatie heeft een radicale transformatie ondergaan met de komst van digitale tools. Of u nu op een kleine recreatievaarder bent of aan het roer van een cruisingjacht staat, de mogelijkheid om positie-, wind- en stroomgegevens direct te verwerken is niet alleen een kwestie van gemak
|
|
16
|
+
html: 'Moderne navigatie heeft een radicale transformatie ondergaan met de komst van digitale tools. Of u nu op een kleine recreatievaarder bent of aan het roer van een cruisingjacht staat, de mogelijkheid om positie-, wind- en stroomgegevens direct te verwerken is niet alleen een kwestie van gemak - het is een fundamentele pijler van veiligheid op zee.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
package/src/category/i18n/pl.ts
CHANGED
|
@@ -13,7 +13,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'Nowoczesna nawigacja przeszła radykalną transformację wraz z pojawieniem się cyfrowych narzędzi. Czy jesteś na małej łodzi rekreacyjnej, czy przy sterze jachtu rejsowego, zdolność do natychmiastowego przetwarzania danych pozycji, wiatru i prądu to nie tylko kwestia wygody
|
|
16
|
+
html: 'Nowoczesna nawigacja przeszła radykalną transformację wraz z pojawieniem się cyfrowych narzędzi. Czy jesteś na małej łodzi rekreacyjnej, czy przy sterze jachtu rejsowego, zdolność do natychmiastowego przetwarzania danych pozycji, wiatru i prądu to nie tylko kwestia wygody - jest to fundamentalny filar bezpieczeństwa na morzu.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
package/src/category/i18n/pt.ts
CHANGED
|
@@ -13,7 +13,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'A navegação moderna passou por uma transformação radical com o advento das ferramentas digitais. Quer você esteja em uma pequena embarcação de recreio ou ao leme de um iate de cruzeiro, a capacidade de processar dados de posição, vento e corrente instantaneamente não é apenas uma questão de conveniência
|
|
16
|
+
html: 'A navegação moderna passou por uma transformação radical com o advento das ferramentas digitais. Quer você esteja em uma pequena embarcação de recreio ou ao leme de um iate de cruzeiro, a capacidade de processar dados de posição, vento e corrente instantaneamente não é apenas uma questão de conveniência - é um pilar fundamental de segurança no mar.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
package/src/category/i18n/ru.ts
CHANGED
|
@@ -13,7 +13,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'Современная навигация претерпела радикальную трансформацию с появлением цифровых инструментов. Находитесь ли вы на небольшом прогулочном судне или стоите за штурвалом крейсерской яхты, способность мгновенно обрабатывать данные о позиции, ветре и течении
|
|
16
|
+
html: 'Современная навигация претерпела радикальную трансформацию с появлением цифровых инструментов. Находитесь ли вы на небольшом прогулочном судне или стоите за штурвалом крейсерской яхты, способность мгновенно обрабатывать данные о позиции, ветре и течении - это не просто вопрос удобства, а основополагающий принцип безопасности на море.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
|
@@ -38,7 +38,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
38
38
|
items: [
|
|
39
39
|
'Точность ввода данных: Минимальные опечатки могут увести вас на мили от пункта назначения.',
|
|
40
40
|
'Совместимость между устройствами: Синхронизируйте планирование перехода на планшете с навигационными приборами.',
|
|
41
|
-
'Резервная копия при отказе оборудования: Лёгкие веб-приложения
|
|
41
|
+
'Резервная копия при отказе оборудования: Лёгкие веб-приложения - это страховка на случай сбоя бортового плоттера.',
|
|
42
42
|
'Планирование перехода: Навигационные калькуляторы позволяют точно оценить расход топлива и расстояния.',
|
|
43
43
|
],
|
|
44
44
|
},
|
|
@@ -49,7 +49,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
type: 'paragraph',
|
|
52
|
-
html: 'Ветер и волнение моря
|
|
52
|
+
html: 'Ветер и волнение моря - истинные властители парусного спорта. Правильная интерпретация шкал ветра (например, шкалы Бофорта) и их перевод в практические единицы - узлы - является повседневной реальностью любого навигатора.',
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
type: 'tip',
|
package/src/category/i18n/sv.ts
CHANGED
|
@@ -13,7 +13,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'Modern navigering har genomgått en radikal omvandling med introduktionen av digitala verktyg. Oavsett om du är ombord på en liten fritidsbåt eller vid rodret på en kryssningsjakt, är förmågan att omedelbart bearbeta positions-, vind- och strömdata inte bara en bekvämlighet
|
|
16
|
+
html: 'Modern navigering har genomgått en radikal omvandling med introduktionen av digitala verktyg. Oavsett om du är ombord på en liten fritidsbåt eller vid rodret på en kryssningsjakt, är förmågan att omedelbart bearbeta positions-, vind- och strömdata inte bara en bekvämlighet - det är en grundläggande säkerhetspelare till sjöss.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
package/src/category/i18n/tr.ts
CHANGED
|
@@ -13,7 +13,7 @@ const seo: CategoryLocaleContent['seo'] = [
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
type: 'paragraph',
|
|
16
|
-
html: 'Modern navigasyon, dijital araçların ortaya çıkmasıyla köklü bir dönüşüm geçirdi. İster küçük bir eğlence teknesinde olun, ister bir kruvaziyer yatın dümenini tutun, konum, rüzgar ve akıntı verilerini anında işleme kabiliyeti yalnızca bir kolaylık meselesi değil
|
|
16
|
+
html: 'Modern navigasyon, dijital araçların ortaya çıkmasıyla köklü bir dönüşüm geçirdi. İster küçük bir eğlence teknesinde olun, ister bir kruvaziyer yatın dümenini tutun, konum, rüzgar ve akıntı verilerini anında işleme kabiliyeti yalnızca bir kolaylık meselesi değil - denizde güvenliğin temel bir direğidir.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'paragraph',
|
package/src/data.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
export { nauticalCategory } from './category';
|
|
2
2
|
export type { CategoryLocaleContent } from './category';
|
|
3
3
|
|
|
4
|
-
export { tideCalculator } from './tool/tideCalculator';
|
|
5
|
-
export { underKeel } from './tool/underKeel';
|
|
6
|
-
export { nauticalConverter } from './tool/nauticalConverter';
|
|
7
|
-
export { sailArea } from './tool/sailArea';
|
|
8
|
-
export { speedConverter } from './tool/speedConverter';
|
|
9
|
-
export { endurance } from './tool/endurance';
|
|
10
4
|
|
|
11
5
|
export type { TideCalculatorUI, TideCalculatorLocaleContent } from './tool/tideCalculator';
|
|
12
6
|
export type { UnderKeelUI, UnderKeelLocaleContent } from './tool/underKeel';
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
|
|
4
|
+
type LocaleWithDiacritics = keyof typeof DIACRITIC_RULES;
|
|
5
|
+
|
|
6
|
+
const DIACRITIC_RULES = {
|
|
7
|
+
de: {
|
|
8
|
+
language: 'German',
|
|
9
|
+
expectedCharacters: 'ä ö ü ß',
|
|
10
|
+
characters: /[äöüÄÖÜß]/g,
|
|
11
|
+
minPerThousandLetters: 0.1,
|
|
12
|
+
},
|
|
13
|
+
es: {
|
|
14
|
+
language: 'Spanish',
|
|
15
|
+
expectedCharacters: 'á é í ó ú ü ñ',
|
|
16
|
+
characters: /[áéíóúüñÁÉÍÓÚÜÑ]/g,
|
|
17
|
+
minPerThousandLetters: 0.1,
|
|
18
|
+
},
|
|
19
|
+
fr: {
|
|
20
|
+
language: 'French',
|
|
21
|
+
expectedCharacters: 'à â æ ç é è ê ë î ï ô œ ù û ü ÿ',
|
|
22
|
+
characters: /[àâæçéèêëîïôœùûüÿÀÂÆÇÉÈÊËÎÏÔŒÙÛÜŸ]/g,
|
|
23
|
+
minPerThousandLetters: 0.1,
|
|
24
|
+
},
|
|
25
|
+
it: {
|
|
26
|
+
language: 'Italian',
|
|
27
|
+
expectedCharacters: 'à è é ì í î ò ó ù ú',
|
|
28
|
+
characters: /[àèéìíîòóùúÀÈÉÌÍÎÒÓÙÚ]/g,
|
|
29
|
+
minPerThousandLetters: 0.1,
|
|
30
|
+
},
|
|
31
|
+
pl: {
|
|
32
|
+
language: 'Polish',
|
|
33
|
+
expectedCharacters: 'ą ć ę ł ń ó ś ź ż',
|
|
34
|
+
characters: /[ąćęłńóśźżĄĆĘŁŃÓŚŹŻ]/g,
|
|
35
|
+
minPerThousandLetters: 0.1,
|
|
36
|
+
},
|
|
37
|
+
pt: {
|
|
38
|
+
language: 'Portuguese',
|
|
39
|
+
expectedCharacters: 'á â ã à ç é ê í ó ô õ ú ü',
|
|
40
|
+
characters: /[áâãàçéêíóôõúüÁÂÃÀÇÉÊÍÓÔÕÚÜ]/g,
|
|
41
|
+
minPerThousandLetters: 0.1,
|
|
42
|
+
},
|
|
43
|
+
sv: {
|
|
44
|
+
language: 'Swedish',
|
|
45
|
+
expectedCharacters: 'å ä ö',
|
|
46
|
+
characters: /[åäöÅÄÖ]/g,
|
|
47
|
+
minPerThousandLetters: 0.1,
|
|
48
|
+
},
|
|
49
|
+
tr: {
|
|
50
|
+
language: 'Turkish',
|
|
51
|
+
expectedCharacters: 'ç ğ ı İ ö ş ü',
|
|
52
|
+
characters: /[çğıöşüÇĞİÖŞÜ]/g,
|
|
53
|
+
minPerThousandLetters: 0.1,
|
|
54
|
+
},
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
const LETTERS = /\p{L}/gu;
|
|
58
|
+
const TRANSLATABLE_KEYS = ['title', 'description', 'ui', 'seo', 'faq', 'howTo'] as const;
|
|
59
|
+
|
|
60
|
+
function collectStrings(value: unknown): string[] {
|
|
61
|
+
if (typeof value === 'string') return [value];
|
|
62
|
+
if (!value || typeof value !== 'object') return [];
|
|
63
|
+
if (Array.isArray(value)) return value.flatMap(collectStrings);
|
|
64
|
+
return Object.values(value).flatMap(collectStrings);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeText(value: unknown): string {
|
|
68
|
+
return collectStrings(value).join(' ').normalize('NFC');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function translatableContent(content: Record<string, unknown>) {
|
|
72
|
+
return TRANSLATABLE_KEYS.map((key) => content[key]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function letterCount(text: string): number {
|
|
76
|
+
return text.match(LETTERS)?.length ?? 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function diacriticCount(text: string, locale: LocaleWithDiacritics): number {
|
|
80
|
+
return text.match(DIACRITIC_RULES[locale].characters)?.length ?? 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function diacriticsPerThousandLetters(text: string, locale: LocaleWithDiacritics): number {
|
|
84
|
+
const letters = letterCount(text);
|
|
85
|
+
if (letters === 0) return 0;
|
|
86
|
+
return diacriticCount(text, locale) / letters * 1000;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe('Diacritics density validation', () => {
|
|
90
|
+
ALL_TOOLS.forEach((tool) => {
|
|
91
|
+
describe(`Tool: ${tool.entry.id}`, () => {
|
|
92
|
+
Object.keys(DIACRITIC_RULES).forEach((locale) => {
|
|
93
|
+
it(`${locale} keeps the expected accent and special-letter set`, async () => {
|
|
94
|
+
const typedLocale = locale as LocaleWithDiacritics;
|
|
95
|
+
const loader = tool.entry.i18n[typedLocale];
|
|
96
|
+
if (!loader) return;
|
|
97
|
+
|
|
98
|
+
const content = await loader();
|
|
99
|
+
const text = normalizeText(translatableContent(content as Record<string, unknown>));
|
|
100
|
+
const rule = DIACRITIC_RULES[typedLocale];
|
|
101
|
+
const letters = letterCount(text);
|
|
102
|
+
const matches = diacriticCount(text, typedLocale);
|
|
103
|
+
const density = diacriticsPerThousandLetters(text, typedLocale);
|
|
104
|
+
|
|
105
|
+
expect(
|
|
106
|
+
density,
|
|
107
|
+
[
|
|
108
|
+
`Possible spelling or encoding issue detected in ${tool.entry.id}/${typedLocale} (${rule.language}).`,
|
|
109
|
+
`The text has ${matches} special characters (${density.toFixed(2)} per 1000 letters, ${letters} letters analyzed).`,
|
|
110
|
+
`This locale should include some of these characters: ${rule.expectedCharacters}.`,
|
|
111
|
+
'If the count is 0 or near 0, accents, tildes, or special letters were probably stripped by encoding or normalization.',
|
|
112
|
+
].join(' '),
|
|
113
|
+
).toBeGreaterThanOrEqual(rule.minPerThousandLetters);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
|
|
4
|
+
const INVERTED_PUNCTUATION_LOCALES = {
|
|
5
|
+
es: {
|
|
6
|
+
language: 'Spanish',
|
|
7
|
+
questionStart: '¿',
|
|
8
|
+
questionEnd: '?',
|
|
9
|
+
exclamationStart: '¡',
|
|
10
|
+
exclamationEnd: '!',
|
|
11
|
+
},
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
type InvertedPunctuationLocale = keyof typeof INVERTED_PUNCTUATION_LOCALES;
|
|
15
|
+
|
|
16
|
+
const TRANSLATABLE_KEYS = ['title', 'description', 'ui', 'seo', 'faq', 'howTo'] as const;
|
|
17
|
+
|
|
18
|
+
function collectStrings(value: unknown): string[] {
|
|
19
|
+
if (typeof value === 'string') return [value];
|
|
20
|
+
if (!value || typeof value !== 'object') return [];
|
|
21
|
+
if (Array.isArray(value)) return value.flatMap(collectStrings);
|
|
22
|
+
return Object.values(value).flatMap(collectStrings);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function translatableStrings(content: Record<string, unknown>): string[] {
|
|
26
|
+
return TRANSLATABLE_KEYS.flatMap((key) => collectStrings(content[key]));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sentenceStart(text: string, endIndex: number): string {
|
|
30
|
+
const beforeMark = text.slice(0, endIndex).trimEnd();
|
|
31
|
+
const boundary = Math.max(
|
|
32
|
+
beforeMark.lastIndexOf('.'),
|
|
33
|
+
beforeMark.lastIndexOf(':'),
|
|
34
|
+
beforeMark.lastIndexOf(';'),
|
|
35
|
+
beforeMark.lastIndexOf('\n'),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return beforeMark.slice(boundary + 1).trimStart();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function findMissingInvertedMarks(
|
|
42
|
+
text: string,
|
|
43
|
+
startMark: string,
|
|
44
|
+
endMark: string,
|
|
45
|
+
): string[] {
|
|
46
|
+
return [...text.matchAll(new RegExp(`\\${endMark}`, 'g'))]
|
|
47
|
+
.map((match) => sentenceStart(text, match.index ?? 0))
|
|
48
|
+
.filter((segment) => segment.length > 0 && !segment.includes(startMark))
|
|
49
|
+
.map((segment) => `${segment}${endMark}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('Inverted punctuation validation', () => {
|
|
53
|
+
ALL_TOOLS.forEach((tool) => {
|
|
54
|
+
describe(`Tool: ${tool.entry.id}`, () => {
|
|
55
|
+
Object.keys(INVERTED_PUNCTUATION_LOCALES).forEach((locale) => {
|
|
56
|
+
it(`${locale} uses opening punctuation marks for questions and exclamations`, async () => {
|
|
57
|
+
const typedLocale = locale as InvertedPunctuationLocale;
|
|
58
|
+
const loader = tool.entry.i18n[typedLocale];
|
|
59
|
+
if (!loader) return;
|
|
60
|
+
|
|
61
|
+
const rule = INVERTED_PUNCTUATION_LOCALES[typedLocale];
|
|
62
|
+
const content = await loader();
|
|
63
|
+
const strings = translatableStrings(content as Record<string, unknown>);
|
|
64
|
+
const missingQuestions = strings.flatMap((text) =>
|
|
65
|
+
findMissingInvertedMarks(text, rule.questionStart, rule.questionEnd)
|
|
66
|
+
);
|
|
67
|
+
const missingExclamations = strings.flatMap((text) =>
|
|
68
|
+
findMissingInvertedMarks(text, rule.exclamationStart, rule.exclamationEnd)
|
|
69
|
+
);
|
|
70
|
+
const failures = [...missingQuestions, ...missingExclamations];
|
|
71
|
+
|
|
72
|
+
expect(
|
|
73
|
+
failures,
|
|
74
|
+
[
|
|
75
|
+
`Missing opening punctuation marks in ${tool.entry.id}/${typedLocale} (${rule.language}).`,
|
|
76
|
+
`Questions must use ${rule.questionStart}...${rule.questionEnd} and exclamations must use ${rule.exclamationStart}...${rule.exclamationEnd}.`,
|
|
77
|
+
`Examples: ${failures.slice(0, 5).join(' | ')}`,
|
|
78
|
+
].join(' '),
|
|
79
|
+
).toHaveLength(0);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
function getFiles(dir: string): string[] {
|
|
6
|
+
const results: string[] = [];
|
|
7
|
+
if (!fs.existsSync(dir)) {
|
|
8
|
+
return results;
|
|
9
|
+
}
|
|
10
|
+
const list = fs.readdirSync(dir);
|
|
11
|
+
for (const file of list) {
|
|
12
|
+
const fullPath = path.join(dir, file);
|
|
13
|
+
const stat = fs.statSync(fullPath);
|
|
14
|
+
if (stat && stat.isDirectory()) {
|
|
15
|
+
if (file !== 'tests' && file !== 'node_modules' && file !== '.astro') {
|
|
16
|
+
results.push(...getFiles(fullPath));
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
results.push(fullPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return results;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isContentFile(filePath: string): boolean {
|
|
26
|
+
return /\\i18n\\/.test(filePath) || filePath.endsWith('bibliography.ts');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const srcDir = path.join(process.cwd(), 'src');
|
|
30
|
+
const scriptsDir = path.join(process.cwd(), 'scripts');
|
|
31
|
+
const filesToTest = [
|
|
32
|
+
...getFiles(srcDir).filter(isContentFile),
|
|
33
|
+
...getFiles(scriptsDir).filter(isContentFile),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const aiTypographyGarbage = [
|
|
37
|
+
'\u2013',
|
|
38
|
+
'\u2014',
|
|
39
|
+
'\u2026',
|
|
40
|
+
'\u201C',
|
|
41
|
+
'\u201D',
|
|
42
|
+
'\u2018',
|
|
43
|
+
'\u2019',
|
|
44
|
+
'\u00AB',
|
|
45
|
+
'\u00BB',
|
|
46
|
+
'\u200B',
|
|
47
|
+
'\u201E',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
describe('Typography Garbage Character Validation', () => {
|
|
51
|
+
filesToTest.forEach((filePath) => {
|
|
52
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
53
|
+
it(`should not contain typography garbage characters in ${relativePath}`, () => {
|
|
54
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
55
|
+
const hasAiPatterns = aiTypographyGarbage.some(char => content.includes(char));
|
|
56
|
+
expect(hasAiPatterns).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it(`should not contain space before colon in ${relativePath}`, () => {
|
|
60
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
61
|
+
const spaceBeforeColon = / : /.test(content);
|
|
62
|
+
expect(spaceBeforeColon).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it(`should not contain double hyphen in ${relativePath}`, () => {
|
|
66
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
67
|
+
expect(content).not.toContain('--');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|