@jjlmoya/utils-hardware 1.3.0 → 1.6.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 (113) hide show
  1. package/package.json +2 -1
  2. package/src/category/i18n/de.ts +106 -0
  3. package/src/category/i18n/id.ts +106 -0
  4. package/src/category/i18n/it.ts +106 -0
  5. package/src/category/i18n/ja.ts +106 -0
  6. package/src/category/i18n/ko.ts +106 -0
  7. package/src/category/i18n/nl.ts +106 -0
  8. package/src/category/i18n/pl.ts +106 -0
  9. package/src/category/i18n/pt.ts +106 -0
  10. package/src/category/i18n/ru.ts +106 -0
  11. package/src/category/i18n/sv.ts +106 -0
  12. package/src/category/i18n/tr.ts +106 -0
  13. package/src/category/i18n/zh.ts +106 -0
  14. package/src/category/index.ts +13 -1
  15. package/src/tests/category_validation.test.ts +73 -0
  16. package/src/tests/faq_count.test.ts +1 -1
  17. package/src/tests/i18n_coverage.test.ts +36 -0
  18. package/src/tests/locale_completeness.test.ts +1 -1
  19. package/src/tests/slug_language_code_format.test.ts +23 -0
  20. package/src/tests/slug_uniqueness.test.ts +81 -0
  21. package/src/tests/title_quality.test.ts +1 -1
  22. package/src/tool/batteryHealthEstimator/i18n/de.ts +158 -0
  23. package/src/tool/batteryHealthEstimator/i18n/id.ts +158 -0
  24. package/src/tool/batteryHealthEstimator/i18n/it.ts +158 -0
  25. package/src/tool/batteryHealthEstimator/i18n/ja.ts +158 -0
  26. package/src/tool/batteryHealthEstimator/i18n/ko.ts +158 -0
  27. package/src/tool/batteryHealthEstimator/i18n/nl.ts +158 -0
  28. package/src/tool/batteryHealthEstimator/i18n/pl.ts +158 -0
  29. package/src/tool/batteryHealthEstimator/i18n/pt.ts +158 -0
  30. package/src/tool/batteryHealthEstimator/i18n/ru.ts +158 -0
  31. package/src/tool/batteryHealthEstimator/i18n/sv.ts +158 -0
  32. package/src/tool/batteryHealthEstimator/i18n/tr.ts +158 -0
  33. package/src/tool/batteryHealthEstimator/i18n/zh.ts +158 -0
  34. package/src/tool/batteryHealthEstimator/index.ts +15 -7
  35. package/src/tool/deadPixelTest/i18n/de.ts +218 -0
  36. package/src/tool/deadPixelTest/i18n/id.ts +218 -0
  37. package/src/tool/deadPixelTest/i18n/it.ts +218 -0
  38. package/src/tool/deadPixelTest/i18n/ja.ts +218 -0
  39. package/src/tool/deadPixelTest/i18n/ko.ts +218 -0
  40. package/src/tool/deadPixelTest/i18n/nl.ts +218 -0
  41. package/src/tool/deadPixelTest/i18n/pl.ts +218 -0
  42. package/src/tool/deadPixelTest/i18n/pt.ts +218 -0
  43. package/src/tool/deadPixelTest/i18n/ru.ts +218 -0
  44. package/src/tool/deadPixelTest/i18n/sv.ts +218 -0
  45. package/src/tool/deadPixelTest/i18n/tr.ts +218 -0
  46. package/src/tool/deadPixelTest/i18n/zh.ts +217 -0
  47. package/src/tool/deadPixelTest/index.ts +15 -7
  48. package/src/tool/gamepadTest/i18n/de.ts +186 -0
  49. package/src/tool/gamepadTest/i18n/id.ts +186 -0
  50. package/src/tool/gamepadTest/i18n/it.ts +186 -0
  51. package/src/tool/gamepadTest/i18n/ja.ts +186 -0
  52. package/src/tool/gamepadTest/i18n/ko.ts +186 -0
  53. package/src/tool/gamepadTest/i18n/nl.ts +186 -0
  54. package/src/tool/gamepadTest/i18n/pl.ts +186 -0
  55. package/src/tool/gamepadTest/i18n/pt.ts +186 -0
  56. package/src/tool/gamepadTest/i18n/ru.ts +186 -0
  57. package/src/tool/gamepadTest/i18n/sv.ts +186 -0
  58. package/src/tool/gamepadTest/i18n/tr.ts +186 -0
  59. package/src/tool/gamepadTest/i18n/zh.ts +186 -0
  60. package/src/tool/gamepadTest/index.ts +15 -7
  61. package/src/tool/gamepadVibrationTester/i18n/de.ts +155 -0
  62. package/src/tool/gamepadVibrationTester/i18n/id.ts +155 -0
  63. package/src/tool/gamepadVibrationTester/i18n/it.ts +155 -0
  64. package/src/tool/gamepadVibrationTester/i18n/ja.ts +155 -0
  65. package/src/tool/gamepadVibrationTester/i18n/ko.ts +155 -0
  66. package/src/tool/gamepadVibrationTester/i18n/nl.ts +155 -0
  67. package/src/tool/gamepadVibrationTester/i18n/pl.ts +155 -0
  68. package/src/tool/gamepadVibrationTester/i18n/pt.ts +155 -0
  69. package/src/tool/gamepadVibrationTester/i18n/ru.ts +155 -0
  70. package/src/tool/gamepadVibrationTester/i18n/sv.ts +155 -0
  71. package/src/tool/gamepadVibrationTester/i18n/tr.ts +155 -0
  72. package/src/tool/gamepadVibrationTester/i18n/zh.ts +155 -0
  73. package/src/tool/gamepadVibrationTester/index.ts +15 -7
  74. package/src/tool/keyboardTest/i18n/de.ts +146 -0
  75. package/src/tool/keyboardTest/i18n/id.ts +146 -0
  76. package/src/tool/keyboardTest/i18n/it.ts +146 -0
  77. package/src/tool/keyboardTest/i18n/ja.ts +146 -0
  78. package/src/tool/keyboardTest/i18n/ko.ts +146 -0
  79. package/src/tool/keyboardTest/i18n/nl.ts +146 -0
  80. package/src/tool/keyboardTest/i18n/pl.ts +146 -0
  81. package/src/tool/keyboardTest/i18n/pt.ts +146 -0
  82. package/src/tool/keyboardTest/i18n/ru.ts +146 -0
  83. package/src/tool/keyboardTest/i18n/sv.ts +146 -0
  84. package/src/tool/keyboardTest/i18n/tr.ts +146 -0
  85. package/src/tool/keyboardTest/i18n/zh.ts +146 -0
  86. package/src/tool/keyboardTest/index.ts +15 -7
  87. package/src/tool/mousePollingTest/i18n/de.ts +135 -0
  88. package/src/tool/mousePollingTest/i18n/id.ts +135 -0
  89. package/src/tool/mousePollingTest/i18n/it.ts +135 -0
  90. package/src/tool/mousePollingTest/i18n/ja.ts +135 -0
  91. package/src/tool/mousePollingTest/i18n/ko.ts +135 -0
  92. package/src/tool/mousePollingTest/i18n/nl.ts +135 -0
  93. package/src/tool/mousePollingTest/i18n/pl.ts +135 -0
  94. package/src/tool/mousePollingTest/i18n/pt.ts +135 -0
  95. package/src/tool/mousePollingTest/i18n/ru.ts +135 -0
  96. package/src/tool/mousePollingTest/i18n/sv.ts +135 -0
  97. package/src/tool/mousePollingTest/i18n/tr.ts +135 -0
  98. package/src/tool/mousePollingTest/i18n/zh.ts +135 -0
  99. package/src/tool/mousePollingTest/index.ts +15 -7
  100. package/src/tool/toneGenerator/i18n/de.ts +146 -0
  101. package/src/tool/toneGenerator/i18n/id.ts +146 -0
  102. package/src/tool/toneGenerator/i18n/it.ts +146 -0
  103. package/src/tool/toneGenerator/i18n/ja.ts +146 -0
  104. package/src/tool/toneGenerator/i18n/ko.ts +146 -0
  105. package/src/tool/toneGenerator/i18n/nl.ts +146 -0
  106. package/src/tool/toneGenerator/i18n/pl.ts +146 -0
  107. package/src/tool/toneGenerator/i18n/pt.ts +146 -0
  108. package/src/tool/toneGenerator/i18n/ru.ts +146 -0
  109. package/src/tool/toneGenerator/i18n/sv.ts +146 -0
  110. package/src/tool/toneGenerator/i18n/tr.ts +146 -0
  111. package/src/tool/toneGenerator/i18n/zh.ts +146 -0
  112. package/src/tool/toneGenerator/index.ts +15 -7
  113. package/src/types.ts +1 -1
@@ -0,0 +1,106 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'donanim-tani-araclari',
5
+ title: 'Donanım Test ve Tanı Araçları',
6
+ description: 'Ücretsiz çevrimiçi araçlarla çevre birimlerinizin ve ekranınızın durumunu doğrulayın. Ölü piksel, klavye ghosting, gamepad drift ve pil sağlığını test edin.',
7
+ seo: [
8
+ {
9
+ type: 'title',
10
+ text: 'Çevre Birimi ve Ekran Tanısı: Yazılım Olmadan Hassasiyet',
11
+ level: 2,
12
+ },
13
+ {
14
+ type: 'paragraph',
15
+ html: 'Donanım bakımı artık ağır tanı uygulamaları kurmayı gerektirmiyor. Bu bölümde, bileşenlerinizi yerel web API\'leri kullanarak test etmek için tasarlanmış <strong>ücretsiz çevrimiçi araçlar</strong> sunuyoruz. LED panel yorgunluğunu tespit etmekten oyun çevre birimlerinin polling rate\'ini ölçmeye kadar standart test protokollerini uygulayarak ekipmanınızın gerçek durumunu bilmenizi sağlıyoruz.',
16
+ },
17
+ {
18
+ type: 'paragraph',
19
+ html: 'Araçlarımız yeni bir ürün satın alan ve bütünlüğünü doğrulamak isteyen kullanıcılar veya gecikme ya da doğruluk hatalarını tespit ederek rekabetçi kurulumlarını optimize etmek isteyen oyuncular için idealdir.',
20
+ },
21
+ {
22
+ type: 'title',
23
+ text: 'Görsel Sağlık: Ölü Piksel Testi ve Onarımı',
24
+ level: 2,
25
+ },
26
+ {
27
+ type: 'paragraph',
28
+ html: 'Kusurlu piksellerle dolu bir monitör kullanıcı deneyiminizi mahvedebilir. <strong>Piksel testimiz</strong> ölü pikselleri (siyah) veya takılı pikselleri (bir renge kilitli) tanımlamak için tam ekranda saf renkler görüntüler. Ayrıca hızlı kromatik frekans değişimleri yoluyla alt pikselleri serbest bırakmak için tasarlanmış bir stroboskop optimize aracı da ekliyoruz.',
29
+ },
30
+ {
31
+ type: 'title',
32
+ text: 'Giriş Kontrolörleri: Klavye (Ghosting) ve Fare (Hz)',
33
+ level: 2,
34
+ },
35
+ {
36
+ type: 'paragraph',
37
+ html: 'Daktilograflar ve FPS oyuncuları için klavye ve fare hayati önem taşır. <strong>Ghosting testi</strong> klavyenizin aynı anda kaç tuşu kaydedebileceğini (Rollover) doğrularken <strong>Polling Rate</strong> aracımız farenizin Hertz (Hz) değerini gerçek zamanlı ölçer ve PC\'nize kararlı, yüksek hızlı bağlantı sağlar.',
38
+ },
39
+ {
40
+ type: 'title',
41
+ text: 'Oyun ve Konsollar: Joy-Con Drift ve Tetik Testi',
42
+ level: 2,
43
+ },
44
+ {
45
+ type: 'paragraph',
46
+ html: 'Joystick aşınması (drift olarak bilinir) modern PS5, Xbox ve Switch kontrolörlerindeki en yaygın arızadır. <strong>Kontrolör simülatörümüz</strong> X/Y eksenlerini kayan nokta hassasiyetiyle analiz ederek çubuklarınızdaki istenmeyen hareketleri veya ölü bölge eksikliğini görselleştirmenizi sağlar. Ayrıca tarayıcı aracılığıyla dokunsal yanıt ve Dual-Rumble motor titreşimi test etmenize olanak tanır.',
47
+ },
48
+ {
49
+ type: 'title',
50
+ text: 'Güç ve Ses: Pil Sağlığı ve Saf Tonlar',
51
+ level: 2,
52
+ },
53
+ {
54
+ type: 'paragraph',
55
+ html: 'Lityum piller her şarj döngüsünde bozunur. <strong>Pil sağlığı tahmin aracımız</strong> mevcut döngüleri ve artık kapasiteyi analiz ederek dizüstü bilgisayarınızda veya mobil cihazınızda ne kadar kullanışlı ömür kaldığını tahmin eder. Öte yandan <strong>ton jeneratörümüz</strong> 20Hz\'den 20kHz\'e frekansları tarayarak hoparlör ve kulaklıkları denetlemenizi ve yüksek aralıklardaki bozulma veya işitme kaybını tespit etmenizi sağlar.',
56
+ },
57
+ {
58
+ type: 'title',
59
+ text: 'Kromatik Kalibrasyon ve Renk Uzayları: Profesyonel Görsel Hassasiyet',
60
+ level: 2,
61
+ },
62
+ {
63
+ type: 'paragraph',
64
+ html: 'Monitörler bozunur. Fabrika renk sıcaklığı (Kelvin), genellikle 6500K = D65 Gün Işığı, zamanla değişir. Grafik tasarımcılar, fotoğrafçılar ve editörler için bu felaket anlamına gelir: yanlış kalibre edilmiş bir monitörde renkleri düzenlersiniz, çalışma diğer ekranlarda farklı görünür ve müşteri reddeder. Derin kalibrasyon donanım gerektirir (€300–1000 kolorimetri), ancak <strong>renk uzayı testimiz</strong> en azından monitörünüzün tutarlı bir gama aralığını (genellikle 2,2) koruyup korumadığını denetlemenizi sağlar.',
65
+ },
66
+ {
67
+ type: 'paragraph',
68
+ html: 'sRGB vs. Adobe RGB vs. P3\'ü anlamak da kritiktir. sRGB web standardıdır (kullanımın %99\'u için yeterli), ancak Adobe RGB %40 daha fazla rengi kapsar (profesyoneller için gerekli). Yalnızca %70 sRGB kapsayan bir monitör yetersiz renk doygunluğu üretir. Araçlarımız profesyonel ekipmana €1000 yatırmadan denetlemenizi sağlar.',
69
+ },
70
+ {
71
+ type: 'title',
72
+ text: 'Donanım Performansı: Kıyaslama ve Darboğaz Tespiti',
73
+ level: 2,
74
+ },
75
+ {
76
+ type: 'paragraph',
77
+ html: 'Oyuncular GPU ve CPU dengesiz olduğunda "darboğaz" dan bahseder. Zayıf bir CPU tarafından beslenen güçlü bir GPU asla potansiyeline ulaşamaz. Kavram basittir: GPU, CPU\'nun bitmesini bekliyorsa bu israftır. Test araçları tanı koymaya yardımcı olur: Sorunum GPU mu yoksa CPU mu? Yükseltme gerekiyor mu? Yoksa video ayarlarını düşürmek yeterli mi?',
78
+ },
79
+ {
80
+ type: 'paragraph',
81
+ html: 'Gecikme testleri de rekabetçi oyunculuk için kritiktir. Giriş-ekran gecikmesi (bir düğmeye basmaktan ekranda görmek) iyi bir kurulumda 20–50ms, yavaş bir kurulumda 200+ms olabilir. Rekabetçi oyunlarda (FPS, dövüş oyunları) 50ms zafer ile yenilgi arasındaki farktır. Bu yüzden profesyonel oyuncular 240Hz monitörlere, 8000Hz farelere ve optimize edilmiş PC\'lere yatırım yapar.',
82
+ },
83
+ {
84
+ type: 'title',
85
+ text: 'Yaşam Döngüsü ve Onarılabilirlik: Dijital Haklar',
86
+ level: 2,
87
+ },
88
+ {
89
+ type: 'paragraph',
90
+ html: '"Onarılabilirlik" 2026\'da giderek daha önemli hale geliyor. "Onarılabilir" bir telefon 6 yıl dayanır; "mühürlü" olan 2–3 yılda sona erer. Yükseltilebilir RAM\'lı dizüstü bilgisayar bir yatırımdır; lehimli RAM\'lı olan ise tek kullanımlıktır. Uyguladığımız tanı araçları Onarım Hakkı hareketiyle uyumludur: cihazlarınızı atmadan önce tam olarak neyin bozuk olduğunu bilmenizi istiyoruz.',
91
+ },
92
+ {
93
+ type: 'paragraph',
94
+ html: 'Araçlarımızla arızayı belgelemek (başarısız piksel testi ekran görüntüsü, drift eden kontrolör videosu) garanti talepleri için kanıttır. Birçok kullanıcı belgelemediği, tanı koymadığı, vazgeçip yeni satın aldığı için para kaybeder. Araçlarımız teknik bilgiyi demokratikleştirir: artık kanıtla tartışabilirsiniz.',
95
+ },
96
+ {
97
+ type: 'title',
98
+ text: 'Giyilebilir Donanımın Geleceği 2026',
99
+ level: 2,
100
+ },
101
+ {
102
+ type: 'paragraph',
103
+ html: '2026\'da işletim sistemi ile tarayıcı arasındaki sınır bulanıklaştı. <strong>Web Serial ve WebUSB</strong> gibi standartlar sayesinde daha önce belirli sürücüler gerektiren tanılar gerçekleştirebiliyoruz. Bu araçlar onarım hakkı ve kullanıcıların kendi cihazları üzerindeki teknik egemenliği için bir hareketin parçasıdır.',
104
+ },
105
+ ],
106
+ };
@@ -0,0 +1,106 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'hardware-tools',
5
+ title: '硬件测试与诊断工具',
6
+ description: '使用免费在线工具验证外设和显示器的状态。测试坏点、键盘幽灵键、手柄漂移和电池健康状况。',
7
+ seo: [
8
+ {
9
+ type: 'title',
10
+ text: '外设与显示器诊断:无需软件的精确检测',
11
+ level: 2,
12
+ },
13
+ {
14
+ type: 'paragraph',
15
+ html: '维护硬件不再需要安装繁重的诊断程序。在本节中,我们提供一套<strong>免费在线工具</strong>,使用原生 Web API 测试您的组件。从检测 LED 面板老化到测量游戏外设的轮询率,我们应用标准测试协议,让您了解设备的真实状态。',
16
+ },
17
+ {
18
+ type: 'paragraph',
19
+ html: '我们的工具非常适合刚刚购买产品并想验证其完整性的用户,或者希望通过检测延迟或精度故障来优化竞技设置的游戏玩家。',
20
+ },
21
+ {
22
+ type: 'title',
23
+ text: '视觉健康:坏点检测与修复',
24
+ level: 2,
25
+ },
26
+ {
27
+ type: 'paragraph',
28
+ html: '有缺陷像素的显示器会破坏您的使用体验。我们的<strong>像素测试</strong>在全屏显示纯色,以识别坏点(黑色)或亮点(固定在某一颜色)。此外,我们还配备了频闪优化器,通过快速的色度频率变化来解除卡死的子像素。',
29
+ },
30
+ {
31
+ type: 'title',
32
+ text: '输入设备:键盘(幽灵键)与鼠标(Hz)',
33
+ level: 2,
34
+ },
35
+ {
36
+ type: 'paragraph',
37
+ html: '对于打字员和 FPS 游戏玩家而言,键盘和鼠标至关重要。<strong>幽灵键测试</strong>验证您的键盘可以同时识别多少个按键(全键无冲),而我们的<strong>轮询率</strong>工具实时测量鼠标的赫兹(Hz),确保与 PC 的稳定高速连接。',
38
+ },
39
+ {
40
+ type: 'title',
41
+ text: '游戏与主机:Joy-Con 漂移与扳机测试',
42
+ level: 2,
43
+ },
44
+ {
45
+ type: 'paragraph',
46
+ html: '摇杆磨损(称为漂移)是现代 PS5、Xbox 和 Switch 手柄中最常见的故障。我们的<strong>手柄模拟器</strong>以浮点精度分析 X/Y 轴,让您可视化摇杆上的任何不必要移动或死区缺失。还可通过浏览器测试触觉反馈和双震动马达振动。',
47
+ },
48
+ {
49
+ type: 'title',
50
+ text: '电源与音频:电池健康与纯音测试',
51
+ level: 2,
52
+ },
53
+ {
54
+ type: 'paragraph',
55
+ html: '锂电池在每个充电周期后都会老化。我们的<strong>电池健康估算器</strong>分析当前循环次数和剩余容量,预测您的笔记本电脑或移动设备还剩多少可用寿命。另一方面,我们的<strong>音调发生器</strong>通过扫描 20Hz 到 20kHz 的频率来检测扬声器和耳机,发现高频范围内的失真或听力损失。',
56
+ },
57
+ {
58
+ type: 'title',
59
+ text: '色彩校准与色彩空间:专业视觉精度',
60
+ level: 2,
61
+ },
62
+ {
63
+ type: 'paragraph',
64
+ html: '显示器会老化。出厂色温(开尔文),通常为 6500K = D65 日光色,会随时间变化。对于平面设计师、摄影师和剪辑师而言,这是灾难性的:在未正确校准的显示器上编辑颜色,作品在其他屏幕上看起来不同,客户会拒绝。深度校准需要硬件(300–1000 欧元的色度计),但我们的<strong>色彩空间测试</strong>至少可以让您检测显示器是否保持一致的伽马范围(通常为 2.2)。',
65
+ },
66
+ {
67
+ type: 'paragraph',
68
+ html: '了解 sRGB vs. Adobe RGB vs. P3 同样至关重要。sRGB 是 Web 标准(满足 99% 的使用需求),但 Adobe RGB 覆盖的色域多 40%(专业人士必须)。只覆盖 70% sRGB 的显示器会产生不足的色彩饱和度。我们的工具让您无需投资 1000 欧元的专业设备即可进行检测。',
69
+ },
70
+ {
71
+ type: 'title',
72
+ text: '硬件性能:基准测试与瓶颈检测',
73
+ level: 2,
74
+ },
75
+ {
76
+ type: 'paragraph',
77
+ html: '玩家在 GPU 与 CPU 不平衡时谈到"瓶颈"。由弱 CPU 驱动的强大 GPU 永远无法发挥其潜力。概念很简单:如果 GPU 在等待 CPU 完成,这就是浪费。测试工具有助于诊断:我的问题出在 GPU 还是 CPU?我需要升级吗?还是只需降低视频设置?',
78
+ },
79
+ {
80
+ type: 'paragraph',
81
+ html: '延迟测试对于竞技游戏同样至关重要。输入到显示的延迟(从按下按钮到在屏幕上看到)在良好的设置中可以是 20–50ms,但在缓慢的设置中可以是 200ms 以上。在竞技游戏(FPS、格斗游戏)中,50ms 是胜败之差。这就是为什么职业玩家投资 240Hz 显示器、8000Hz 鼠标和优化 PC。',
82
+ },
83
+ {
84
+ type: 'title',
85
+ text: '生命周期与可维修性:数字权利',
86
+ level: 2,
87
+ },
88
+ {
89
+ type: 'paragraph',
90
+ html: '"可维修性"在 2026 年变得越来越重要。"可维修"手机可用 6 年;"密封"手机在 2–3 年内到期。带可升级 RAM 的笔记本电脑是投资;带焊接 RAM 的是消耗品。我们实施的诊断工具与维修权运动一致:我们希望您在丢弃设备之前确切知道哪里出了问题。',
91
+ },
92
+ {
93
+ type: 'paragraph',
94
+ html: '使用我们的工具记录故障(失败像素测试的截图、漂移手柄的视频)是保修索赔的证据。许多用户因不记录、不诊断、放弃并购买新品而损失金钱。我们的工具使技术信息民主化:现在您可以用证据进行论证。',
95
+ },
96
+ {
97
+ type: 'title',
98
+ text: '可穿戴硬件的未来 2026',
99
+ level: 2,
100
+ },
101
+ {
102
+ type: 'paragraph',
103
+ html: '2026 年,操作系统与浏览器之间的界限已经模糊。得益于 <strong>Web Serial 和 WebUSB</strong> 等标准,我们可以执行以前需要特定驱动程序的诊断。这些工具是朝向维修权和用户对自己设备的技术主权运动的一部分。',
104
+ },
105
+ ],
106
+ };
@@ -11,8 +11,20 @@ export const hardwareCategory: HardwareCategoryEntry = {
11
11
  icon: 'mdi:memory',
12
12
  tools: [pixelesPantalla, testTeclado, testMando, probadorVibracionMando, testRaton, estimadorSaludBateria, toneGenerator],
13
13
  i18n: {
14
- es: () => import('./i18n/es').then((m) => m.content),
15
14
  en: () => import('./i18n/en').then((m) => m.content),
15
+ es: () => import('./i18n/es').then((m) => m.content),
16
16
  fr: () => import('./i18n/fr').then((m) => m.content),
17
+ de: () => import('./i18n/de').then((m) => m.content),
18
+ id: () => import('./i18n/id').then((m) => m.content),
19
+ it: () => import('./i18n/it').then((m) => m.content),
20
+ ja: () => import('./i18n/ja').then((m) => m.content),
21
+ ko: () => import('./i18n/ko').then((m) => m.content),
22
+ nl: () => import('./i18n/nl').then((m) => m.content),
23
+ pl: () => import('./i18n/pl').then((m) => m.content),
24
+ pt: () => import('./i18n/pt').then((m) => m.content),
25
+ ru: () => import('./i18n/ru').then((m) => m.content),
26
+ sv: () => import('./i18n/sv').then((m) => m.content),
27
+ tr: () => import('./i18n/tr').then((m) => m.content),
28
+ zh: () => import('./i18n/zh').then((m) => m.content),
17
29
  },
18
30
  };
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { hardwareCategory as 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
+ });
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import type * as DATA from '../data';
3
3
 
4
- const TOOLS: typeof DATA.hardwareCategory[] = [];
4
+ const TOOLS: typeof DATA.audiovisualCategory[] = [];
5
5
 
6
6
  describe('FAQ Content Validation', () => {
7
7
  TOOLS.forEach((entry) => {
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+
4
+ const EXPECTED_LOCALES = [
5
+ 'de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'
6
+ ];
7
+
8
+ describe('I18n Coverage Validation', () => {
9
+ it('all tools should be registered', () => {
10
+ expect(ALL_TOOLS.length).toBeGreaterThan(0);
11
+ });
12
+
13
+ ALL_TOOLS.forEach(({ entry }: { entry: any }) => {
14
+ describe(`Tool: ${entry.id}`, () => {
15
+ it('should have all 15 required locales', () => {
16
+ const registeredLocales = Object.keys(entry.i18n);
17
+ EXPECTED_LOCALES.forEach((locale) => {
18
+ expect(
19
+ registeredLocales,
20
+ `Tool "${entry.id}" is missing locale "${locale}"`,
21
+ ).toContain(locale);
22
+ });
23
+ });
24
+
25
+ it('all locale loaders should be functions', () => {
26
+ EXPECTED_LOCALES.forEach((locale) => {
27
+ const loader = entry.i18n[locale as keyof typeof entry.i18n];
28
+ expect(
29
+ typeof loader,
30
+ `Tool "${entry.id}" locale "${locale}" loader is not a function`,
31
+ ).toBe('function');
32
+ });
33
+ });
34
+ });
35
+ });
36
+ });
@@ -35,7 +35,7 @@ describe('Locale Completeness Validation', () => {
35
35
  });
36
36
  });
37
37
 
38
- it('all tools registered and available', () => {
38
+ it('all 10 tools registered', () => {
39
39
  expect(ALL_TOOLS.length).toBe(7);
40
40
  });
41
41
  });
@@ -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
+ });
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import type { ToolLocaleContent } from '../types';
4
+
5
+ const sharingLocales = ['ja', 'ko', 'zh'];
6
+
7
+ interface ValidateParams {
8
+ toolId: string;
9
+ locale: string;
10
+ content: ToolLocaleContent;
11
+ enSlug: string;
12
+ slugs: Map<string, string>;
13
+ }
14
+
15
+ const validateLocaleSlug = ({
16
+ toolId,
17
+ locale,
18
+ content,
19
+ enSlug,
20
+ slugs,
21
+ }: ValidateParams) => {
22
+ expect(
23
+ content.slug,
24
+ `Tool "${toolId}" locale "${locale}" has an invalid slug ("${content.slug}"). Slugs must be transliterated (only a-z, 0-9, and -).`,
25
+ ).toMatch(/^[a-z0-9-]+$/);
26
+
27
+ if (locale === 'en') {
28
+ return;
29
+ }
30
+
31
+ if (sharingLocales.includes(locale)) {
32
+ expect(
33
+ content.slug,
34
+ `Tool "${toolId}" locale "${locale}" must use the same slug as "en" ("${enSlug}").`,
35
+ ).toBe(enSlug);
36
+ } else {
37
+ expect(
38
+ content.slug,
39
+ `Tool "${toolId}" locale "${locale}" has the same slug as "en" ("${enSlug}"). Cada slug tiene que estar en su propia idioma`,
40
+ ).not.toBe(enSlug);
41
+
42
+ if (slugs.has(content.slug)) {
43
+ const previousLocale = slugs.get(content.slug);
44
+ expect(
45
+ false,
46
+ `Tool "${toolId}" locales "${locale}" and "${previousLocale}" share the same slug ("${content.slug}"). Cada slug tiene que estar en su propia idioma`,
47
+ ).toBe(true);
48
+ }
49
+ slugs.set(content.slug, locale);
50
+ }
51
+ };
52
+
53
+ describe('Slug Localization and Uniqueness Validation', () => {
54
+ ALL_TOOLS.forEach((tool) => {
55
+ describe(`Tool: ${tool.entry.id}`, () => {
56
+ it('every locale should have a unique, translated slug', async () => {
57
+ const slugs = new Map<string, string>();
58
+ const locales = Object.keys(tool.entry.i18n);
59
+
60
+ let enSlug = '';
61
+ if (locales.includes('en')) {
62
+ const enLoader = tool.entry.i18n['en' as keyof typeof tool.entry.i18n];
63
+ const enContent = (await enLoader?.()) as ToolLocaleContent;
64
+ enSlug = enContent.slug;
65
+ }
66
+
67
+ for (const locale of locales) {
68
+ const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
69
+ const content = (await loader?.()) as ToolLocaleContent;
70
+ validateLocaleSlug({
71
+ toolId: tool.entry.id,
72
+ locale,
73
+ content,
74
+ enSlug,
75
+ slugs,
76
+ });
77
+ }
78
+ });
79
+ });
80
+ });
81
+ });
@@ -41,7 +41,7 @@ describe('Project Titles - Separator Validation', () => {
41
41
  let match;
42
42
  while ((match = pattern.exec(content)) !== null) {
43
43
  const title = match[1];
44
- if (title && (title.includes('|') || title.includes('-'))) {
44
+ if (title.includes('|') || title.includes('-')) {
45
45
  findings.push(title);
46
46
  }
47
47
  }
@@ -0,0 +1,158 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { EstimadorSaludBateriaUI } from '../ui';
4
+
5
+ const slug = 'lithium-batterie-gesundheitsrechner';
6
+ const title = 'Rechner für den Zustand von Lithium Batterien';
7
+ const description =
8
+ 'Berechnen Sie den Gesundheitszustand (SoH) Ihrer Lithium-Batterie basierend auf Zyklen, Spannung und Temperatur. Wissenschaftlicher Leitfaden zur Maximierung der Langlebigkeit.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'Was ist die chemische Degradation von Batterien?',
13
+ answer:
14
+ 'Mit jedem Lade- und Entladezyklus erleiden Lithium-Zellen Mikrorisse und die Ansammlung von chemischen Ablagerungen (S.E.I.), die ihre Energiespeicherkapazität verringern. Dies ist ein unvermeidlicher Prozess, der jedoch durch gute Gewohnheiten verlangsamt werden kann.',
15
+ },
16
+ {
17
+ question: 'Warum wird empfohlen, nur bis 80 % zu laden?',
18
+ answer:
19
+ 'Lithium-Batterien leiden bei extremen Spannungen (0 % und 100 %) unter mehr Stress. Wenn die Ladung zwischen 20 % und 80 % gehalten wird, kann dies die Lebensdauer der Zellen verdreifachen, da Hitze und Innendruck reduziert werden.',
20
+ },
21
+ {
22
+ question: 'Wie beeinflusst Hitze die Lebensdauer der Batterie?',
23
+ answer:
24
+ 'Hitze ist der Feind Nummer eins. Für jeden Anstieg um 10 Grad über die optimale Umgebungstemperatur (25 Grad) verdoppelt sich die Geschwindigkeit der chemischen Degradation in etwa.',
25
+ },
26
+ {
27
+ question: 'Was ist ein vollständiger Ladezyklus?',
28
+ answer:
29
+ 'Ein Zyklus ist die Nutzung von 100 % der Batteriekapazität, muss aber nicht auf einmal erfolgen. Wenn Sie heute 50 % verbrauchen, die Batterie aufladen und morgen 50 % verbrauchen, haben Sie einen vollständigen Zyklus abgeschlossen.',
30
+ },
31
+ ];
32
+
33
+ const howToData = [
34
+ {
35
+ name: 'Ursprüngliche Kapazität ermitteln',
36
+ text: 'Suchen Sie auf der Verpackung Ihres Geräts oder auf der Website des Herstellers nach den mAh, die Ihre Batterie im Neuzustand hatte.',
37
+ },
38
+ {
39
+ name: 'Aktuelle Zyklen prüfen',
40
+ text: 'Viele Systeme (wie iOS oder Android 14) ermöglichen es Ihnen zu sehen, wie viele Ladezyklen Ihre Batterie bereits angesammelt hat.',
41
+ },
42
+ {
43
+ name: 'Technische Daten eingeben',
44
+ text: 'Passen Sie die aktuelle Spannung, die Zyklen und die Temperatur an, damit unser Berechnungsmodell den SoH schätzen kann.',
45
+ },
46
+ {
47
+ name: 'Diagnose analysieren',
48
+ text: 'Überprüfen Sie den Gesundheitsprozentsatz. Wenn Sie unter 80 % liegen, bemerken Sie möglicherweise Leistungseinbußen oder unerwartete Abschaltungen.',
49
+ },
50
+ ];
51
+
52
+ const faqSchema: WithContext<FAQPage> = {
53
+ '@context': 'https://schema.org',
54
+ '@type': 'FAQPage',
55
+ mainEntity: faqData.map((item) => ({
56
+ '@type': 'Question',
57
+ name: item.question,
58
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
59
+ })),
60
+ };
61
+
62
+ const howToSchema: WithContext<HowTo> = {
63
+ '@context': 'https://schema.org',
64
+ '@type': 'HowTo',
65
+ name: title,
66
+ description,
67
+ step: howToData.map((step, i) => ({
68
+ '@type': 'HowToStep',
69
+ position: i + 1,
70
+ name: step.name,
71
+ text: step.text,
72
+ })),
73
+ };
74
+
75
+ const appSchema: WithContext<SoftwareApplication> = {
76
+ '@context': 'https://schema.org',
77
+ '@type': 'SoftwareApplication',
78
+ name: title,
79
+ description,
80
+ applicationCategory: 'UtilityApplication',
81
+ operatingSystem: 'All',
82
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
83
+ inLanguage: 'de',
84
+ };
85
+
86
+ export const content: ToolLocaleContent<EstimadorSaludBateriaUI> = {
87
+ slug,
88
+ title,
89
+ description,
90
+ faqTitle: 'Häufig gestellte Fragen',
91
+ faq: faqData,
92
+ bibliographyTitle: 'Referenzen',
93
+ bibliography: [
94
+ {
95
+ name: 'Journal of Power Sources',
96
+ url: 'https://www.sciencedirect.com/journal/journal-of-power-sources',
97
+ },
98
+ {
99
+ name: 'IEEE Xplore — Lithium-Ion Battery Life Prediction',
100
+ url: 'https://ieeexplore.ieee.org/abstract/document/11090151',
101
+ },
102
+ ],
103
+ howTo: howToData,
104
+ schemas: [faqSchema, howToSchema, appSchema],
105
+ seo: [
106
+ { type: 'title', text: 'Die Chemie der Zeit: Warum Lithium Batterien sterben', level: 2 },
107
+ {
108
+ type: 'paragraph',
109
+ html: 'Eine Lithium-Ionen-Batterie ist keine statische Energiebox, sondern ein dynamisches chemisches Ökosystem, das sich seit dem Moment der Herstellung ständig verschlechtert. Jeder Lade- und Entladezyklus, jede Temperaturschwankung und jede Minute bei extremen Spannungen trägt zu Nebenprodukten bei, die den Ionenfluss behindern.',
110
+ },
111
+ { type: 'title', text: 'Wichtigste Degradationsmechanismen', level: 3 },
112
+ {
113
+ type: 'paragraph',
114
+ html: '<strong>SEI-Schicht:</strong> Die Festelektrolyt-Zwischenphase wächst mit der Zeit, verbraucht aktives Lithium und erhöht den Innenwiderstand. <strong>Elektrolytoxidation:</strong> Spannungen über 4,1 V beschleunigen die Oxidation und können die Batterie aufblähen lassen. <strong>Lithium-Plating:</strong> Das Laden bei niedrigen Temperaturen lagert Lithium in metallischer Form ab, wodurch Dendriten entstehen, die den Separator durchstoßen können.',
115
+ },
116
+ { type: 'title', text: 'Der 100 % Mythos: Warum das Laden über Nacht ein Fehler ist', level: 3 },
117
+ {
118
+ type: 'paragraph',
119
+ html: 'Für ein Lithium-Ion ist ein Ladezustand von 100 % (4,2 V) ein Zustand hoher Belastung. Untersuchungen zeigen, dass sich die Zykluslebensdauer verdoppelt oder verdreifacht, wenn das Gerät zwischen <strong>20 % und 80 %</strong> gehalten wird. Zudem verdoppelt sich für jeweils 10 °C über 25 °C die Geschwindigkeit der chemischen Degradation in etwa.',
120
+ },
121
+ { type: 'title', text: 'Protokoll zum Überleben der Energie', level: 3 },
122
+ {
123
+ type: 'paragraph',
124
+ html: 'Laden Sie eine Batterie niemals unter 0 °C auf: Lithium lagert sich an der Anode ab und verursacht dauerhafte Schäden. Schnelles Laden erzeugt lokale Hitze und mechanische Belastung; nutzen Sie es nur, wenn es unbedingt notwendig ist. Für eine langfristige Lagerung bewahren Sie die Batterie bei 50 % an einem kühlen Ort auf.',
125
+ },
126
+ ],
127
+ ui: {
128
+ badge: 'Li-Ion Batterie',
129
+ title: 'Batterie Gesundheitsrechner',
130
+ description: 'Technische Degradationsdiagnose für Lithium-Ionen-Zellen.',
131
+ paramsTitle: 'Zellparameter',
132
+ voltageLabel: 'Aktuelle Spannung',
133
+ cyclesLabel: 'Ladezyklen',
134
+ tempLabel: 'Temperatur',
135
+ voltageHint: 'Nennbereich: 3,0 V (leer) bis 4,2 V (voll).',
136
+ labelUsefulLife: 'Nutzungsdauer',
137
+ yearsPrefix: 'Gesch.',
138
+ yearsSuffix: 'Jahre',
139
+ metricThermalStress: 'Thermischer Stress',
140
+ metricVoltageStress: 'Spannungsstress',
141
+ metricLithiumPlating: 'Lithium Plating',
142
+ statusExcelente: 'Hervorragender Zustand',
143
+ statusBueno: 'Guter Zustand',
144
+ statusRegular: 'Mäßiger Zustand',
145
+ statusCritico: 'Kritischer Zustand',
146
+ indicatorTempNormal: 'Normal',
147
+ indicatorTempCritical: 'Kritisch',
148
+ indicatorVoltageHigh: 'Hoch',
149
+ indicatorVoltageLow: 'Niedrig',
150
+ indicatorPlatingRisk: 'Hohes Risiko',
151
+ indicatorPlatingOk: 'Kein Risiko',
152
+ recTemp: 'Umgebungstemperatur senken oder Belüftung verbessern, um Elektrolytoxidation zu vermeiden.',
153
+ recVoltageHigh: 'Vermeiden Sie es, die Batterie über längere Zeit bei 100 % Ladung (4,2 V) zu halten.',
154
+ recVoltageLow: 'Tiefe Entladungen vermeiden; Zyklen zwischen 20 % und 80 % verdoppeln die Lebensdauer.',
155
+ recSohLow: 'Kapazität ist unter den optimalen Standard gefallen. Erwägen Sie einen Austausch, wenn die Autonomie nicht ausreicht.',
156
+ recDefault: 'Behalten Sie Ihre aktuellen Gewohnheiten bei — Ihre Batterie befindet sich in einem idealen Betriebsbereich.',
157
+ },
158
+ };