@jjlmoya/utils-alcohol 1.16.0 → 1.19.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-alcohol",
3
- "version": "1.16.0",
3
+ "version": "1.19.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,19 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'alkohol-party',
5
+ title: 'Alkohol und Party Dienstprogramme',
6
+ description: 'Tools zur Berechnung des Alkoholpegels, Getränkekühlung und Veranstaltungsplanung',
7
+ seo: [
8
+ {
9
+ type: 'summary',
10
+ title: 'Wissenschaft und Feier',
11
+ items: [
12
+ 'Berechnen Sie die perfekte Balance für Ihre Cocktails',
13
+ 'Kühlen Sie Ihre Getränke in Rekordzeit mit physikalischer Präzision',
14
+ 'Planen Sie Fass- und Eisbestände für Ihre Veranstaltungen',
15
+ 'Schätzen Sie Ihren Stoffwechsel und Ihre Erholungszeit',
16
+ ],
17
+ },
18
+ ],
19
+ };
@@ -0,0 +1,19 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'alcol-festa',
5
+ title: 'Utilità per alcolici e feste',
6
+ description: 'Strumenti per il calcolo del tasso alcolemico, il raffreddamento delle bevande e la pianificazione di eventi',
7
+ seo: [
8
+ {
9
+ type: 'summary',
10
+ title: 'Scienza e Celebrazione',
11
+ items: [
12
+ 'Calcola il bilanciamento perfetto per i tuoi cocktail',
13
+ 'Raffredda le tue bevande a tempo di record con precisione fisica',
14
+ 'Pianifica le scorte di fusti e ghiaccio per i tuoi eventi',
15
+ 'Stima il tuo metabolismo e il tempo de recupero',
16
+ ],
17
+ },
18
+ ],
19
+ };
@@ -0,0 +1,19 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'alcohol-party',
5
+ title: 'アルコールおよびパーティーユーティリティ',
6
+ description: 'アルコール度数計算、飲料冷却、イベント計画のためのツール',
7
+ seo: [
8
+ {
9
+ type: 'summary',
10
+ title: '科学とセレブレーション',
11
+ items: [
12
+ 'カクテルの完璧なバランスを計算する',
13
+ '物理的な精度で記録的な速さで飲み物を冷やす',
14
+ 'イベントの樽と氷の在庫を計画する',
15
+ '代謝と回復時間を推定する',
16
+ ],
17
+ },
18
+ ],
19
+ };
@@ -0,0 +1,19 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'alcohol-party',
5
+ title: '알코올 및 파티 유틸리티',
6
+ description: '알코올 농도 계산, 음료 냉각 및 행사 계획을 위한 도구',
7
+ seo: [
8
+ {
9
+ type: 'summary',
10
+ title: '과학과 축하',
11
+ items: [
12
+ '칵테일의 완벽한 밸런스를 계산하세요',
13
+ '물리적 정밀도로 기록적인 속도로 음료를 냉각하세요',
14
+ '행사를 위한 케그와 얼음 재고를 계획하세요',
15
+ '대사 및 회복 시간을 추정하세요',
16
+ ],
17
+ },
18
+ ],
19
+ };
@@ -0,0 +1,19 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'alcohol-feest',
5
+ title: 'Alcohol en feesthulpmiddelen',
6
+ description: 'Tools voor het berekenen van alcoholpromillage, drankkoeling en evenementenplanning',
7
+ seo: [
8
+ {
9
+ type: 'summary',
10
+ title: 'Wetenschap en Viering',
11
+ items: [
12
+ 'Bereken de perfecte balans voor uw cocktails',
13
+ 'Koel uw drankjes in recordtijd met fysieke precisie',
14
+ 'Plan fust- en ijsvoorraad voor uw evenementen',
15
+ 'Schat uw metabolisme en hersteltijd in',
16
+ ],
17
+ },
18
+ ],
19
+ };
@@ -0,0 +1,19 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'alkohol-impreza',
5
+ title: 'Narzędzia alkoholowe i imprezowe',
6
+ description: 'Narzędzia do obliczania poziomu alkoholu, chłodzenia napojów i planowania imprez',
7
+ seo: [
8
+ {
9
+ type: 'summary',
10
+ title: 'Nauka i Świętowanie',
11
+ items: [
12
+ 'Oblicz idealną równowagę dla swoich koktajli',
13
+ 'Schłodź swoje napoje w rekordowym czasie z fizyczną precyzją',
14
+ 'Zaplanuj zapasy beczek i lodu na swoje wydarzenia',
15
+ 'Oszacuj swój metabolizm i czas regeneracji',
16
+ ],
17
+ },
18
+ ],
19
+ };
@@ -0,0 +1,19 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'alcool-festas',
5
+ title: 'Utilitários de álcool e festas',
6
+ description: 'Ferramentas para calcular níveis de álcool, resfriamento de bebidas e planejamento de eventos',
7
+ seo: [
8
+ {
9
+ type: 'summary',
10
+ title: 'Ciência e Celebração',
11
+ items: [
12
+ 'Calcule o equilíbrio perfeito para seus coquetéis',
13
+ 'Resfrie suas bebidas em tempo recorde com precisão física',
14
+ 'Planeje o estoque de barris e gelo para seus eventos',
15
+ 'Estime seu metabolismo e tempo de recuperação',
16
+ ],
17
+ },
18
+ ],
19
+ };
@@ -0,0 +1,19 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'alkogol-vecherinka',
5
+ title: 'Алкогольные и праздничные утилиты',
6
+ description: 'Инструменты для расчета уровня алкоголя, охлаждения напитков и планирования мероприятий',
7
+ seo: [
8
+ {
9
+ type: 'summary',
10
+ title: 'Наука и Праздник',
11
+ items: [
12
+ 'Рассчитайте идеальный баланс для ваших коктейлей',
13
+ 'Охлаждайте напитки в рекордные сроки с физической точностью',
14
+ 'Планируйте запас кег и льда для ваших мероприятий',
15
+ 'Оцените свой метаболизм и время восстановления',
16
+ ],
17
+ },
18
+ ],
19
+ };
@@ -0,0 +1,19 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'alkol-partisi',
5
+ title: 'Alkol ve Parti Araçları',
6
+ description: 'Alkol seviyesi hesaplama, içecek soğutma ve etkinlik planlama araçları',
7
+ seo: [
8
+ {
9
+ type: 'summary',
10
+ title: 'Bilim ve Kutlama',
11
+ items: [
12
+ 'Kokteylleriniz için mükemmel dengeyi hesaplayın',
13
+ 'Fiziksel hassasiyetle içeceklerinizi rekor sürede soğutun',
14
+ 'Etkinlikleriniz için fıçı ve buz stoklarını planlayın',
15
+ 'Metabolizmanızı ve iyileşme sürenizi tahmin edin',
16
+ ],
17
+ },
18
+ ],
19
+ };
@@ -0,0 +1,19 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'alcohol-party',
5
+ title: '酒精与派对实用工具',
6
+ description: '用于计算酒精含量、饮料冷却和活动规划的工具',
7
+ seo: [
8
+ {
9
+ type: 'summary',
10
+ title: '科学与庆典',
11
+ items: [
12
+ '为您的鸡尾酒计算完美的平衡',
13
+ '利用物理精度在创纪录的时间内冷却您的饮料',
14
+ '为您的活动规划桶装啤酒和冰块库存',
15
+ '估算您的新陈代谢和恢复时间',
16
+ ],
17
+ },
18
+ ],
19
+ };
@@ -4,11 +4,22 @@ export const alcoholCategory: AlcoholCategoryEntry = {
4
4
  icon: 'mdi:shape',
5
5
  tools: [],
6
6
  i18n: {
7
- es: () => import('./i18n/es').then((m) => m.content),
7
+ de: () => import('./i18n/de').then((m) => m.content),
8
8
  en: () => import('./i18n/en').then((m) => m.content),
9
+ es: () => import('./i18n/es').then((m) => m.content),
9
10
  fr: () => import('./i18n/fr').then((m) => m.content),
10
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),
11
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),
12
22
  },
13
23
  };
14
24
 
25
+ export const toolsCategory = alcoholCategory;
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { toolsCategory } from '../category/index';
3
+ import type { CategoryLocaleContent } from '../types';
4
+
5
+ const EXPECTED_LOCALES = [
6
+ 'de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'
7
+ ];
8
+
9
+ const sharingLocales = ['ja', 'ko', 'zh'];
10
+
11
+ describe('Category Validation', () => {
12
+ it('should have all 15 required locales', () => {
13
+ const registeredLocales = Object.keys(toolsCategory.i18n);
14
+ EXPECTED_LOCALES.forEach((locale) => {
15
+ expect(
16
+ registeredLocales,
17
+ `Category is missing locale "${locale}"`,
18
+ ).toContain(locale);
19
+ });
20
+ });
21
+
22
+ describe('Category Slug Validation', () => {
23
+ it('every locale should have a unique, translated slug and follow format rules', async () => {
24
+ const slugs = new Map<string, string>();
25
+ const locales = Object.keys(toolsCategory.i18n);
26
+
27
+ let enSlug = '';
28
+ if (locales.includes('en')) {
29
+ const enLoader = toolsCategory.i18n['en' as keyof typeof toolsCategory.i18n];
30
+ const enContent = (await enLoader?.()) as CategoryLocaleContent;
31
+ enSlug = enContent.slug;
32
+ }
33
+
34
+ for (const locale of locales) {
35
+ const loader = toolsCategory.i18n[locale as keyof typeof toolsCategory.i18n];
36
+ const content = (await loader?.()) as CategoryLocaleContent;
37
+
38
+ expect(
39
+ content.slug,
40
+ `Category locale "${locale}" has an invalid slug ("${content.slug}"). Slugs must be transliterated (only a-z, 0-9, and -).`,
41
+ ).toMatch(/^[a-z0-9-]+$/);
42
+
43
+ expect(
44
+ content.slug,
45
+ `Category locale "${locale}" slug ("${content.slug}") cannot end with a 2-letter language code (e.g., -ja, -ru, -ko).`,
46
+ ).not.toMatch(/-[a-z]{2}$/);
47
+
48
+ if (locale !== 'en') {
49
+ if (sharingLocales.includes(locale)) {
50
+ expect(
51
+ content.slug,
52
+ `Category locale "${locale}" must use the same slug as "en" ("${enSlug}").`,
53
+ ).toBe(enSlug);
54
+ } else {
55
+ expect(
56
+ content.slug,
57
+ `Category locale "${locale}" has the same slug as "en" ("${enSlug}"). Cada slug tiene que estar en su propio idioma`,
58
+ ).not.toBe(enSlug);
59
+
60
+ if (slugs.has(content.slug)) {
61
+ const previousLocale = slugs.get(content.slug);
62
+ expect(
63
+ false,
64
+ `Category locales "${locale}" and "${previousLocale}" share the same slug ("${content.slug}"). Cada slug tiene que estar en su propia idioma`,
65
+ ).toBe(true);
66
+ }
67
+ slugs.set(content.slug, locale);
68
+ }
69
+ }
70
+ }
71
+ });
72
+ });
73
+ });
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import type { ToolLocaleContent } from '../types';
4
+
5
+ describe('Slug Language Code Format Validation', () => {
6
+ ALL_TOOLS.forEach((tool) => {
7
+ describe(`Tool: ${tool.entry.id}`, () => {
8
+ it('slug should not end with 2-letter language codes like -ja, -ru, -ko', async () => {
9
+ const locales = Object.keys(tool.entry.i18n);
10
+
11
+ for (const locale of locales) {
12
+ const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
13
+ const content = (await loader?.()) as ToolLocaleContent;
14
+
15
+ expect(
16
+ content.slug,
17
+ `Tool "${tool.entry.id}" locale "${locale}" slug ("${content.slug}") cannot end with a 2-letter language code (e.g., -ja, -ru, -ko).`,
18
+ ).not.toMatch(/-[a-z]{2}$/);
19
+ }
20
+ });
21
+ });
22
+ });
23
+ });
@@ -1,7 +1,7 @@
1
1
  import type { WithContext, SoftwareApplication, FAQPage, HowTo } from 'schema-dts';
2
2
  import type { CocktailBalancerUI, CocktailBalancerLocaleContent } from '../index';
3
3
 
4
- const slug = 'cocktail-balancer-nl';
4
+ const slug = 'cocktail-evenwicht';
5
5
  const title = 'Cocktail Balancer: De Zuurwet';
6
6
  const description = 'Bereken het perfecte evenwicht tussen zoet en zuur voor je cocktails. Beheers de gulden snede van mixologie.';
7
7