@jjlmoya/utils-cooking 1.36.0 → 1.37.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 (57) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +4 -0
  3. package/src/entries.ts +5 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/brix-sorbet-density-calculator.test.ts +53 -0
  6. package/src/tests/i18n-titles.test.ts +2 -2
  7. package/src/tests/locale_completeness.test.ts +2 -2
  8. package/src/tests/tool_validation.test.ts +2 -2
  9. package/src/tool/brix-sorbet-density-calculator/bibliography.astro +6 -0
  10. package/src/tool/brix-sorbet-density-calculator/bibliography.ts +10 -0
  11. package/src/tool/brix-sorbet-density-calculator/brix-sorbet-density-calculator.css +878 -0
  12. package/src/tool/brix-sorbet-density-calculator/component.astro +220 -0
  13. package/src/tool/brix-sorbet-density-calculator/entry.ts +26 -0
  14. package/src/tool/brix-sorbet-density-calculator/helpers.ts +102 -0
  15. package/src/tool/brix-sorbet-density-calculator/i18n/de.ts +295 -0
  16. package/src/tool/brix-sorbet-density-calculator/i18n/en.ts +295 -0
  17. package/src/tool/brix-sorbet-density-calculator/i18n/es.ts +295 -0
  18. package/src/tool/brix-sorbet-density-calculator/i18n/fr.ts +295 -0
  19. package/src/tool/brix-sorbet-density-calculator/i18n/id.ts +295 -0
  20. package/src/tool/brix-sorbet-density-calculator/i18n/it.ts +295 -0
  21. package/src/tool/brix-sorbet-density-calculator/i18n/ja.ts +295 -0
  22. package/src/tool/brix-sorbet-density-calculator/i18n/ko.ts +295 -0
  23. package/src/tool/brix-sorbet-density-calculator/i18n/nl.ts +295 -0
  24. package/src/tool/brix-sorbet-density-calculator/i18n/pl.ts +295 -0
  25. package/src/tool/brix-sorbet-density-calculator/i18n/pt.ts +295 -0
  26. package/src/tool/brix-sorbet-density-calculator/i18n/ru.ts +295 -0
  27. package/src/tool/brix-sorbet-density-calculator/i18n/sv.ts +295 -0
  28. package/src/tool/brix-sorbet-density-calculator/i18n/tr.ts +295 -0
  29. package/src/tool/brix-sorbet-density-calculator/i18n/zh.ts +295 -0
  30. package/src/tool/brix-sorbet-density-calculator/index.ts +11 -0
  31. package/src/tool/brix-sorbet-density-calculator/logic.ts +180 -0
  32. package/src/tool/brix-sorbet-density-calculator/script.ts +114 -0
  33. package/src/tool/brix-sorbet-density-calculator/seo.astro +15 -0
  34. package/src/tool/oil-smoke-point-tracker/bibliography.astro +6 -0
  35. package/src/tool/oil-smoke-point-tracker/bibliography.ts +10 -0
  36. package/src/tool/oil-smoke-point-tracker/component.astro +445 -0
  37. package/src/tool/oil-smoke-point-tracker/entry.ts +26 -0
  38. package/src/tool/oil-smoke-point-tracker/i18n/de.ts +285 -0
  39. package/src/tool/oil-smoke-point-tracker/i18n/en.ts +285 -0
  40. package/src/tool/oil-smoke-point-tracker/i18n/es.ts +285 -0
  41. package/src/tool/oil-smoke-point-tracker/i18n/fr.ts +285 -0
  42. package/src/tool/oil-smoke-point-tracker/i18n/id.ts +244 -0
  43. package/src/tool/oil-smoke-point-tracker/i18n/it.ts +244 -0
  44. package/src/tool/oil-smoke-point-tracker/i18n/ja.ts +244 -0
  45. package/src/tool/oil-smoke-point-tracker/i18n/ko.ts +244 -0
  46. package/src/tool/oil-smoke-point-tracker/i18n/nl.ts +244 -0
  47. package/src/tool/oil-smoke-point-tracker/i18n/pl.ts +244 -0
  48. package/src/tool/oil-smoke-point-tracker/i18n/pt.ts +244 -0
  49. package/src/tool/oil-smoke-point-tracker/i18n/ru.ts +244 -0
  50. package/src/tool/oil-smoke-point-tracker/i18n/sv.ts +244 -0
  51. package/src/tool/oil-smoke-point-tracker/i18n/tr.ts +244 -0
  52. package/src/tool/oil-smoke-point-tracker/i18n/zh.ts +244 -0
  53. package/src/tool/oil-smoke-point-tracker/index.ts +11 -0
  54. package/src/tool/oil-smoke-point-tracker/logic.ts +70 -0
  55. package/src/tool/oil-smoke-point-tracker/oil-smoke-point-tracker.css +937 -0
  56. package/src/tool/oil-smoke-point-tracker/seo.astro +15 -0
  57. package/src/tools.ts +4 -0
@@ -0,0 +1,244 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import { bibliography } from '../bibliography';
3
+
4
+ const title = '食用油烟点追踪器:油炸用油寿命与降解估算';
5
+ const description = '监控油炸用油品质,估算当前烟点下降幅度。追踪使用次数、温度和食材类型,防止有害极性化合物累积。';
6
+
7
+ const faq = [
8
+ {
9
+ question: '为什么油炸用油的烟点会随着时间下降?',
10
+ answer: '每次将油加热到油炸温度时,热降解、水解和氧化反应会将甘油三酯分解为游离脂肪酸(FFA)、单酰甘油和二酰甘油。这些分解产物的沸点和烟点远低于完整的甘油三酯,因此随着重复使用,油在更低的温度就会开始冒烟。',
11
+ },
12
+ {
13
+ question: '什么是极性化合物?为什么受到监管?',
14
+ answer: '极性化合物(TPC)是油炸过程中形成的降解产物。当它们超过油重的25%时,该油被视为已降解、氧化,食用有害,会产生异味并带来潜在的心血管风险。许多欧洲国家在法律上强制规定,当TPC超过25%时必须废弃油炸用油。',
15
+ },
16
+ {
17
+ question: '裹粉或面粉如何影响油脂降解?',
18
+ answer: '裹粉、面糊和散落的面粉会将食物颗粒掉入热油中。这些颗粒在超过180摄氏度的温度下会迅速焦化和碳化,释放游离脂肪酸,并作为催化剂加速热降解。土豆等清洁淀粉类食材对油脂的降解速度则慢得多。',
19
+ },
20
+ {
21
+ question: '食用油重复使用的安全限度是多少?',
22
+ answer: '一般来说,精炼的高温油(如菜籽油或花生油)在清洁条件下可重复使用5至8次。但未精炼的油,或在超过190摄氏度的高温下使用的油,或用于油炸裹粉食品的油,应在使用1至3次后废弃。',
23
+ },
24
+ ];
25
+
26
+ const howTo = [
27
+ {
28
+ name: '选择油炸用油类型',
29
+ text: '从数据库列表中选择您的油品。精炼高温油的起始烟点高于未精炼的选项。',
30
+ },
31
+ {
32
+ name: '追踪油炸次数',
33
+ text: '输入当前批次油已经历的油炸总次数。',
34
+ },
35
+ {
36
+ name: '输入油炸温度',
37
+ text: '调整滑块以匹配您的油炸平均温度。超过180摄氏度的温度会加速油脂分解。',
38
+ },
39
+ {
40
+ name: '识别食材裹层类型',
41
+ text: '指定您是油炸清洁淀粉类食材,还是裹粉/面包糠类食材(后者会留下碳化残渣)。',
42
+ },
43
+ {
44
+ name: '查看烟点与废弃状态',
45
+ text: '查看已降解的烟点和废弃指示。如果油品健康进入危险区域,请立即废弃。',
46
+ },
47
+ ];
48
+
49
+ const faqSchema = {
50
+ '@context': 'https://schema.org' as const,
51
+ '@type': 'FAQPage' as const,
52
+ mainEntity: faq.map((item) => ({
53
+ '@type': 'Question' as const,
54
+ name: item.question,
55
+ acceptedAnswer: { '@type': 'Answer' as const, text: item.answer },
56
+ })),
57
+ };
58
+
59
+ const howToSchema = {
60
+ '@context': 'https://schema.org' as const,
61
+ '@type': 'HowTo' as const,
62
+ name: title,
63
+ description,
64
+ step: howTo.map((step) => ({
65
+ '@type': 'HowToStep' as const,
66
+ name: step.name,
67
+ text: step.text,
68
+ })),
69
+ };
70
+
71
+ const appSchema = {
72
+ '@context': 'https://schema.org' as const,
73
+ '@type': 'SoftwareApplication' as const,
74
+ name: title,
75
+ description,
76
+ applicationCategory: 'UtilitiesApplication',
77
+ operatingSystem: 'Web',
78
+ offers: { '@type': 'Offer' as const, price: '0', priceCurrency: 'USD' },
79
+ };
80
+
81
+ export const content: ToolLocaleContent = {
82
+ slug: 'oil-smoke-point-tracker',
83
+ title: '食用油烟点追踪器',
84
+ description: '监控油炸用油品质,估算当前烟点下降幅度。追踪使用次数、温度和食材类型,防止有害极性化合物累积。',
85
+ faqTitle: '常见问题',
86
+ ui: {
87
+ oilPresetLabel: '油炸用油类型',
88
+ presetAvocadoRefined: '牛油果油(精炼)',
89
+ presetSunflowerRefined: '葵花籽油(精炼)',
90
+ presetPeanutRefined: '花生油(精炼)',
91
+ presetCanolaRefined: '菜籽油(精炼)',
92
+ presetOliveEV: '特级初榨橄榄油',
93
+ presetOlivePomace: '橄榄果渣油',
94
+ presetCoconutUnrefined: '椰子油(未精炼)',
95
+ presetSunflowerUnrefined: '葵花籽油(未精炼)',
96
+ usesLabel: '油炸次数',
97
+ tempLabel: '油炸温度',
98
+ foodTypeLabel: '食材准备 / 裹层类型',
99
+ optionStarch: '清洁淀粉类(土豆、法式薯条)',
100
+ optionBreading: '裹粉、面糊或面包糠类食材',
101
+ baseSmokePointLabel: '原始烟点',
102
+ currentSmokePointLabel: '当前烟点(已降解)',
103
+ polarCompoundsLabel: '极性化合物(TPC)',
104
+ polymerizationLabel: '油脂聚合健康度',
105
+ statusLabel: '油脂安全概况',
106
+ statusGood: '可安全重复使用',
107
+ statusCaution: '注意 - - 请尽快过滤',
108
+ statusDiscard: '请立即废弃',
109
+ adviceGood: '油脂性质稳定。可继续油炸,但冷却后请滤除残留食物颗粒。',
110
+ adviceCaution: '降解已开始,烟点已下降。建议过滤后最多再使用一次。',
111
+ adviceDiscard: '已达到严重降解程度,极性化合物浓度很高。请废弃油脂,以防酸败和健康风险。',
112
+ gaugeSafe: '安全',
113
+ gaugeCaution: '警告',
114
+ gaugeDiscard: '丢弃',
115
+ limitLabel: '限制',
116
+ },
117
+ faq,
118
+ howTo,
119
+ seo: [
120
+ {
121
+ type: 'title',
122
+ text: '油炸化学:食用油为何会降解',
123
+ level: 2,
124
+ },
125
+ {
126
+ type: 'paragraph',
127
+ html: '油炸是化学反应最剧烈的烹饪技术之一。当食物浸入高温油中(通常在160到190摄氏度之间),油同时暴露于热量、食物中的水分和空气中的氧气。这会触发三种截然不同的化学反应:水解(水分子打断酯键)、氧化(氧气产生氢过氧化物)和聚合(受损油分子结合成黏稠的大分子链)。随着这些反应的进行,总极性化合物(TPC)浓度升高,油脂开始分解和冒烟的温度也显著下降。',
128
+ },
129
+ {
130
+ type: 'stats',
131
+ columns: 4,
132
+ items: [
133
+ { value: '25% TPC', label: '法定废弃上限', icon: 'mdi:alert-octagon' },
134
+ { value: '180°C', label: '关键温度阈值', icon: 'mdi:thermometer-alert' },
135
+ { value: '2.2倍', label: '裹粉加速衰变率', icon: 'mdi:chart-timeline-variant' },
136
+ { value: 'FFA', label: '游离脂肪酸是主因', icon: 'mdi:molecule' },
137
+ ],
138
+ },
139
+ {
140
+ type: 'title',
141
+ text: '极性化合物与健康法规',
142
+ level: 3,
143
+ },
144
+ {
145
+ type: 'paragraph',
146
+ html: '总极性化合物(TPC)是衡量油炸油脂降解程度的国际标准指标。在专业厨房中,由于安全考虑,一些国家法律禁止使用TPC浓度超过25%的油。这些极性化合物会抑制吸收、降低烹饪效率,并使炸制食品的外壳失去酥脆度。更重要的是,长期食用氧化油脂会摄入自由基和有毒化合物,与心血管问题相关。',
147
+ },
148
+ {
149
+ type: 'title',
150
+ text: '油品对比:初始烟点',
151
+ level: 3,
152
+ },
153
+ {
154
+ type: 'comparative',
155
+ columns: 2,
156
+ items: [
157
+ {
158
+ title: '精炼油(耐高温)',
159
+ icon: 'mdi:shield-check',
160
+ description: '经加工去除挥发性化合物、游离脂肪酸和杂质的油品。它们具有极高的初始烟点。',
161
+ points: ['精炼牛油果油:270°C / 518°F', '精炼葵花籽油:232°C / 450°F', '精炼花生油:232°C / 450°F', '初始抗降解能力强'],
162
+ },
163
+ {
164
+ title: '未精炼油(低温烹饪 / 风味浓郁)',
165
+ icon: 'mdi:leaf',
166
+ description: '冷压或初榨油,含有大量天然成分、矿物质和游离脂肪酸。',
167
+ highlight: true,
168
+ points: ['特级初榨橄榄油:190°C / 374°F', '未精炼椰子油:177°C / 350°F', '未精炼葵花籽油:107°C / 225°F', '在高温下降解极快'],
169
+ },
170
+ ],
171
+ },
172
+ {
173
+ type: 'title',
174
+ text: '油炸用油烟点与最大重复使用次数参考表',
175
+ level: 3,
176
+ },
177
+ {
178
+ type: 'table',
179
+ headers: ['油品种类', '原始烟点(°C)', '原始烟点(°F)', '状态 / 加工方式', '建议最大使用次数'],
180
+ rows: [
181
+ ['牛油果油(精炼)', '270°C', '518°F', '精炼', '10至12次'],
182
+ ['橄榄果渣油', '238°C', '460°F', '精炼', '8至10次'],
183
+ ['葵花籽油(精炼)', '232°C', '450°F', '精炼', '6至8次'],
184
+ ['花生油(精炼)', '232°C', '450°F', '精炼', '6至8次'],
185
+ ['菜籽油(精炼)', '204°C', '400°F', '精炼', '5至7次'],
186
+ ['特级初榨橄榄油', '190°C', '374°F', '冷压', '2至3次'],
187
+ ['椰子油(未精炼)', '177°C', '350°F', '未精炼', '1至2次'],
188
+ ['葵花籽油(未精炼)', '107°C', '225°F', '未精炼', '不适用于油炸'],
189
+ ],
190
+ },
191
+ {
192
+ type: 'title',
193
+ text: '油脂降解的关键警示信号',
194
+ level: 3,
195
+ },
196
+ {
197
+ type: 'diagnostic',
198
+ variant: 'warning',
199
+ title: '危险:当油炸用油变成有毒物质',
200
+ html: '如果观察到以下任何症状,请停止继续使用该油:<strong>酸败或肥皂味</strong>、颜色变深浑浊,或食物周围<strong>泡沫过多</strong>。如果油在正常烹饪温度(170-180°C)下开始冒烟,说明由于游离脂肪酸(FFA)极度累积,烟点已急剧下降。用已降解的油烹饪会将有毒的极性化合物和致癌元素转移到食物中,损害风味和健康。',
201
+ },
202
+ {
203
+ type: 'title',
204
+ text: '如何延长油炸用油品质',
205
+ level: 3,
206
+ },
207
+ {
208
+ type: 'tip',
209
+ title: '立即滤除碳化残渣',
210
+ html: '油炸面糊或裹粉食物会在油锅底部留下微小颗粒。这些颗粒在烹饪过程中持续焦化,作为催化剂加速油脂衰变。为减缓这一过程,请在油炸过程中随时撇去浮渣,并在<strong>每次油炸结束后</strong>,用细网筛、纱布或咖啡滤纸过滤冷却后的油。将过滤后的油储存在密封玻璃容器中,置于阴凉避光处。',
211
+ },
212
+ {
213
+ type: 'title',
214
+ text: '油炸最佳实践',
215
+ level: 3,
216
+ },
217
+ {
218
+ type: 'list',
219
+ items: [
220
+ '<strong>彻底擦干食材表面</strong> - - 多余水分会引发快速水解,将油脂分解为游离脂肪酸。',
221
+ '<strong>避免油炸前撒盐</strong> - - 盐会作为催化剂加速油脂氧化;应在食物离开热油后立即调味。',
222
+ '<strong>保持最佳温度</strong> - - 将油温维持在170°C至180°C之间。温度过高会加速热降解,过低则导致食物吸收过多油脂。',
223
+ '<strong>不要用新油补充旧油</strong> - - 将新油与已降解的油混合,会加速新油的分解,而非使其恢复。',
224
+ ],
225
+ },
226
+ {
227
+ type: 'title',
228
+ text: '油炸与脂质降解术语表',
229
+ level: 3,
230
+ },
231
+ {
232
+ type: 'glossary',
233
+ items: [
234
+ { term: '烟点', definition: '油脂开始持续分解并产生可见蓝烟的温度,释放丙烯醛。' },
235
+ { term: '总极性化合物(TPC)', definition: '衡量油脂降解的全球标准指标,表示氧化化合物、游离脂肪酸和聚合物的总重量百分比。' },
236
+ { term: '水解', definition: '水分子将甘油三酯分解为游离脂肪酸和甘油的化学反应。由食物中的水分触发。' },
237
+ { term: '聚合', definition: '热损伤的油分子结合在一起形成大分子结构的过程,导致油脂黏度增加。' },
238
+ { term: '游离脂肪酸(FFA)', definition: '在水解过程中从甘油三酯释放出的羧酸。它们直接降低脂肪的初始烟点。' },
239
+ ],
240
+ },
241
+ ],
242
+ bibliography,
243
+ schemas: [faqSchema, howToSchema, appSchema],
244
+ };
@@ -0,0 +1,11 @@
1
+ import type { ToolDefinition } from '../../types';
2
+ import { oilSmokePoint } from './entry';
3
+
4
+ export * from './entry';
5
+
6
+ export const OIL_SMOKE_POINT_TRACKER_TOOL: ToolDefinition = {
7
+ entry: oilSmokePoint,
8
+ Component: () => import('./component.astro'),
9
+ SEOComponent: () => import('./seo.astro'),
10
+ BibliographyComponent: () => import('./bibliography.astro'),
11
+ };
@@ -0,0 +1,70 @@
1
+ export interface OilPreset {
2
+ id: string;
3
+ nameKey: string;
4
+ baseSmokePointC: number;
5
+ isRefined: boolean;
6
+ }
7
+
8
+ export const OIL_PRESETS: OilPreset[] = [
9
+ { id: 'avocado-refined', nameKey: 'presetAvocadoRefined', baseSmokePointC: 270, isRefined: true },
10
+ { id: 'sunflower-refined', nameKey: 'presetSunflowerRefined', baseSmokePointC: 232, isRefined: true },
11
+ { id: 'peanut-refined', nameKey: 'presetPeanutRefined', baseSmokePointC: 232, isRefined: true },
12
+ { id: 'canola-refined', nameKey: 'presetCanolaRefined', baseSmokePointC: 204, isRefined: true },
13
+ { id: 'olive-extra-virgin', nameKey: 'presetOliveEV', baseSmokePointC: 190, isRefined: false },
14
+ { id: 'olive-pomace', nameKey: 'presetOlivePomace', baseSmokePointC: 238, isRefined: true },
15
+ { id: 'coconut-unrefined', nameKey: 'presetCoconutUnrefined', baseSmokePointC: 177, isRefined: false },
16
+ { id: 'sunflower-unrefined', nameKey: 'presetSunflowerUnrefined', baseSmokePointC: 107, isRefined: false },
17
+ ];
18
+
19
+ export interface OilTrackerInputs {
20
+ oilId: string;
21
+ uses: number;
22
+ tempC: number;
23
+ foodType: 'starch' | 'breaded';
24
+ }
25
+
26
+ export interface OilTrackerResult {
27
+ baseSmokePointC: number;
28
+ currentSmokePointC: number;
29
+ polymerizationPct: number;
30
+ status: 'good' | 'caution' | 'discard';
31
+ polarCompoundsPct: number;
32
+ }
33
+
34
+ export class OilSmokePointLogic {
35
+ static getStatus(polarCompounds: number, currentC: number): 'good' | 'caution' | 'discard' {
36
+ if (polarCompounds >= 25 || currentC <= 170) {
37
+ return 'discard';
38
+ }
39
+ if (polarCompounds >= 15 || currentC <= 190) {
40
+ return 'caution';
41
+ }
42
+ return 'good';
43
+ }
44
+
45
+ static calculate(inputs: OilTrackerInputs): OilTrackerResult {
46
+ const preset = OIL_PRESETS.find(p => p.id === inputs.oilId) || OIL_PRESETS[1];
47
+ const baseC = preset.baseSmokePointC;
48
+
49
+ const baseDegradation = preset.isRefined ? 4 : 9;
50
+ const tempFactor = 1 + Math.max(0, inputs.tempC - 180) * 0.06;
51
+ const foodFactor = inputs.foodType === 'breaded' ? 2.2 : 1.0;
52
+
53
+ const degradationPerUse = baseDegradation * tempFactor * foodFactor;
54
+ const totalDegradation = inputs.uses * degradationPerUse;
55
+ const currentC = Math.max(60, baseC - totalDegradation);
56
+
57
+ const basePolarIncrease = preset.isRefined ? 1.5 : 3.0;
58
+ const polarCompounds = Math.min(100, 2 + inputs.uses * basePolarIncrease * tempFactor * foodFactor);
59
+
60
+ const status = OilSmokePointLogic.getStatus(polarCompounds, currentC);
61
+
62
+ return {
63
+ baseSmokePointC: baseC,
64
+ currentSmokePointC: Math.round(currentC),
65
+ polymerizationPct: Math.round(Math.min(100, (polarCompounds / 35) * 100)),
66
+ status,
67
+ polarCompoundsPct: parseFloat(polarCompounds.toFixed(1)),
68
+ };
69
+ }
70
+ }