@jjlmoya/utils-textiles 1.14.0 → 1.16.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 (93) hide show
  1. package/package.json +1 -1
  2. package/src/category/i18n/de.ts +2 -2
  3. package/src/category/i18n/en.ts +2 -2
  4. package/src/category/i18n/es.ts +2 -2
  5. package/src/category/i18n/fr.ts +9 -9
  6. package/src/category/i18n/id.ts +2 -2
  7. package/src/category/i18n/it.ts +2 -2
  8. package/src/category/i18n/nl.ts +2 -2
  9. package/src/category/i18n/pl.ts +3 -3
  10. package/src/category/i18n/pt.ts +2 -2
  11. package/src/category/i18n/ru.ts +5 -5
  12. package/src/category/i18n/sv.ts +3 -3
  13. package/src/category/i18n/zh.ts +2 -2
  14. package/src/layouts/PreviewLayout.astro +1 -0
  15. package/src/tests/diacritics_density.test.ts +118 -0
  16. package/src/tests/inverted_punctuation.test.ts +84 -0
  17. package/src/tests/no_en_dash.test.ts +70 -0
  18. package/src/tests/script_density.test.ts +94 -0
  19. package/src/tool/burnTest/i18n/de.ts +3 -3
  20. package/src/tool/burnTest/i18n/fr.ts +3 -3
  21. package/src/tool/burnTest/i18n/pl.ts +3 -3
  22. package/src/tool/burnTest/i18n/ru.ts +6 -6
  23. package/src/tool/burnTest/i18n/zh.ts +3 -3
  24. package/src/tool/clothingSizeConverter/i18n/en.ts +1 -1
  25. package/src/tool/clothingSizeConverter/i18n/fr.ts +6 -6
  26. package/src/tool/clothingSizeConverter/i18n/pl.ts +1 -1
  27. package/src/tool/clothingSizeConverter/i18n/ru.ts +2 -2
  28. package/src/tool/clothingSizeConverter/i18n/zh.ts +1 -1
  29. package/src/tool/fabricProjectCalculator/component.astro +4 -5
  30. package/src/tool/fabricProjectCalculator/fabric-project-calculator.css +474 -502
  31. package/src/tool/fabricProjectCalculator/i18n/ru.ts +2 -2
  32. package/src/tool/fabricProjectCalculator/i18n/zh.ts +1 -1
  33. package/src/tool/fabricTruth/i18n/de.ts +1 -1
  34. package/src/tool/fabricTruth/i18n/en.ts +1 -1
  35. package/src/tool/fabricTruth/i18n/es.ts +1 -1
  36. package/src/tool/fabricTruth/i18n/fr.ts +13 -13
  37. package/src/tool/fabricTruth/i18n/id.ts +1 -1
  38. package/src/tool/fabricTruth/i18n/it.ts +1 -1
  39. package/src/tool/fabricTruth/i18n/ja.ts +1 -1
  40. package/src/tool/fabricTruth/i18n/ko.ts +1 -1
  41. package/src/tool/fabricTruth/i18n/nl.ts +1 -1
  42. package/src/tool/fabricTruth/i18n/pl.ts +1 -1
  43. package/src/tool/fabricTruth/i18n/pt.ts +1 -1
  44. package/src/tool/fabricTruth/i18n/ru.ts +11 -11
  45. package/src/tool/fabricTruth/i18n/sv.ts +1 -1
  46. package/src/tool/fabricTruth/i18n/tr.ts +1 -1
  47. package/src/tool/fabricTruth/i18n/zh.ts +9 -9
  48. package/src/tool/fiberPrep/fiber-preparation-natural-dyeing.css +130 -11
  49. package/src/tool/fiberPrep/i18n/fr.ts +4 -4
  50. package/src/tool/fiberPrep/i18n/ru.ts +3 -3
  51. package/src/tool/fiberPrep/i18n/zh.ts +2 -2
  52. package/src/tool/knittingGauge/i18n/ru.ts +6 -6
  53. package/src/tool/knittingGauge/i18n/zh.ts +1 -1
  54. package/src/tool/laundryGuide/i18n/de.ts +1 -1
  55. package/src/tool/laundryGuide/i18n/en.ts +1 -1
  56. package/src/tool/laundryGuide/i18n/es.ts +1 -1
  57. package/src/tool/laundryGuide/i18n/fr.ts +12 -12
  58. package/src/tool/laundryGuide/i18n/id.ts +1 -1
  59. package/src/tool/laundryGuide/i18n/it.ts +1 -1
  60. package/src/tool/laundryGuide/i18n/nl.ts +1 -1
  61. package/src/tool/laundryGuide/i18n/pl.ts +2 -2
  62. package/src/tool/laundryGuide/i18n/pt.ts +1 -1
  63. package/src/tool/laundryGuide/i18n/ru.ts +6 -6
  64. package/src/tool/laundryGuide/i18n/sv.ts +1 -1
  65. package/src/tool/laundryGuide/i18n/zh.ts +2 -2
  66. package/src/tool/needleConverter/i18n/fr.ts +5 -5
  67. package/src/tool/needleConverter/i18n/ru.ts +4 -4
  68. package/src/tool/sewingPatternScaler/i18n/fr.ts +2 -2
  69. package/src/tool/sewingPatternScaler/i18n/pl.ts +2 -2
  70. package/src/tool/sewingPatternScaler/i18n/ru.ts +7 -7
  71. package/src/tool/sewingPatternScaler/i18n/zh.ts +3 -3
  72. package/src/tool/shoeSizeConverter/i18n/fr.ts +1 -1
  73. package/src/tool/shoeSizeConverter/i18n/ru.ts +4 -4
  74. package/src/tool/shoeSizeConverter/i18n/zh.ts +3 -3
  75. package/src/tool/stainChemistry/bibliography.ts +2 -2
  76. package/src/tool/stainChemistry/i18n/de.ts +3 -3
  77. package/src/tool/stainChemistry/i18n/en.ts +3 -3
  78. package/src/tool/stainChemistry/i18n/es.ts +3 -3
  79. package/src/tool/stainChemistry/i18n/fr.ts +6 -6
  80. package/src/tool/stainChemistry/i18n/id.ts +3 -3
  81. package/src/tool/stainChemistry/i18n/it.ts +3 -3
  82. package/src/tool/stainChemistry/i18n/ja.ts +3 -3
  83. package/src/tool/stainChemistry/i18n/ko.ts +3 -3
  84. package/src/tool/stainChemistry/i18n/nl.ts +3 -3
  85. package/src/tool/stainChemistry/i18n/pl.ts +3 -3
  86. package/src/tool/stainChemistry/i18n/pt.ts +3 -3
  87. package/src/tool/stainChemistry/i18n/ru.ts +7 -7
  88. package/src/tool/stainChemistry/i18n/sv.ts +3 -3
  89. package/src/tool/stainChemistry/i18n/tr.ts +3 -3
  90. package/src/tool/stainChemistry/i18n/zh.ts +7 -7
  91. package/src/tool/yarnCalculator/i18n/fr.ts +4 -4
  92. package/src/tool/yarnCalculator/i18n/ru.ts +7 -7
  93. package/src/tool/yarnCalculator/i18n/zh.ts +5 -5
@@ -0,0 +1,94 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+
4
+ type ScriptLocale = keyof typeof SCRIPT_RULES;
5
+
6
+ const SCRIPT_RULES = {
7
+ ja: {
8
+ language: 'Japanese',
9
+ scriptName: 'kana/kanji',
10
+ scriptCharacters: /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/gu,
11
+ minScriptRatio: 0.45,
12
+ },
13
+ ko: {
14
+ language: 'Korean',
15
+ scriptName: 'hangul',
16
+ scriptCharacters: /\p{Script=Hangul}/gu,
17
+ minScriptRatio: 0.55,
18
+ },
19
+ ru: {
20
+ language: 'Russian',
21
+ scriptName: 'cyrillic',
22
+ scriptCharacters: /\p{Script=Cyrillic}/gu,
23
+ minScriptRatio: 0.65,
24
+ },
25
+ zh: {
26
+ language: 'Chinese',
27
+ scriptName: 'han',
28
+ scriptCharacters: /\p{Script=Han}/gu,
29
+ minScriptRatio: 0.45,
30
+ },
31
+ } as const;
32
+
33
+ const LETTERS = /\p{L}/gu;
34
+ const TRANSLATABLE_KEYS = ['title', 'description', 'ui', 'seo', 'faq', 'howTo'] as const;
35
+
36
+ function collectStrings(value: unknown): string[] {
37
+ if (typeof value === 'string') return [value];
38
+ if (!value || typeof value !== 'object') return [];
39
+ if (Array.isArray(value)) return value.flatMap(collectStrings);
40
+ return Object.values(value).flatMap(collectStrings);
41
+ }
42
+
43
+ function normalizeText(value: unknown): string {
44
+ return collectStrings(value).join(' ').normalize('NFC');
45
+ }
46
+
47
+ function translatableContent(content: Record<string, unknown>) {
48
+ return TRANSLATABLE_KEYS.map((key) => content[key]);
49
+ }
50
+
51
+ function letterCount(text: string): number {
52
+ return text.match(LETTERS)?.length ?? 0;
53
+ }
54
+
55
+ function scriptCount(text: string, locale: ScriptLocale): number {
56
+ return text.match(SCRIPT_RULES[locale].scriptCharacters)?.length ?? 0;
57
+ }
58
+
59
+ function scriptRatio(text: string, locale: ScriptLocale): number {
60
+ const letters = letterCount(text);
61
+ if (letters === 0) return 0;
62
+ return scriptCount(text, locale) / letters;
63
+ }
64
+
65
+ describe('Native script density validation', () => {
66
+ ALL_TOOLS.forEach((tool) => {
67
+ describe(`Tool: ${tool.entry.id}`, () => {
68
+ Object.keys(SCRIPT_RULES).forEach((locale) => {
69
+ it(`${locale} keeps most translated text in its native script`, async () => {
70
+ const typedLocale = locale as ScriptLocale;
71
+ const loader = tool.entry.i18n[typedLocale];
72
+ if (!loader) return;
73
+
74
+ const content = await loader();
75
+ const rule = SCRIPT_RULES[typedLocale];
76
+ const text = normalizeText(translatableContent(content as Record<string, unknown>));
77
+ const letters = letterCount(text);
78
+ const matches = scriptCount(text, typedLocale);
79
+ const ratio = scriptRatio(text, typedLocale);
80
+
81
+ expect(
82
+ ratio,
83
+ [
84
+ `Possible broken translation detected in ${tool.entry.id}/${typedLocale} (${rule.language}).`,
85
+ `The text has ${matches} ${rule.scriptName} characters out of ${letters} analyzed letters (${(ratio * 100).toFixed(1)}%).`,
86
+ `Most translatable content should be written in ${rule.scriptName} script.`,
87
+ 'Non-translatable fields such as slug, bibliography, and schemas are ignored to avoid false positives.',
88
+ ].join(' '),
89
+ ).toBeGreaterThanOrEqual(rule.minScriptRatio);
90
+ });
91
+ });
92
+ });
93
+ });
94
+ });
@@ -154,7 +154,7 @@ export const content: ToolLocaleContent<BurnTestUI> = {
154
154
  },
155
155
  {
156
156
  type: 'paragraph',
157
- html: 'Eine präzise Analyse erfordert den Abgleich von mindestens drei Beweisstücken. Der Geruch ist der zuverlässigste Indikator zur Identifizierung von Mischgeweben: Wenn ein Leinen“-Stoff ein leichtes Kunststoffaroma verströmt und harte Rückstände hinterlässt, deutet dies auf eine nicht deklarierte Polyestermischung hin.',
157
+ html: 'Eine präzise Analyse erfordert den Abgleich von mindestens drei Beweisstücken. Der Geruch ist der zuverlässigste Indikator zur Identifizierung von Mischgeweben: Wenn ein "Leinen"-Stoff ein leichtes Kunststoffaroma verströmt und harte Rückstände hinterlässt, deutet dies auf eine nicht deklarierte Polyestermischung hin.',
158
158
  },
159
159
  {
160
160
  type: 'tip',
@@ -187,8 +187,8 @@ export const content: ToolLocaleContent<BurnTestUI> = {
187
187
  wool: { name: 'Wolle / Merino', icon: 'mdi:sheep', color: '#FCD34D', description: 'Tierische Proteinfaser, hervorragender Wärmeisolator und elastisch.' },
188
188
  silk: { name: 'Seide', icon: 'mdi:ticket-percent', color: '#F472B6', description: 'Tierische Endlosfaser. Natürlicher Glanz und trockener Griff.' },
189
189
  cashmere: { name: 'Kaschmir', icon: 'mdi:crown', color: '#F59E0B', description: 'Luxuriöse Kaschmirziegenfaser. Extrem weich, leicht und warm.' },
190
- mohair: { name: 'Mohair', icon: 'mdi:shimmer', color: '#14B8A6', description: 'Die Diamantfaser der Angora-Ziege. Natürlicher Glanz, sehr widerstandsfähig.' },
191
- angora: { name: 'Angora', icon: 'mdi:rabbit', color: '#F9FAFB', description: 'Haar des Angora-Kaninchens. Bekannt für seinen flauschigen Halo“-Effekt und extreme Leichtigkeit.' },
190
+ mohair: { name: 'Mohair', icon: 'mdi:shimmer', color: '#14B8A6', description: 'Die "Diamantfaser" der Angora-Ziege. Natürlicher Glanz, sehr widerstandsfähig.' },
191
+ angora: { name: 'Angora', icon: 'mdi:rabbit', color: '#F9FAFB', description: 'Haar des Angora-Kaninchens. Bekannt für seinen flauschigen "Halo"-Effekt und extreme Leichtigkeit.' },
192
192
  alpaca: { name: 'Alpaka', icon: 'mdi:image-filter-hdr', color: '#9F7AEA', description: 'Schatz der Anden. Hohle, thermische, hypoallergene und seidige Faser.' },
193
193
  polyester: { name: 'Polyester', icon: 'mdi:oil', color: '#94A3B8', description: 'Synthetische Faser auf Erdölbasis. Widerstandsfähig, knitterfrei, nicht atmungsaktiv.' },
194
194
  nylon: { name: 'Nylon (Polyamid)', icon: 'mdi:parachute', color: '#2DD4BF', description: 'Hochwiderstandsfähige Kunstfaser für Zugkraft und Abrieb.' },
@@ -12,7 +12,7 @@ const faqData = [
12
12
  {
13
13
  question: 'Est-il sûr d\'effectuer un test de combustion à la maison?',
14
14
  answer:
15
- "Oui, à condition de prendre des précautions extrêmes : utilisez des pinces métalliques, travaillez sur une surface ignifuge (céramique ou métal), gardez de l'eau à portée et effectuez le test dans un endroit ventilé. Vous n'avez besoin que d'un petit échantillon de 1x1 cm ou de quelques fils.",
15
+ "Oui, à condition de prendre des précautions extrêmes: utilisez des pinces métalliques, travaillez sur une surface ignifuge (céramique ou métal), gardez de l'eau à portée et effectuez le test dans un endroit ventilé. Vous n'avez besoin que d'un petit échantillon de 1x1 cm ou de quelques fils.",
16
16
  },
17
17
  {
18
18
  question: 'Comment sent le coton quand il brûle?',
@@ -127,7 +127,7 @@ export const content: ToolLocaleContent<BurnTestUI> = {
127
127
  },
128
128
  {
129
129
  type: 'paragraph',
130
- html: "Le test de combustion est la méthode analytique la plus rapide et précise pour différencier les polymères naturels des synthétiques sans recourir au microscope électronique. En soumettant une fibre à la chaleur, son comportement moléculaire révèle son origine : depuis la cellulose végétale jusqu'aux protéines animales ou aux dérivés du pétrole.",
130
+ html: "Le test de combustion est la méthode analytique la plus rapide et précise pour différencier les polymères naturels des synthétiques sans recourir au microscope électronique. En soumettant une fibre à la chaleur, son comportement moléculaire révèle son origine: depuis la cellulose végétale jusqu'aux protéines animales ou aux dérivés du pétrole.",
131
131
  },
132
132
  {
133
133
  type: 'card',
@@ -154,7 +154,7 @@ export const content: ToolLocaleContent<BurnTestUI> = {
154
154
  },
155
155
  {
156
156
  type: 'paragraph',
157
- html: "Une analyse précise nécessite de croiser au moins trois preuves. L'odeur est l'indicateur le plus fiable pour identifier les mélanges : si un tissu de \"lin\" dégage un léger arôme plastique et laisse des résidus durs, cela indique un mélange polyester non déclaré.",
157
+ html: "Une analyse précise nécessite de croiser au moins trois preuves. L'odeur est l'indicateur le plus fiable pour identifier les mélanges: si un tissu de \"lin\" dégage un léger arôme plastique et laisse des résidus durs, cela indique un mélange polyester non déclaré.",
158
158
  },
159
159
  {
160
160
  type: 'tip',
@@ -154,7 +154,7 @@ export const content: ToolLocaleContent<BurnTestUI> = {
154
154
  },
155
155
  {
156
156
  type: 'paragraph',
157
- html: 'Precyzyjna analiza wymaga zestawienia co najmniej trzech dowodów. Zapach jest najbardziej wiarygodnym wskaźnikiem przy identyfikacji mieszanek: jeśli lniana tkanina wydziela lekki plastikowy aromat i pozostawia twarde resztki, oznacza to niezadeklarowaną domieszkę poliestru.',
157
+ html: 'Precyzyjna analiza wymaga zestawienia co najmniej trzech dowodów. Zapach jest najbardziej wiarygodnym wskaźnikiem przy identyfikacji mieszanek: jeśli "lniana" tkanina wydziela lekki plastikowy aromat i pozostawia twarde resztki, oznacza to niezadeklarowaną domieszkę poliestru.',
158
158
  },
159
159
  {
160
160
  type: 'tip',
@@ -187,8 +187,8 @@ export const content: ToolLocaleContent<BurnTestUI> = {
187
187
  wool: { name: 'Wełna / Merino', icon: 'mdi:sheep', color: '#FCD34D', description: 'Białkowe włókno zwierzęce, doskonały izolator termiczny i elastyczne.' },
188
188
  silk: { name: 'Jedwab', icon: 'mdi:ticket-percent', color: '#F472B6', description: 'Zwierzęce włókno ciągłe. Naturalny połysk i suchy chwyt.' },
189
189
  cashmere: { name: 'Kaszmir', icon: 'mdi:crown', color: '#F59E0B', description: 'Luksusowe włókno z kóz kaszmirskich. Ekstremalnie miękkie, lekkie i ciepłe.' },
190
- mohair: { name: 'Mohair', icon: 'mdi:shimmer', color: '#14B8A6', description: 'Diamentowe włókno z kóz angorskich. Naturalny połysk, bardzo wytrzymałe.' },
191
- angora: { name: 'Angora', icon: 'mdi:rabbit', color: '#F9FAFB', description: 'Sierść krolika angorskiego. Znana z puszystego efektu halo i ekstremalnej lekkości.' },
190
+ mohair: { name: 'Mohair', icon: 'mdi:shimmer', color: '#14B8A6', description: '"Diamentowe włókno" z kóz angorskich. Naturalny połysk, bardzo wytrzymałe.' },
191
+ angora: { name: 'Angora', icon: 'mdi:rabbit', color: '#F9FAFB', description: 'Sierść krolika angorskiego. Znana z puszystego efektu "halo" i ekstremalnej lekkości.' },
192
192
  alpaca: { name: 'Alpaka', icon: 'mdi:image-filter-hdr', color: '#9F7AEA', description: 'Skarb Andów. Włókno puste w środku, termiczne, hipoalergiczne i jedwabiste.' },
193
193
  polyester: { name: 'Poliester', icon: 'mdi:oil', color: '#94A3B8', description: 'Syntetyczne włókno na bazie ropy naftowej. Wytrzymałe, nie gniecie się, nie oddycha.' },
194
194
  nylon: { name: 'Nylon (Poliamid)', icon: 'mdi:parachute', color: '#2DD4BF', description: 'Bardzo wytrzymałe włókno syntetyczne na rozciąganie i ścieranie.' },
@@ -17,7 +17,7 @@ const faqData = [
17
17
  {
18
18
  question: 'Как пахнет хлопок при горении?',
19
19
  answer:
20
- 'Хлопокэто чистая целлюлоза. При горении он пахнет точно так же, как жженая бумага или сухие листья. Образующаяся зола серая, очень мелкая и рассыпается при прикосновении.',
20
+ 'Хлопок - это чистая целлюлоза. При горении он пахнет точно так же, как жженая бумага или сухие листья. Образующаяся зола серая, очень мелкая и рассыпается при прикосновении.',
21
21
  },
22
22
  {
23
23
  question: 'Что означает твердый черный шарик на конце волокна?',
@@ -27,7 +27,7 @@ const faqData = [
27
27
  {
28
28
  question: 'Почему шерсть плохо пахнет при горении?',
29
29
  answer:
30
- 'Шерсть и шелкэто животные белки, содержащие серу. При контакте с огнем они издают резкий запах паленой шерсти или перьев. Это самый простой способ отличить их от растительных волокон.',
30
+ 'Шерсть и шелк - это животные белки, содержащие серу. При контакте с огнем они издают резкий запах паленой шерсти или перьев. Это самый простой способ отличить их от растительных волокон.',
31
31
  },
32
32
  ];
33
33
 
@@ -127,7 +127,7 @@ export const content: ToolLocaleContent<BurnTestUI> = {
127
127
  },
128
128
  {
129
129
  type: 'paragraph',
130
- html: 'Тест на горениеэто самый быстрый и точный аналитический метод дифференциации натуральных полимеров от синтетических без использования электронного микроскопа. При воздействии тепла на волокно его молекулярное поведение раскрывает его происхождение: от растительной целлюлозы до животных белков или производных нефти.',
130
+ html: 'Тест на горение - это самый быстрый и точный аналитический метод дифференциации натуральных полимеров от синтетических без использования электронного микроскопа. При воздействии тепла на волокно его молекулярное поведение раскрывает его происхождение: от растительной целлюлозы до животных белков или производных нефти.',
131
131
  },
132
132
  {
133
133
  type: 'card',
@@ -145,7 +145,7 @@ export const content: ToolLocaleContent<BurnTestUI> = {
145
145
  type: 'card',
146
146
  title: 'Синтетические волокна',
147
147
  icon: 'mdi:bottle-soda-classic',
148
- html: '<strong>Полиэстер</strong> и <strong>Нейлон</strong>это, по сути, пластик. Они плавятся, образуя горячую каплю, которая застывает в стеклянную бусину, которую невозможно сломать, с резким химическим запахом.',
148
+ html: '<strong>Полиэстер</strong> и <strong>Нейлон</strong> - это, по сути, пластик. Они плавятся, образуя горячую каплю, которая застывает в стеклянную бусину, которую невозможно сломать, с резким химическим запахом.',
149
149
  },
150
150
  {
151
151
  type: 'title',
@@ -154,7 +154,7 @@ export const content: ToolLocaleContent<BurnTestUI> = {
154
154
  },
155
155
  {
156
156
  type: 'paragraph',
157
- html: 'Точный анализ требует сопоставления как минимум трех улик. Запахсамый надежный индикатор для определения смешанных составов: если «льняная» ткань издает легкий пластиковый аромат и оставляет твердые остатки, это указывает на незаявленную примесь полиэстера.',
157
+ html: 'Точный анализ требует сопоставления как минимум трех улик. Запах - самый надежный индикатор для определения смешанных составов: если "льняная" ткань издает легкий пластиковый аромат и оставляет твердые остатки, это указывает на незаявленную примесь полиэстера.',
158
158
  },
159
159
  {
160
160
  type: 'tip',
@@ -187,7 +187,7 @@ export const content: ToolLocaleContent<BurnTestUI> = {
187
187
  wool: { name: 'Шерсть / Меринос', icon: 'mdi:sheep', color: '#FCD34D', description: 'Белковое животное волокно, отличный термоизолятор, эластичное.' },
188
188
  silk: { name: 'Шелк', icon: 'mdi:ticket-percent', color: '#F472B6', description: 'Животное волокно в виде непрерывной нити. Естественный блеск и приятная сухость.' },
189
189
  cashmere: { name: 'Кашемир', icon: 'mdi:crown', color: '#F59E0B', description: 'Роскошное волокно из подшерстка кашемировых коз. Невероятно мягкое, легкое и теплое.' },
190
- mohair: { name: 'Мохер', icon: 'mdi:shimmer', color: '#14B8A6', description: '«Алмазное волокно» из шерсти ангорской козы. Естественный блеск, очень прочное.' },
190
+ mohair: { name: 'Мохер', icon: 'mdi:shimmer', color: '#14B8A6', description: '"Алмазное волокно" из шерсти ангорской козы. Естественный блеск, очень прочное.' },
191
191
  angora: { name: 'Ангора', icon: 'mdi:rabbit', color: '#F9FAFB', description: 'Пух ангорского кролика. Известен своим пушистым эффектом и экстремальной легкостью.' },
192
192
  alpaca: { name: 'Альпака', icon: 'mdi:image-filter-hdr', color: '#9F7AEA', description: 'Сокровище Анд. Полое, теплое, гипоаллергенное и шелковистое волокно.' },
193
193
  polyester: { name: 'Полиэстер', icon: 'mdi:oil', color: '#94A3B8', description: 'Синтетическое волокно на основе нефти. Прочное, не мнется, не дышит.' },
@@ -154,7 +154,7 @@ export const content: ToolLocaleContent<BurnTestUI> = {
154
154
  },
155
155
  {
156
156
  type: 'paragraph',
157
- html: '精确的分析需要交叉验证至少三个证据。气味是识别混纺最可靠的指标:如果一种“亚麻”织物散发出轻微的塑料香味并留下坚硬的残留物,这表明其含有未声明的聚酯成分。',
157
+ html: '精确的分析需要交叉验证至少三个证据。气味是识别混纺最可靠的指标:如果一种"亚麻"织物散发出轻微的塑料香味并留下坚硬的残留物,这表明其含有未声明的聚酯成分。',
158
158
  },
159
159
  {
160
160
  type: 'tip',
@@ -187,8 +187,8 @@ export const content: ToolLocaleContent<BurnTestUI> = {
187
187
  wool: { name: '羊毛 / 羊绒', icon: 'mdi:sheep', color: '#FCD34D', description: '动物蛋白质纤维,优异的绝热体,具有弹性。' },
188
188
  silk: { name: '丝绸 (真丝)', icon: 'mdi:ticket-percent', color: '#F472B6', description: '动物长丝纤维。天然光泽,手感干爽。' },
189
189
  cashmere: { name: '山羊绒', icon: 'mdi:crown', color: '#F59E0B', description: '昂贵的克什米尔山羊纤维。极其柔软、轻盈、保暖。' },
190
- mohair: { name: '马海毛', icon: 'mdi:shimmer', color: '#14B8A6', description: '安哥拉山羊的“钻石纤维”。天然光泽,非常耐磨。' },
191
- angora: { name: '安哥拉兔毛', icon: 'mdi:rabbit', color: '#F9FAFB', description: '安哥拉兔的毛。以其蓬松的“光环”效果和极致轻盈著称。' },
190
+ mohair: { name: '马海毛', icon: 'mdi:shimmer', color: '#14B8A6', description: '安哥拉山羊的"钻石纤维"。天然光泽,非常耐磨。' },
191
+ angora: { name: '安哥拉兔毛', icon: 'mdi:rabbit', color: '#F9FAFB', description: '安哥拉兔的毛。以其蓬松的"光环"效果和极致轻盈著称。' },
192
192
  alpaca: { name: '阿尔パカ (羊驼毛)', icon: 'mdi:image-filter-hdr', color: '#9F7AEA', description: '安第斯山的珍宝。中空纤维,保暖、防过敏、丝滑。' },
193
193
  polyester: { name: '聚酯纤维 (涤纶)', icon: 'mdi:oil', color: '#94A3B8', description: '石油基合成纤维。耐用、抗皱、不透气。' },
194
194
  nylon: { name: '尼龙 (锦纶)', icon: 'mdi:parachute', color: '#2DD4BF', description: '抗拉和耐磨性极强的合成纤维。' },
@@ -32,7 +32,7 @@ const faqData = [
32
32
  {
33
33
  question: 'Do clothes shrink after washing?',
34
34
  answer:
35
- '100% cotton can shrink 510% if washed in hot water and tumble dried. Synthetic materials hold their shape better. Always check the care label instructions.',
35
+ '100% cotton can shrink 5-10% if washed in hot water and tumble dried. Synthetic materials hold their shape better. Always check the care label instructions.',
36
36
  },
37
37
  ];
38
38
 
@@ -22,7 +22,7 @@ const faqData = [
22
22
  {
23
23
  question: 'Comment savoir si je dois prendre une taille au-dessus ou en dessous ?',
24
24
  answer:
25
- 'Le meilleur moyen est de lire les avis d\'autres clients. Des expressions comme « taille petit » ou « taille grand » sont très utiles. Consultez aussi le guide des tailles spécifique de la marque.',
25
+ 'Le meilleur moyen est de lire les avis d\'autres clients. Des expressions comme " taille petit " ou " taille grand " sont très utiles. Consultez aussi le guide des tailles spécifique de la marque.',
26
26
  },
27
27
  {
28
28
  question: 'La table de conversion des tailles est-elle exacte ?',
@@ -43,7 +43,7 @@ const howToData = [
43
43
  },
44
44
  {
45
45
  name: 'Choisir votre Système de Taille',
46
- text: 'Sélectionnez le système que vous connaissez : EU, US, UK ou IT.',
46
+ text: 'Sélectionnez le système que vous connaissez: EU, US, UK ou IT.',
47
47
  },
48
48
  {
49
49
  name: 'Sélectionner votre Taille',
@@ -214,7 +214,7 @@ export const content: ToolLocaleContent<ClothingSizeConverterUI> = {
214
214
  seo: [
215
215
  {
216
216
  type: 'title',
217
- text: 'Convertisseur de Tailles : Guide International Complet',
217
+ text: 'Convertisseur de Tailles: Guide International Complet',
218
218
  level: 2,
219
219
  },
220
220
  {
@@ -304,9 +304,9 @@ export const content: ToolLocaleContent<ClothingSizeConverterUI> = {
304
304
  chestLabel: 'Poitrine (cm)',
305
305
  waistLabel: 'Taille (cm)',
306
306
  hipLabel: 'Hanches (cm)',
307
- chestPlaceholder: 'Ex. : 95',
308
- waistPlaceholder: 'Ex. : 85',
309
- hipPlaceholder: 'Ex. : 95',
307
+ chestPlaceholder: 'Ex.: 95',
308
+ waistPlaceholder: 'Ex.: 85',
309
+ hipPlaceholder: 'Ex.: 95',
310
310
  recommendBtnLabel: 'Recommander une Taille',
311
311
  tipsTitle: 'Conseils de Mesure',
312
312
  tipOverClothes: 'Par-dessus les vêtements',
@@ -22,7 +22,7 @@ const faqData = [
22
22
  {
23
23
  question: 'Skąd mam wiedzieć, czy w danej marce powinienem wziąć rozmiar większy czy mniejszy?',
24
24
  answer:
25
- 'Najlepszym sposobem jest sprawdzenie opinii innych klientów. Zdania typu było na mnie za małe lub duży krój są bardzo pomocne. Sprawdź również konkretną tabelę wymiarów danej marki.',
25
+ 'Najlepszym sposobem jest sprawdzenie opinii innych klientów. Zdania typu "było na mnie za małe" lub "duży krój" są bardzo pomocne. Sprawdź również konkretną tabelę wymiarów danej marki.',
26
26
  },
27
27
  {
28
28
  question: 'Jak dokładna jest tabela konwersji rozmiarów?',
@@ -17,12 +17,12 @@ const faqData = [
17
17
  {
18
18
  question: 'Меняются ли размеры для мужской и женской одежды?',
19
19
  answer:
20
- 'Да, безусловно. Даже в одной стране мужской размер 40это не то же самое, что женский размер 40. У мужчин другие пропорции (более широкие плечи), поэтому расчет размера варьируется.',
20
+ 'Да, безусловно. Даже в одной стране мужской размер 40 - это не то же самое, что женский размер 40. У мужчин другие пропорции (более широкие плечи), поэтому расчет размера варьируется.',
21
21
  },
22
22
  {
23
23
  question: 'Как узнать, стоит ли мне брать размер больше или меньше в конкретном бренде?',
24
24
  answer:
25
- 'Лучший способпроверить отзывы других покупателей. Фразы типа «маломерит» или «большемерит» очень полезны. Также сверяйтесь с конкретной таблицей измерений этого бренда.',
25
+ 'Лучший способ - проверить отзывы других покупателей. Фразы типа "маломерит" или "большемерит" очень полезны. Также сверяйтесь с конкретной таблицей измерений этого бренда.',
26
26
  },
27
27
  {
28
28
  question: 'Насколько точна таблица соответствия размеров?',
@@ -22,7 +22,7 @@ const faqData = [
22
22
  {
23
23
  question: '我如何知道在某个品牌中应该买大一码还是买小一码?',
24
24
  answer:
25
- '最好的方法是查看其他顾客的评价。诸如“我觉得偏小”或“版型偏大”之类的短语非常有帮助。同时请参考该品牌的具体尺寸测量表。',
25
+ '最好的方法是查看其他顾客的评价。诸如"我觉得偏小"或"版型偏大"之类的短语非常有帮助。同时请参考该品牌的具体尺寸测量表。',
26
26
  },
27
27
  {
28
28
  question: '此尺码换算表准确吗?',
@@ -10,7 +10,7 @@ const calcUI = ui as FabricProjectCalculatorUI;
10
10
  ---
11
11
 
12
12
  <div class="fabric-calculator-card" id="fabric-calc-app">
13
- <aside class="fabric-sidebar">
13
+ <div class="fabric-sidebar">
14
14
  <div class="config-group">
15
15
  <h3>{calcUI.sectionProject}</h3>
16
16
  <div class="fabric-field">
@@ -59,9 +59,9 @@ const calcUI = ui as FabricProjectCalculatorUI;
59
59
  <button id="btn-clear-f" class="f-btn f-seco">{calcUI.btnClear}</button>
60
60
  <button id="btn-share-f" class="f-btn f-prim">{calcUI.btnShare}</button>
61
61
  </div>
62
- </aside>
62
+ </div>
63
63
 
64
- <main class="fabric-main">
64
+ <div class="fabric-main">
65
65
  <div id="warning-msg" class="warning-banner"></div>
66
66
 
67
67
  <section class="magic-result-area">
@@ -89,7 +89,7 @@ const calcUI = ui as FabricProjectCalculatorUI;
89
89
  </div>
90
90
  <ul id="check-list" class="merceria-checklist"></ul>
91
91
  </section>
92
- </main>
92
+ </div>
93
93
  </div>
94
94
 
95
95
  <script define:vars={{ ui }} is:inline>
@@ -371,4 +371,3 @@ const calcUI = ui as FabricProjectCalculatorUI;
371
371
  document.addEventListener('astro:page-load', init);
372
372
  init();
373
373
  </script>
374
-