@jjlmoya/utils-cooking 1.10.0 → 1.12.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 (33) hide show
  1. package/package.json +58 -58
  2. package/src/tests/schemas_fulfillment.test.ts +23 -0
  3. package/src/tests/title_quality.test.ts +55 -0
  4. package/src/tool/american-kitchen-converter/i18n/fr.ts +1 -1
  5. package/src/tool/banana-ripeness/i18n/fr.ts +86 -86
  6. package/src/tool/brine/component.astro +20 -22
  7. package/src/tool/cookware-guide/component.astro +22 -6
  8. package/src/tool/cookware-guide/i18n/en.ts +2 -2
  9. package/src/tool/cookware-guide/i18n/es.ts +3 -3
  10. package/src/tool/cookware-guide/i18n/fr.ts +109 -110
  11. package/src/tool/ingredient-rescaler/component.astro +8 -3
  12. package/src/tool/ingredient-rescaler/i18n/en.ts +74 -97
  13. package/src/tool/ingredient-rescaler/i18n/es.ts +77 -100
  14. package/src/tool/kitchen-timer/component.astro +27 -9
  15. package/src/tool/kitchen-timer/i18n/en.ts +6 -7
  16. package/src/tool/kitchen-timer/i18n/es.ts +7 -8
  17. package/src/tool/kitchen-timer/i18n/fr.ts +76 -77
  18. package/src/tool/kitchen-timer/init.ts +23 -6
  19. package/src/tool/kitchen-timer/lib/KitchenTimer.ts +20 -8
  20. package/src/tool/meringue-peak/component.astro +4 -4
  21. package/src/tool/meringue-peak/i18n/fr.ts +1 -1
  22. package/src/tool/mold-scaler/component.astro +17 -11
  23. package/src/tool/mold-scaler/i18n/en.ts +87 -60
  24. package/src/tool/mold-scaler/i18n/es.ts +87 -61
  25. package/src/tool/mold-scaler/i18n/fr.ts +97 -69
  26. package/src/tool/pizza/i18n/en.ts +2 -2
  27. package/src/tool/pizza/i18n/es.ts +2 -2
  28. package/src/tool/pizza/i18n/fr.ts +2 -2
  29. package/src/tool/roux-guide/i18n/en.ts +18 -1
  30. package/src/tool/roux-guide/i18n/es.ts +21 -4
  31. package/src/tool/roux-guide/i18n/fr.ts +18 -1
  32. package/src/tool/roux-guide/init.ts +55 -52
  33. package/src/tool/sourdough-calculator/i18n/es.ts +133 -133
@@ -1,45 +1,45 @@
1
1
  import type { ToolLocaleContent } from '../../../types';
2
2
 
3
- const title = "Multiple Kitchen Timer";
4
- const description = "Manage multiple cooking times simultaneously. Independent alarms, ideal for chefs and kitchen organization (Mise en Place).";
3
+ const title = "Minuteur de Cuisine Multiple";
4
+ const description = "Gérez plusieurs temps de cuisson simultanément. Alarmes indépendantes, idéal pour les chefs et l'organisation de la cuisine (Mise en Place).";
5
5
  const faq = [
6
6
  {
7
- question: 'How many timers can I create?',
7
+ question: 'Combien de minuteurs puis-je créer ?',
8
8
  answer:
9
- 'Unlimited. Add as many as you need with the \'+\' button. Perfect for cooking multiple dishes simultaneously: pasta boiling, sauce reducing, meat resting, and oven baking, all at once.',
9
+ 'Illimité. Ajoutez-en autant que nécessaire avec le bouton "+". Parfait pour cuisiner plusieurs plats simultanément : bouillir des pâtes, réduire une sauce, reposer la viande et cuire au four, tout en même temps.',
10
10
  },
11
11
  {
12
- question: 'Why is resting time important for meat?',
12
+ question: 'Pourquoi le temps de repos est-il important pour la viande ?',
13
13
  answer:
14
- 'When you cut meat fresh from heat, juices escape onto the plate. If you rest 5-10 minutes, fibers relax and juices redistribute. Result: juicy meat instead of dry. Resting is passive cooking.',
14
+ 'Lorsque vous coupez la viande juste après la cuisson, les jus s\'échappent sur l\'assiette. Si vous la laissez reposer 5 à 10 minutes, les fibres se détendent et les jus se redistribuent. Résultat : une viande juteuse au lieu d\'être sèche. Le repos est une cuisson passive.',
15
15
  },
16
16
  {
17
- question: 'Does it work with screen locked?',
17
+ question: 'Fonctionne-t-il avec l\'écran verrouillé ?',
18
18
  answer:
19
- 'Yes, but you need to grant notification permissions. Timers keep running in background and will alert you with sound and browser notification even if you switch tabs or lock your phone.',
19
+ 'Oui, mais vous devez accorder les permissions de notification. Les minuteurs continuent de fonctionner en arrière-plan et vous alerteront avec un son et une notification de navigateur même si vous changez d\'onglet ou verrouillez votre téléphone.',
20
20
  },
21
21
  {
22
- question: 'What is the food \'Danger Zone\'?',
22
+ question: 'Qu\'est-ce que la "Zone de Danger" alimentaire ?',
23
23
  answer:
24
- 'Between 5°C and 65°C bacteria multiply rapidly. Cooked foods shouldn\'t stay in this range more than 2 hours (1 hour if temperature exceeds 30°C). Use a timer to monitor cooling before refrigerating.',
24
+ 'Entre 5°C et 65°C, les bactéries se multiplient rapidement. Les aliments cuits ne doivent pas rester dans cette plage plus de 2 heures (1 heure si la température dépasse 30°C). Utilisez un minuteur pour surveiller le refroidissement avant de réfrigérer.',
25
25
  },
26
26
  ];
27
27
  const howTo = [
28
28
  {
29
- name: 'Create multiple timers',
30
- text: 'Use the \'+\' button to add as many timers as you need. Perfect for orchestrating multiple dishes simultaneously.',
29
+ name: 'Créer plusieurs minuteurs',
30
+ text: 'Utilisez le bouton "+" pour ajouter autant de minuteurs que nécessaire. Parfait pour orchestrer plusieurs plats simultanément.',
31
31
  },
32
32
  {
33
- name: 'Customize each timer',
34
- text: 'Change the name of each timer to identify what\'s cooking: \'Oven\', \'Rice\', \'Sauce\', etc.',
33
+ name: 'Personnaliser chaque minuteur',
34
+ text: 'Changez le nom de chaque minuteur pour identifier ce qui cuit : "Four", "Riz", "Sauce", etc.',
35
35
  },
36
36
  {
37
- name: 'Control from mobile dock',
38
- text: 'On mobile, active timers appear in a sliding dock at the bottom. Pause or restart directly from there.',
37
+ name: 'Contrôler depuis le dock mobile',
38
+ text: 'Sur mobile, les minuteurs actifs apparaissent dans un dock coulissant en bas. Mettez en pause ou redémarrez directement depuis là.',
39
39
  },
40
40
  {
41
- name: 'Receive notifications',
42
- text: 'Authorize notifications so the browser alerts you when time\'s up, even if you switch tabs.',
41
+ name: 'Recevoir des notifications',
42
+ text: 'Autorisez les notifications pour que le navigateur vous alerte quand le temps est écoulé, même si vous changez d\'onglet.',
43
43
  },
44
44
  ];
45
45
 
@@ -76,154 +76,153 @@ const appSchema = {
76
76
  };
77
77
 
78
78
  export const content: ToolLocaleContent = {
79
- slug: 'kitchen-timer',
80
- title: 'Multiple Kitchen Timer',
79
+ slug: 'minuteur-cuisine',
80
+ title: 'Minuteur de Cuisine Multiple',
81
81
  description:
82
- 'Manage multiple cooking times simultaneously. Independent alarms, ideal for chefs and kitchen organization (Mise en Place).',
82
+ 'Gérez plusieurs temps de cuisson simultanément. Alarmes indépendantes, idéal pour les chefs et l\'organisation de la cuisine (Mise en Place).',
83
83
  faqTitle: 'Questions Fréquemment Posées',
84
84
  bibliographyTitle: 'Références',
85
85
 
86
86
  ui: {
87
- addTimer: 'Add Timer',
88
- stopAll: 'Stop All',
89
- defaultName: 'Timer',
90
- newTimerName: 'New Timer',
91
- timerDefault1: 'Timer 1',
92
- timerDefault2: 'Timer 2',
93
- timerDefault3: 'Timer 3',
94
- label: 'Label',
95
- hours: 'Hours',
87
+ addTimer: 'Ajouter',
88
+ stopAll: 'Tout Arrêter',
89
+ defaultName: 'Minuteur',
90
+ newTimerName: 'Nouveau Minuteur',
91
+ timerDefault1: 'Minuteur 1',
92
+ timerDefault2: 'Minuteur 2',
93
+ timerDefault3: 'Minuteur 3',
94
+ label: 'Étiquette',
95
+ hours: 'Heures',
96
96
  minutes: 'Min',
97
97
  seconds: 'Sec',
98
- ready: 'Ready',
99
- start: 'Start',
98
+ ready: 'Prêt',
99
+ start: 'Démarrer',
100
100
  pause: 'Pause',
101
- reset: 'Reset',
101
+ reset: 'Réinitialiser',
102
102
  addOneMin: '+1 min',
103
103
  addFiveMin: '+5 min',
104
- status: {
105
- ready: 'Ready',
106
- running: 'Running',
107
- paused: 'Paused',
108
- finished: 'TIME!',
109
- },
104
+ statusReady: 'Prêt',
105
+ statusRunning: 'En cours',
106
+ statusPaused: 'Pause',
107
+ statusFinished: 'FINI !',
108
+ finishNotification: 'Minuteur Terminé pour',
110
109
  },
111
110
 
112
111
  faq: [
113
112
  {
114
- question: 'How many timers can I create?',
113
+ question: 'Combien de minuteurs puis-je créer ?',
115
114
  answer:
116
- 'Unlimited. Add as many as you need with the \'+\' button. Perfect for cooking multiple dishes simultaneously: pasta boiling, sauce reducing, meat resting, and oven baking, all at once.',
115
+ 'Illimité. Ajoutez-en autant que nécessaire avec le bouton "+". Parfait pour cuisiner plusieurs plats simultanément : bouillir des pâtes, réduire une sauce, reposer la viande et cuire au four, tout en même temps.',
117
116
  },
118
117
  {
119
- question: 'Why is resting time important for meat?',
118
+ question: 'Pourquoi le temps de repos est-il important pour la viande ?',
120
119
  answer:
121
- 'When you cut meat fresh from heat, juices escape onto the plate. If you rest 5-10 minutes, fibers relax and juices redistribute. Result: juicy meat instead of dry. Resting is passive cooking.',
120
+ 'Lorsque vous coupez la viande juste après la cuisson, les jus s\'échappent sur l\'assiette. Si vous la laissez reposer 5 à 10 minutes, les fibres se détendent et les jus se redistribuent. Résultat : une viande juteuse au lieu d\'être sèche. Le repos est une cuisson passive.',
122
121
  },
123
122
  {
124
- question: 'Does it work with screen locked?',
123
+ question: 'Fonctionne-t-il avec l\'écran verrouillé ?',
125
124
  answer:
126
- 'Yes, but you need to grant notification permissions. Timers keep running in background and will alert you with sound and browser notification even if you switch tabs or lock your phone.',
125
+ 'Oui, mais vous devez accorder les permissions de notification. Les minuteurs continuent de fonctionner en arrière-plan et vous alerteront avec un son et une notification de navigateur même si vous changez d\'onglet ou verrouillez votre téléphone.',
127
126
  },
128
127
  {
129
- question: 'What is the food \'Danger Zone\'?',
128
+ question: 'Qu\'est-ce que la "Zone de Danger" alimentaire ?',
130
129
  answer:
131
- 'Between 5°C and 65°C bacteria multiply rapidly. Cooked foods shouldn\'t stay in this range more than 2 hours (1 hour if temperature exceeds 30°C). Use a timer to monitor cooling before refrigerating.',
130
+ 'Entre 5°C et 65°C, les bactéries se multiplient rapidement. Les aliments cuits ne doivent pas rester dans cette plage plus de 2 heures (1 heure si la température dépasse 30°C). Utilisez un minuteur pour surveiller le refroidissement avant de réfrigérer.',
132
131
  },
133
132
  ],
134
133
 
135
134
  bibliography: [
136
135
  {
137
- name: 'Food Safety: USDA Guidelines',
136
+ name: 'Sécurité Alimentaire : Directives de l\'USDA',
138
137
  url: 'https://www.fsis.usda.gov/',
139
138
  },
140
139
  {
141
- name: 'Mise en Place - Professional Cooking',
140
+ name: 'Mise en Place - Cuisine Professionnelle (Escoffier)',
142
141
  url: 'https://www.escoffier.edu/',
143
142
  },
144
143
  ],
145
144
 
146
145
  howTo: [
147
146
  {
148
- name: 'Create multiple timers',
149
- text: 'Use the \'+\' button to add as many timers as you need. Perfect for orchestrating multiple dishes simultaneously.',
147
+ name: 'Créer plusieurs minuteurs',
148
+ text: 'Utilisez le bouton "+" pour ajouter autant de minuteurs que nécessaire. Parfait pour orchestrer plusieurs plats simultanément.',
150
149
  },
151
150
  {
152
- name: 'Customize each timer',
153
- text: 'Change the name of each timer to identify what\'s cooking: \'Oven\', \'Rice\', \'Sauce\', etc.',
151
+ name: 'Personnaliser chaque minuteur',
152
+ text: 'Changez le nom de chaque minuteur pour identifier ce qui cuit : "Four", "Riz", "Sauce", etc.',
154
153
  },
155
154
  {
156
- name: 'Control from mobile dock',
157
- text: 'On mobile, active timers appear in a sliding dock at the bottom. Pause or restart directly from there.',
155
+ name: 'Contrôler depuis le dock mobile',
156
+ text: 'Sur mobile, les minuteurs actifs apparaissent dans un dock coulissant en bas. Mettez en pause ou redémarrez directement depuis là.',
158
157
  },
159
158
  {
160
- name: 'Receive notifications',
161
- text: 'Authorize notifications so the browser alerts you when time\'s up, even if you switch tabs.',
159
+ name: 'Recevoir des notifications',
160
+ text: 'Autorisez les notifications pour que le navigateur vous alerte quand le temps est écoulé, même si vous changez d\'onglet.',
162
161
  },
163
162
  ],
164
163
 
165
164
  seo: [
166
165
  {
167
166
  type: 'title',
168
- text: 'Temporal Mastery in the Kitchen',
167
+ text: 'Maîtrise Temporelle en Cuisine',
169
168
  level: 2,
170
169
  },
171
170
  {
172
171
  type: 'paragraph',
173
- html: 'Professional cooking isn\'t based solely on recipes, but on <strong>precise time management</strong>. The French concept <em>"Mise en Place"</em> (everything in its place) includes time as an ingredient. A steak that rests 5 minutes redistributes its juices and becomes tender; one that doesn\'t rest loses them at the first cut.',
172
+ html: 'La cuisine professionnelle ne repose pas uniquement sur les recettes, mais sur une <strong>gestion précise du temps</strong>. Le concept français de <em>"Mise en Place"</em> (chaque chose à sa place) inclut le temps comme ingrédient. Un steak qui repose 5 minutes redistribue ses jus et devient tendre ; un steak qui ne repose pas les perd à la première découpe.',
174
173
  },
175
174
  {
176
175
  type: 'paragraph',
177
- html: 'This <strong>Multiple Timer</strong> tool has been designed for home chefs and professionals who need to orchestrate a symphony of dishes: pasta boiling, roast in the oven, and sauce reducing, all happening simultaneously.',
176
+ html: 'Cet outil de <strong>Minuteur Multiple</strong> a été conçu pour les chefs à domicile et les professionnels qui ont besoin d\'orchestrer une symphonie de plats : bouillir des pâtes, rôti au four et réduction de sauce, tout cela se passant simultanément.',
178
177
  },
179
178
  {
180
179
  type: 'title',
181
- text: 'The Role of Temperature and Time',
180
+ text: 'Le Rôle de la Température et du Temps',
182
181
  level: 3,
183
182
  },
184
183
  {
185
184
  type: 'paragraph',
186
- html: 'Cooking is essentially applying heat for a specific duration to transform the chemical structure of food. Protein denaturation and the Maillard reaction (which gives golden color and flavor) critically depend on the timer.',
185
+ html: 'Cuisiner consiste essentiellement à appliquer de la chaleur pendant une durée spécifique pour transformer la structure chimique des aliments. La dénaturation des protéines et la réaction de Maillard (qui donne la couleur dorée et la saveur) dépendent de manière critique du minuteur.',
187
186
  },
188
187
  {
189
188
  type: 'table',
190
- headers: ['Food', 'Cooking Point', 'Time', 'Benefit'],
189
+ headers: ['Aliment', 'Point de Cuisson', 'Temps', 'Bénéfice'],
191
190
  rows: [
192
- ['Soft-boiled Eggs', 'Liquid yolk', '3-4 min', 'Soft protein, easy digestion'],
193
- ['Mollet Eggs', 'Dense yolk', '5-6 min', 'Perfect texture balance'],
194
- ['Hard-boiled Eggs', 'Fully set', '9-11 min', 'Maximum firmness'],
195
- ['Meat - Searing', 'High heat', '1-2 min/side', 'Maillard reaction, juiciness'],
196
- ['Meat - Fine Rest', 'Resting', '5 min', 'Juice redistribution'],
197
- ['Meat - Large Rest', 'Resting', '15-20 min', 'Relaxed fibers'],
191
+ ['Œufs à la coque', 'Jaune liquide', '3-4 min', 'Protéine douce, digestion facile'],
192
+ ['Œufs mollets', 'Jaune dense', '5-6 min', 'Équilibre parfait de texture'],
193
+ ['Œufs durs', 'Entièrement pris', '9-11 min', 'Fermeté maximale'],
194
+ ['Viande - Saisie', 'Feu vif', '1-2 min/face', 'Réaction de Maillard, jutosité'],
195
+ ['Viande - Repos fin', 'Repos', '5 min', 'Redistribution des jus'],
196
+ ['Viande - Grand Repos', 'Repos', '15-20 min', 'Fibres détendues'],
198
197
  ],
199
198
  },
200
199
  {
201
200
  type: 'title',
202
- text: 'Food Safety: The Danger Zone',
201
+ text: 'Sécurité Alimentaire : La Zone de Danger',
203
202
  level: 3,
204
203
  },
205
204
  {
206
205
  type: 'paragraph',
207
- html: 'Time doesn\'t just affect quality, it affects safety. The bacterial "Danger Zone" sits between 5°C and 65°C. Cooked foods must not remain in this range for more than <strong>2 hours</strong> (or 1 hour if ambient temperature exceeds 30°C). Use a timer to monitor cooling before storing food in the fridge.',
206
+ html: 'Le temps n\'affecte pas seulement la qualité, il affecte la sécurité. La "Zone de Danger" bactérienne se situe entre 5°C et 65°C. Les aliments cuits ne doivent pas rester dans cette plage pendant plus de <strong>2 heures</strong> (ou 1 heure si la température ambiante dépasse 30°C). Utilisez un minuteur pour surveiller le refroidissement avant de ranger les aliments au réfrigérateur.',
208
207
  },
209
208
  {
210
209
  type: 'title',
211
- text: 'Professional Organization Tips',
210
+ text: 'Conseils d\'Organisation Professionnelle',
212
211
  level: 3,
213
212
  },
214
213
  {
215
214
  type: 'list',
216
215
  items: [
217
- '<strong>Stagger finishing times:</strong> If everything finishes at once, you\'ll feel overwhelmed plating. Try to have side dishes ready 5 minutes before the main course.',
218
- '<strong>Use residual heat:</strong> Turn off vegetables or pasta 1-2 minutes before the timer sounds. Residual heat will finish cooking gently.',
219
- '<strong>Name your timers:</strong> In a busy kitchen it\'s easy to forget which alarm is which. Use this tool\'s renaming feature to label \'Oven\', \'Rice\', or \'Sauce\'.',
216
+ '<strong>Échelonnez les temps de fin :</strong> Si tout se termine en même temps, vous vous sentirez débordé pour le dressage. Essayez d\'avoir les accompagnements prêts 5 minutes avant le plat principal.',
217
+ '<strong>Utilisez la chaleur résiduelle :</strong> Éteignez les légumes ou les pâtes 1 à 2 minutes avant que le minuteur ne sonne. La chaleur résiduelle finira la cuisson en douceur.',
218
+ '<strong>Nommez vos minuteurs :</strong> Dans une cuisine occupée, il est facile d\'oublier quelle alarme correspond à quoi. Utilisez la fonction de renommage de cet outil pour étiqueter "Four", "Riz" ou "Sauce".',
220
219
  ],
221
220
  },
222
221
  {
223
222
  type: 'tip',
224
- html: '<strong>Professional tip:</strong> Time precision is what separates a chef from a home cook. Invest in a good timer and use it always. Experience tells you when something "looks ready", but time guarantees consistency.',
223
+ html: '<strong>Conseil de pro :</strong> La précision temporelle est ce qui sépare un chef d\'un cuisinier amateur. Investissez dans un bon minuteur et utilisez-le toujours. L\'expérience vous dit quand quelque chose "semble prêt", mais le temps garantit la régularité.',
225
224
  },
226
225
  ],
227
226
 
228
- schemas: [faqSchema, howToSchema, appSchema],
227
+ schemas: [faqSchema as any, howToSchema as any, appSchema as any],
229
228
  };
@@ -3,7 +3,8 @@ import { KitchenTimer } from "./lib/KitchenTimer";
3
3
  function setupAddTimerButton(
4
4
  timerTemplate: HTMLTemplateElement,
5
5
  timersGrid: HTMLElement,
6
- activeTimers: KitchenTimer[]
6
+ activeTimers: KitchenTimer[],
7
+ ui: Record<string, string>
7
8
  ) {
8
9
  document.getElementById("add-timer-btn")?.addEventListener("click", () => {
9
10
  if (!timerTemplate || !timersGrid) return;
@@ -14,11 +15,19 @@ function setupAddTimerButton(
14
15
  const count = activeTimers.length + 1;
15
16
  newCard.setAttribute("data-index", count.toString());
16
17
  const nameInput = newCard.querySelector(".timer-name") as HTMLInputElement;
17
- if (nameInput) nameInput.value = `Temporizador ${count}`;
18
+ if (nameInput) nameInput.value = `${ui.defaultName} ${count}`;
18
19
 
19
20
  timersGrid.appendChild(newCard);
20
21
 
21
- const timer = new KitchenTimer(newCard);
22
+ const timer = new KitchenTimer(newCard, {
23
+ ready: ui.statusReady as string,
24
+ running: ui.statusRunning as string,
25
+ paused: ui.statusPaused as string,
26
+ finished: ui.statusFinished as string,
27
+ start: ui.start as string,
28
+ pause: ui.pause as string,
29
+ finishNotification: ui.finishNotification as string
30
+ });
22
31
  activeTimers.push(timer);
23
32
 
24
33
  newCard.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -39,17 +48,25 @@ function requestNotificationPermission() {
39
48
  }
40
49
  }
41
50
 
42
- export function initKitchenTimers() {
51
+ export function initKitchenTimers(ui: Record<string, string>) {
43
52
  const timersGrid = document.getElementById("timers-grid") as HTMLElement;
44
53
  const timerTemplate = document.getElementById("timer-template") as HTMLTemplateElement;
45
54
  const activeTimers: KitchenTimer[] = [];
46
55
 
47
56
  document.querySelectorAll(".timer-card").forEach((card) => {
48
- const timer = new KitchenTimer(card as HTMLElement);
57
+ const timer = new KitchenTimer(card as HTMLElement, {
58
+ ready: ui.statusReady as string,
59
+ running: ui.statusRunning as string,
60
+ paused: ui.statusPaused as string,
61
+ finished: ui.statusFinished as string,
62
+ start: ui.start as string,
63
+ pause: ui.pause as string,
64
+ finishNotification: ui.finishNotification as string
65
+ });
49
66
  activeTimers.push(timer);
50
67
  });
51
68
 
52
- setupAddTimerButton(timerTemplate, timersGrid, activeTimers);
69
+ setupAddTimerButton(timerTemplate, timersGrid, activeTimers, ui);
53
70
  setupStopAllButton(activeTimers);
54
71
  requestNotificationPermission();
55
72
  }
@@ -1,5 +1,15 @@
1
1
  import { getAudioContext, playBeep } from "./AudioHelper";
2
2
 
3
+ export interface TimerTranslations {
4
+ ready: string;
5
+ running: string;
6
+ paused: string;
7
+ finished: string;
8
+ start: string;
9
+ pause: string;
10
+ finishNotification: string;
11
+ }
12
+
3
13
  export class KitchenTimer extends EventTarget {
4
14
  element: HTMLElement;
5
15
  private inputs: { h: HTMLInputElement; m: HTMLInputElement; s: HTMLInputElement };
@@ -9,6 +19,7 @@ export class KitchenTimer extends EventTarget {
9
19
  private btnAdd5m: HTMLButtonElement;
10
20
  private statusText: HTMLElement;
11
21
  private timerNameInput: HTMLInputElement;
22
+ private t: TimerTranslations;
12
23
 
13
24
  totalSeconds: number = 0;
14
25
  remainingSeconds: number = 0;
@@ -17,9 +28,10 @@ export class KitchenTimer extends EventTarget {
17
28
 
18
29
  private audioContext: AudioContext | null = null;
19
30
 
20
- constructor(element: HTMLElement) {
31
+ constructor(element: HTMLElement, translations: TimerTranslations) {
21
32
  super();
22
33
  this.element = element;
34
+ this.t = translations;
23
35
 
24
36
  const hoursInput = element.querySelector(".hours") as HTMLInputElement | null;
25
37
  const minutesInput = element.querySelector(".minutes") as HTMLInputElement | null;
@@ -153,7 +165,7 @@ export class KitchenTimer extends EventTarget {
153
165
  this.setDisplay(0);
154
166
  this.remainingSeconds = 0;
155
167
  this.totalSeconds = 0;
156
- this.statusText.textContent = "Listo";
168
+ this.statusText.textContent = this.t.ready;
157
169
  this.statusText.classList.remove("running", "finished");
158
170
  this.element.classList.remove("finished");
159
171
  this.dispatchUpdate();
@@ -173,12 +185,12 @@ export class KitchenTimer extends EventTarget {
173
185
  private timeUp() {
174
186
  this.pause();
175
187
  this.playAlarm();
176
- this.statusText.textContent = "¡TIEMPO!";
188
+ this.statusText.textContent = this.t.finished;
177
189
  this.statusText.classList.add("finished");
178
190
  this.element.classList.add("finished");
179
191
 
180
192
  if ("Notification" in window && Notification.permission === "granted") {
181
- new Notification(`Temporizador Terminado de ${this.getName()}`);
193
+ new Notification(`${this.t.finishNotification} ${this.getName()}`);
182
194
  }
183
195
  this.dispatchUpdate();
184
196
  }
@@ -217,12 +229,12 @@ export class KitchenTimer extends EventTarget {
217
229
  const btnText = this.element.querySelector(".btn-toggle .btn-text");
218
230
  const iconPlay = this.element.querySelector(".icon-play");
219
231
  const iconPause = this.element.querySelector(".icon-pause");
220
- if (btnText) btnText.textContent = "Pausar";
232
+ if (btnText) btnText.textContent = this.t.pause;
221
233
  iconPlay?.setAttribute("style", "display: none;");
222
234
  iconPause?.removeAttribute("style");
223
235
  this.element.classList.add("running");
224
236
  this.statusText.classList.add("running");
225
- this.statusText.textContent = "Corriendo";
237
+ this.statusText.textContent = this.t.running;
226
238
  Object.values(this.inputs).forEach((i) => (i.disabled = true));
227
239
  }
228
240
 
@@ -230,12 +242,12 @@ export class KitchenTimer extends EventTarget {
230
242
  const btnText = this.element.querySelector(".btn-toggle .btn-text");
231
243
  const iconPlay = this.element.querySelector(".icon-play");
232
244
  const iconPause = this.element.querySelector(".icon-pause");
233
- if (btnText) btnText.textContent = "Iniciar";
245
+ if (btnText) btnText.textContent = this.t.start;
234
246
  iconPlay?.removeAttribute("style");
235
247
  iconPause?.setAttribute("style", "display: none;");
236
248
  this.element.classList.remove("running");
237
249
  this.statusText.classList.remove("running");
238
- this.statusText.textContent = this.remainingSeconds > 0 && this.remainingSeconds < this.totalSeconds ? "Pausado" : "Listo";
250
+ this.statusText.textContent = this.remainingSeconds > 0 && this.remainingSeconds < this.totalSeconds ? this.t.paused : this.t.ready;
239
251
  Object.values(this.inputs).forEach((i) => (i.disabled = false));
240
252
  }
241
253
 
@@ -334,7 +334,7 @@ const { ui } = Astro.props;
334
334
  gap: 1.5rem;
335
335
  }
336
336
 
337
- .meringue-result-item {
337
+ :global(.meringue-result-item) {
338
338
  background: var(--time-bg);
339
339
  border: 1px solid var(--card-border);
340
340
  border-radius: 20px;
@@ -345,7 +345,7 @@ const { ui } = Astro.props;
345
345
  transition: all 0.2s ease;
346
346
  }
347
347
 
348
- .meringue-result-item:hover {
348
+ :global(.meringue-result-item):hover {
349
349
  background: var(--card-bg);
350
350
  transform: scale(1.02);
351
351
  }
@@ -384,7 +384,7 @@ const { ui } = Astro.props;
384
384
  gap: 1rem;
385
385
  }
386
386
 
387
- .meringue-time-row {
387
+ :global(.meringue-time-row) {
388
388
  display: flex;
389
389
  align-items: center;
390
390
  justify-content: space-between;
@@ -450,7 +450,7 @@ const { ui } = Astro.props;
450
450
  color: var(--pink-light);
451
451
  }
452
452
 
453
- .meringue-fade-in {
453
+ :global(.meringue-fade-in) {
454
454
  animation: meringue-fade-in 0.5s ease forwards;
455
455
  }
456
456
 
@@ -262,7 +262,7 @@ export const content: ToolLocaleContent = {
262
262
  {
263
263
  type: "diagnostic",
264
264
  variant: "warning",
265
- title: "Votre Meringue rejette-t-elle du Liquide ?",
265
+ title: "Votre Meringue rejette t elle du Liquide ?",
266
266
  html: "Si vous voyez du sirop s'échapper (synérèse), c'est soit que le sucre n'était pas dissous, soit que l'humidité ambiante est trop forte. Pour l'italienne, versez le sirop en filet constant, jamais directement sur le fouet.",
267
267
  },
268
268
  {
@@ -274,13 +274,13 @@ const { ui } = Astro.props;
274
274
  width: 2.5rem;
275
275
  }
276
276
 
277
- .ms-input-group {
277
+ :global(.ms-input-group) {
278
278
  display: flex;
279
279
  flex-direction: column;
280
280
  gap: 0.5rem;
281
281
  }
282
282
 
283
- .ms-label {
283
+ :global(.ms-label) {
284
284
  font-size: 0.75rem;
285
285
  font-weight: 700;
286
286
  color: var(--ms-text-muted);
@@ -288,7 +288,7 @@ const { ui } = Astro.props;
288
288
  letter-spacing: 0.05em;
289
289
  }
290
290
 
291
- .ms-input {
291
+ :global(.ms-input) {
292
292
  width: 100%;
293
293
  padding: 0.75rem 1rem;
294
294
  border-radius: 0.5rem;
@@ -299,7 +299,7 @@ const { ui } = Astro.props;
299
299
  transition: var(--ms-transition);
300
300
  }
301
301
 
302
- .ms-input:focus {
302
+ :global(.ms-input):focus {
303
303
  outline: none;
304
304
  border-color: var(--ms-primary);
305
305
  box-shadow: 0 0 0 3px hsla(262deg, 83%, 58%, 0.1);
@@ -309,7 +309,7 @@ const { ui } = Astro.props;
309
309
  text-align: center;
310
310
  padding: 2.5rem;
311
311
  background: linear-gradient(135deg, var(--ms-primary), var(--ms-primary-dark));
312
- color: var(--ms-bg-card);
312
+ color: hsl(0deg, 0%, 100%);
313
313
  border-radius: var(--ms-radius);
314
314
  box-shadow: var(--ms-shadow-md);
315
315
  position: relative;
@@ -402,7 +402,7 @@ const { ui } = Astro.props;
402
402
  margin-bottom: 1.5rem;
403
403
  }
404
404
 
405
- .ms-ingredient-row {
405
+ :global(.ms-ingredient-row) {
406
406
  display: grid;
407
407
  grid-template-columns: 1fr;
408
408
  gap: 0.75rem;
@@ -426,13 +426,13 @@ const { ui } = Astro.props;
426
426
  }
427
427
 
428
428
  @media (min-width: 640px) {
429
- .ms-ingredient-row {
429
+ :global(.ms-ingredient-row) {
430
430
  grid-template-columns: 2fr 1fr 1fr auto;
431
431
  align-items: center;
432
432
  }
433
433
  }
434
434
 
435
- .ms-ingredient-final {
435
+ :global(.ms-ingredient-final) {
436
436
  font-weight: 800;
437
437
  color: hsl(150deg, 80%, 40%);
438
438
  font-size: 1.125rem;
@@ -440,7 +440,7 @@ const { ui } = Astro.props;
440
440
  text-align: center;
441
441
  }
442
442
 
443
- .ms-del-btn {
443
+ :global(.ms-del-btn) {
444
444
  background: transparent;
445
445
  border: none;
446
446
  color: var(--ms-text-muted);
@@ -450,7 +450,7 @@ const { ui } = Astro.props;
450
450
  transition: var(--ms-transition);
451
451
  }
452
452
 
453
- .ms-del-btn:hover {
453
+ :global(.ms-del-btn):hover {
454
454
  background: hsl(0deg, 85%, 60%);
455
455
  color: var(--ms-bg-card);
456
456
  }
@@ -476,11 +476,17 @@ const { ui } = Astro.props;
476
476
  border-style: solid;
477
477
  }
478
478
 
479
- .ms-empty-state {
479
+ :global(.ms-empty-state) {
480
480
  text-align: center;
481
481
  padding: 2rem;
482
482
  color: var(--ms-text-muted);
483
483
  font-style: italic;
484
484
  }
485
+
486
+ :global(.ms-inputs-grid) {
487
+ display: grid;
488
+ grid-template-columns: 1fr 1fr;
489
+ gap: 1rem;
490
+ }
485
491
  </style>
486
492