@jjlmoya/utils-aquarium 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +56 -0
- package/scripts/postinstall.mjs +27 -0
- package/src/category/i18n/de.ts +13 -0
- package/src/category/i18n/en.ts +13 -0
- package/src/category/i18n/es.ts +13 -0
- package/src/category/i18n/fr.ts +13 -0
- package/src/category/i18n/id.ts +13 -0
- package/src/category/i18n/it.ts +13 -0
- package/src/category/i18n/ja.ts +13 -0
- package/src/category/i18n/ko.ts +13 -0
- package/src/category/i18n/nl.ts +13 -0
- package/src/category/i18n/pl.ts +13 -0
- package/src/category/i18n/pt.ts +13 -0
- package/src/category/i18n/ru.ts +13 -0
- package/src/category/i18n/sv.ts +13 -0
- package/src/category/i18n/tr.ts +13 -0
- package/src/category/i18n/zh.ts +13 -0
- package/src/category/index.ts +24 -0
- package/src/category/seo.astro +12 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +14 -0
- package/src/entries.ts +7 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +20 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +65 -0
- package/src/pages/[locale].astro +69 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/diacritics_density.test.ts +118 -0
- package/src/tests/faq_count.test.ts +13 -0
- package/src/tests/i18n_coverage.test.ts +14 -0
- package/src/tests/inverted_punctuation.test.ts +84 -0
- package/src/tests/locale_completeness.test.ts +29 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_en_dash.test.ts +70 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/pagespeed_best_practices.test.ts +198 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/script_density.test.ts +94 -0
- package/src/tests/seo_length.test.ts +26 -0
- package/src/tests/seo_parity.test.ts +60 -0
- package/src/tests/seo_translation_completeness.test.ts +69 -0
- package/src/tests/shared-test-helpers.ts +56 -0
- package/src/tests/slug_language_code_format.test.ts +23 -0
- package/src/tests/slug_uniqueness.test.ts +81 -0
- package/src/tests/spanish_leakage.test.ts +175 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tests/tool_exports.test.ts +34 -0
- package/src/tests/tool_validation.test.ts +11 -0
- package/src/tests/translation_copy.test.ts +123 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/aquarium-tank-volume-water-change-calculator.css +391 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/bibliography.astro +6 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/bibliography.ts +14 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/component.astro +93 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/controller.ts +147 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/dom-views.ts +64 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/entry.ts +13 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/evaluator.ts +19 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/i18n/en.ts +186 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/index.ts +11 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/logic.test.ts +60 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/logic.ts +132 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/seo.astro +13 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/storage.ts +30 -0
- package/src/tool/aquariumTankVolumeWaterChangeCalculator/ui.ts +41 -0
- package/src/tools.ts +9 -0
- package/src/types.ts +54 -0
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jjlmoya/utils-aquarium",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./data": "./src/data.ts",
|
|
10
|
+
"./entries": "./src/entries.ts",
|
|
11
|
+
"./runtime/*": "./src/tool/*/index.ts",
|
|
12
|
+
"./category-seo": "./src/category/seo.astro"
|
|
13
|
+
},
|
|
14
|
+
"files": ["src", "scripts"],
|
|
15
|
+
"publishConfig": {"access": "public"},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "astro dev",
|
|
18
|
+
"start": "astro dev",
|
|
19
|
+
"build": "astro build",
|
|
20
|
+
"astro": "astro",
|
|
21
|
+
"lint": "eslint src/ --max-warnings 0 && stylelint \"src/**/*.{css,astro}\"",
|
|
22
|
+
"check": "astro check",
|
|
23
|
+
"type-check": "astro check",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"preversion": "npm run lint && npm run test && npm run build",
|
|
26
|
+
"postversion": "git push && git push --tags",
|
|
27
|
+
"patch": "npm version patch",
|
|
28
|
+
"minor": "npm version minor",
|
|
29
|
+
"major": "npm version major",
|
|
30
|
+
"postinstall": "node scripts/postinstall.mjs"
|
|
31
|
+
},
|
|
32
|
+
"lint-staged": {"*.{ts,tsx,astro}": ["eslint --fix"]},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@iconify-json/mdi": "^1.2.3",
|
|
35
|
+
"@jjlmoya/prompagate": "^1.1.0",
|
|
36
|
+
"@jjlmoya/utils-shared": "1.2.0",
|
|
37
|
+
"astro": "^6.1.2",
|
|
38
|
+
"astro-icon": "^1.1.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@astrojs/check": "^0.9.8",
|
|
42
|
+
"eslint": "^9.39.4",
|
|
43
|
+
"eslint-plugin-astro": "^1.6.0",
|
|
44
|
+
"eslint-plugin-no-comments": "^1.1.10",
|
|
45
|
+
"husky": "^9.1.7",
|
|
46
|
+
"lint-staged": "^16.4.0",
|
|
47
|
+
"postcss-html": "^1.8.1",
|
|
48
|
+
"schema-dts": "^1.1.2",
|
|
49
|
+
"stylelint": "^17.6.0",
|
|
50
|
+
"stylelint-config-standard": "^40.0.0",
|
|
51
|
+
"stylelint-declaration-strict-value": "^1.11.1",
|
|
52
|
+
"typescript": "^5.4.0",
|
|
53
|
+
"typescript-eslint": "^8.58.0",
|
|
54
|
+
"vitest": "^4.1.2"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const libDir = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const toolsDir = join(libDir, '../src/tool');
|
|
7
|
+
|
|
8
|
+
const inNodeModules = libDir.includes('node_modules');
|
|
9
|
+
if (!inNodeModules) process.exit(0);
|
|
10
|
+
|
|
11
|
+
const projectRoot = join(libDir, '../../../..');
|
|
12
|
+
const categoryKey = JSON.parse(readFileSync(join(libDir, '../package.json'), 'utf8')).name.replace('@jjlmoya/utils-', '');
|
|
13
|
+
const destDir = join(projectRoot, `public/styles/lib/${categoryKey}`);
|
|
14
|
+
|
|
15
|
+
mkdirSync(destDir, { recursive: true });
|
|
16
|
+
|
|
17
|
+
const tools = readdirSync(toolsDir, { withFileTypes: true }).filter(d => d.isDirectory());
|
|
18
|
+
for (const tool of tools) {
|
|
19
|
+
const toolDir = join(toolsDir, tool.name);
|
|
20
|
+
let files;
|
|
21
|
+
try { files = readdirSync(toolDir).filter(f => f.endsWith('.css')); }
|
|
22
|
+
catch { continue; }
|
|
23
|
+
for (const file of files) {
|
|
24
|
+
writeFileSync(join(destDir, file), readFileSync(join(toolDir, file)));
|
|
25
|
+
console.log(`[@jjlmoya/utils-${categoryKey}] copied ${file}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'aquaristik',
|
|
5
|
+
title: 'Aquaristik Werkzeuge und Rechner',
|
|
6
|
+
description: 'Berechne nutzbares Beckenvolumen, Bodengrund und Wasserwechsel mit deinen eigenen Innenmaßen.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'Wasser, das du wirklich hast', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'Diese Sammlung verwandelt Innenmaße in eine nachvollziehbare Schätzung für das nutzbare Wasservolumen. Bodengrund und Wasserwechsel werden getrennt sichtbar.' },
|
|
10
|
+
{ type: 'title', text: 'Geometrie', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Rechteckige, zylindrische und gebogene Becken mit klaren Annahmen.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'aquarium',
|
|
5
|
+
title: 'Aquarium Tools and Calculators',
|
|
6
|
+
description: 'Calculate useful aquarium volume, substrate displacement and water changes from your own tank measurements.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'The water your aquarium really holds', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'This collection turns inside measurements into a useful working estimate. Substrate displacement and water changes stay visible instead of hiding inside a headline capacity.' },
|
|
10
|
+
{ type: 'title', text: 'Clear geometry', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Rectangular, cylindrical and bow front tanks with transparent assumptions.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'acuarios',
|
|
5
|
+
title: 'Herramientas y calculadoras para acuarios',
|
|
6
|
+
description: 'Calcula el volumen útil, el desplazamiento del sustrato y los cambios de agua con las medidas interiores de tu acuario.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'El agua que realmente cabe', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'Esta colección convierte las medidas interiores en una estimación útil para trabajar. El sustrato y el cambio de agua quedan visibles en lugar de esconderse tras una capacidad comercial.' },
|
|
10
|
+
{ type: 'title', text: 'Geometría clara', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Acuarios rectangulares, cilíndricos y con frontal curvo y supuestos transparentes.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'aquariums',
|
|
5
|
+
title: "Outils et calculateurs pour aquariums",
|
|
6
|
+
description: "Calculez le volume utile, le déplacement du substrat et les changements d'eau avec vos mesures intérieures.",
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: "Le volume d'eau réellement disponible", level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: "Cette collection transforme les mesures intérieures en une estimation pratique. Le substrat et le changement d'eau restent visibles au lieu de se cacher derrière une capacité annoncée." },
|
|
10
|
+
{ type: 'title', text: 'Géométrie claire', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Bacs rectangulaires, cylindriques et à façade bombée avec des hypothèses explicites.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'akuarium',
|
|
5
|
+
title: 'Alat dan Kalkulator Akuarium',
|
|
6
|
+
description: 'Hitung volume air yang berguna, perpindahan substrat, dan penggantian air dari ukuran bagian dalam akuarium.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'Air yang benar-benar tersedia', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'Koleksi ini mengubah ukuran bagian dalam menjadi perkiraan kerja yang mudah dipahami. Perpindahan substrat dan penggantian air ditampilkan secara terpisah.' },
|
|
10
|
+
{ type: 'title', text: 'Geometri jelas', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Akuarium persegi panjang, silinder, dan bagian depan melengkung dengan asumsi transparan.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'acquari',
|
|
5
|
+
title: 'Strumenti e calcolatori per acquari',
|
|
6
|
+
description: "Calcola il volume utile, lo spazio del substrato e i cambi d'acqua usando le misure interne del tuo acquario.",
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: "L'acqua che hai davvero", level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: "Questa raccolta trasforma le misure interne in una stima pratica. Il substrato e il cambio d'acqua restano visibili invece di sparire nella capacità dichiarata." },
|
|
10
|
+
{ type: 'title', text: 'Geometria chiara', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Acquari rettangolari, cilindrici e con fronte curvo e ipotesi trasparenti.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'aquarium',
|
|
5
|
+
title: '水族館と水槽の計算ツール',
|
|
6
|
+
description: '水槽の内寸から実用水量、底床の占有量、水換え量を計算します。単位を切り替えてオフラインで使えます。',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: '水槽に本当に入る水', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'このコレクションは内寸をわかりやすい実用水量の目安に変換します。底床と水換え量を分けて表示します。' },
|
|
10
|
+
{ type: 'title', text: 'わかりやすい形状計算', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['長方形、円筒形、前面が湾曲した水槽を透明な前提で計算します。'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'aquarium',
|
|
5
|
+
title: '수족관 도구와 계산기',
|
|
6
|
+
description: '수조의 내부 치수로 실제 사용 물량, 바닥재 부피, 물갈이 양을 계산합니다. 단위를 바꾸어 오프라인에서 사용할 수 있습니다.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: '수조에 실제로 담기는 물', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: '이 컬렉션은 내부 치수를 이해하기 쉬운 작업용 추정치로 바꿉니다. 바닥재와 물갈이 양을 따로 보여 줍니다.' },
|
|
10
|
+
{ type: 'title', text: '명확한 도형 계산', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['직사각형, 원통형, 곡면 전면 수조를 투명한 가정으로 계산합니다.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'aquariums',
|
|
5
|
+
title: 'Aquariumtools en rekenmachines',
|
|
6
|
+
description: 'Bereken bruikbaar aquariumvolume, substraatverplaatsing en waterwissels met je eigen binnenmaten.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'Het water dat er echt in past', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'Deze collectie zet binnenmaten om in een praktische schatting. Substraat en waterwissel blijven zichtbaar in plaats van te verdwijnen in een opgegeven capaciteit.' },
|
|
10
|
+
{ type: 'title', text: 'Heldere geometrie', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Rechthoekige, cilindrische en gebogen aquaria met transparante aannames.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'akwaria',
|
|
5
|
+
title: 'Narzędzia i kalkulatory akwarystyczne',
|
|
6
|
+
description: 'Oblicz użyteczną objętość akwarium, miejsce zajęte przez podłoże i podmianę wody na podstawie wymiarów wewnętrznych.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'Woda, która naprawdę się mieści', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'Ta kolekcja zamienia wymiary wewnętrzne w praktyczne oszacowanie. Podłoże i podmiana wody są pokazane osobno.' },
|
|
10
|
+
{ type: 'title', text: 'Czytelna geometria', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Akwaria prostokątne, cylindryczne i z giętym frontem oraz jawnymi założeniami.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'aquarios',
|
|
5
|
+
title: 'Ferramentas e calculadoras para aquários',
|
|
6
|
+
description: 'Calcule o volume útil, o espaço ocupado pelo substrato e a troca de água usando as medidas internas do aquário.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'A água que realmente cabe', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'Esta coleção transforma medidas internas numa estimativa prática. O substrato e a troca de água aparecem separadamente, sem depender da capacidade anunciada.' },
|
|
10
|
+
{ type: 'title', text: 'Geometria clara', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Aquários retangulares, cilíndricos e com frente curva, com pressupostos transparentes.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'akvariumy',
|
|
5
|
+
title: 'Инструменты и калькуляторы для аквариумов',
|
|
6
|
+
description: 'Рассчитайте полезный объём аквариума, объём грунта и количество воды для подмены по внутренним размерам.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'Сколько воды действительно помещается', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'Эта коллекция превращает внутренние размеры в понятную рабочую оценку. Объём грунта и подмены показываются отдельно.' },
|
|
10
|
+
{ type: 'title', text: 'Понятная геометрия', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Прямоугольные, цилиндрические и панорамные аквариумы с прозрачными допущениями.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'akvarier',
|
|
5
|
+
title: 'Verktyg och kalkylatorer för akvarier',
|
|
6
|
+
description: 'Beräkna användbar akvarievolym, substratets volym och vattenbyten från dina egna innermått.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'Vattnet som faktiskt får plats', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'Den här samlingen gör innermått till en tydlig arbetsuppskattning. Substrat och vattenbyte visas var för sig.' },
|
|
10
|
+
{ type: 'title', text: 'Tydlig geometri', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Rektangulära, cylindriska och välvda akvarier med transparenta antaganden.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'akvaryumlar',
|
|
5
|
+
title: 'Akvaryum Araçları ve Hesaplayıcılar',
|
|
6
|
+
description: 'Kendi akvaryum iç ölçülerinize göre kullanılabilir hacmi, taban malzemesini ve su değişimini hesaplayın.',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: 'Akvaryuma gerçekten sığan su', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: 'Bu koleksiyon iç ölçüleri pratik bir çalışma tahminine dönüştürür. Taban malzemesi ve su değişimi ayrı görünür.' },
|
|
10
|
+
{ type: 'title', text: 'Açık geometri', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['Dikdörtgen, silindir ve kavisli ön cepheli akvaryumlar için şeffaf varsayımlar.'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'shuicao',
|
|
5
|
+
title: '水族箱体积与换水计算工具',
|
|
6
|
+
description: '根据鱼缸内尺寸计算可用水量、底砂占用体积和换水量,并可切换单位离线使用,适合日常维护。',
|
|
7
|
+
seo: [
|
|
8
|
+
{ type: 'title', text: '鱼缸真正能装多少水', level: 2 },
|
|
9
|
+
{ type: 'paragraph', html: '这个工具集合把内尺寸转换为清晰的工作估算,并分别展示底砂占用和换水量。' },
|
|
10
|
+
{ type: 'title', text: '清晰的几何计算', level: 2 },
|
|
11
|
+
{ type: 'list', items: ['支持长方体、圆柱体和弧形前面的鱼缸,并明确说明计算假设。'] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AquariumCategoryEntry } from '../types';
|
|
2
|
+
import { aquariumTankVolumeWaterChangeCalculator } from '../tool/aquariumTankVolumeWaterChangeCalculator/entry';
|
|
3
|
+
|
|
4
|
+
export const aquariumCategory: AquariumCategoryEntry = {
|
|
5
|
+
icon: 'mdi:fishbowl-outline',
|
|
6
|
+
tools: [aquariumTankVolumeWaterChangeCalculator],
|
|
7
|
+
i18n: {
|
|
8
|
+
en: () => import('./i18n/en').then((m) => m.content),
|
|
9
|
+
es: () => import('./i18n/es').then((m) => m.content),
|
|
10
|
+
fr: () => import('./i18n/fr').then((m) => m.content),
|
|
11
|
+
de: () => import('./i18n/de').then((m) => m.content),
|
|
12
|
+
id: () => import('./i18n/id').then((m) => m.content),
|
|
13
|
+
it: () => import('./i18n/it').then((m) => m.content),
|
|
14
|
+
ja: () => import('./i18n/ja').then((m) => m.content),
|
|
15
|
+
ko: () => import('./i18n/ko').then((m) => m.content),
|
|
16
|
+
nl: () => import('./i18n/nl').then((m) => m.content),
|
|
17
|
+
pl: () => import('./i18n/pl').then((m) => m.content),
|
|
18
|
+
pt: () => import('./i18n/pt').then((m) => m.content),
|
|
19
|
+
ru: () => import('./i18n/ru').then((m) => m.content),
|
|
20
|
+
sv: () => import('./i18n/sv').then((m) => m.content),
|
|
21
|
+
tr: () => import('./i18n/tr').then((m) => m.content),
|
|
22
|
+
zh: () => import('./i18n/zh').then((m) => m.content),
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { aquariumCategory } from './index';
|
|
4
|
+
import type { KnownLocale } from '../types';
|
|
5
|
+
|
|
6
|
+
interface Props { locale?: KnownLocale; }
|
|
7
|
+
|
|
8
|
+
const { locale = 'es' } = Astro.props;
|
|
9
|
+
const content = await aquariumCategory.i18n[locale]?.();
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
{content && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface NavItem {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
href: string;
|
|
6
|
+
isActive?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
categoryTitle: string;
|
|
11
|
+
tools?: NavItem[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { categoryTitle, tools = [] } = Astro.props;
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<nav class="preview-nav-sidebar">
|
|
18
|
+
<div class="sidebar-header">
|
|
19
|
+
<h3>{categoryTitle}</h3>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<ul class="tools-list">
|
|
23
|
+
{tools.map((tool) => (
|
|
24
|
+
<li>
|
|
25
|
+
<a
|
|
26
|
+
href={tool.href}
|
|
27
|
+
class:list={['tool-link', { active: tool.isActive }]}
|
|
28
|
+
>
|
|
29
|
+
<span class="tool-title">{tool.title}</span>
|
|
30
|
+
</a>
|
|
31
|
+
</li>
|
|
32
|
+
))}
|
|
33
|
+
</ul>
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.preview-nav-sidebar {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
height: 100%;
|
|
41
|
+
background: var(--bg-surface, #0f172a);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.sidebar-header {
|
|
45
|
+
padding: 2rem 1.5rem 1.5rem;
|
|
46
|
+
border-bottom: 1px solid var(--border-color, #1e293b);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.sidebar-header h3 {
|
|
50
|
+
margin: 0;
|
|
51
|
+
font-size: 0.75rem;
|
|
52
|
+
font-weight: 900;
|
|
53
|
+
text-transform: uppercase;
|
|
54
|
+
letter-spacing: 0.1em;
|
|
55
|
+
color: var(--text-muted, #94a3b8);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.tools-list {
|
|
59
|
+
list-style: none;
|
|
60
|
+
margin: 0;
|
|
61
|
+
padding: 0.5rem 0;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: 0.25rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.tools-list li {
|
|
68
|
+
margin: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.tool-link {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
padding: 0.75rem 1.5rem;
|
|
75
|
+
color: var(--text-muted, #94a3b8);
|
|
76
|
+
text-decoration: none;
|
|
77
|
+
font-size: 0.9375rem;
|
|
78
|
+
font-weight: 500;
|
|
79
|
+
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
|
80
|
+
border-left: 3px solid transparent;
|
|
81
|
+
position: relative;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.tool-link:hover {
|
|
85
|
+
color: var(--text-base, #f1f5f9);
|
|
86
|
+
background: rgba(255, 255, 255, 0.08);
|
|
87
|
+
padding-left: 1.75rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.tool-link.active {
|
|
91
|
+
color: var(--accent, #f43f5e);
|
|
92
|
+
background: rgba(244, 63, 94, 0.15);
|
|
93
|
+
border-left-color: var(--accent, #f43f5e);
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.tool-link.active::before {
|
|
98
|
+
content: '';
|
|
99
|
+
position: absolute;
|
|
100
|
+
left: 0;
|
|
101
|
+
top: 50%;
|
|
102
|
+
transform: translateY(-50%);
|
|
103
|
+
width: 3px;
|
|
104
|
+
height: 24px;
|
|
105
|
+
background: var(--accent, #f43f5e);
|
|
106
|
+
border-radius: 0 2px 2px 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.tool-title {
|
|
110
|
+
display: block;
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
text-overflow: ellipsis;
|
|
113
|
+
white-space: nowrap;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
116
|
+
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { KnownLocale } from '../types';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
currentLocale: KnownLocale;
|
|
6
|
+
localeUrls?: Partial<Record<KnownLocale, string>>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { currentLocale, localeUrls = {} } = Astro.props;
|
|
10
|
+
|
|
11
|
+
const otherLocales = Object.entries(localeUrls).filter(([l]) => l !== currentLocale) as [
|
|
12
|
+
KnownLocale,
|
|
13
|
+
string,
|
|
14
|
+
][];
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<nav class="preview-toolbar">
|
|
18
|
+
{
|
|
19
|
+
otherLocales.length > 0 && (
|
|
20
|
+
<div class="locale-group">
|
|
21
|
+
<span class="locale-current">{currentLocale.toUpperCase()}</span>
|
|
22
|
+
{otherLocales.map(([locale, url]) => (
|
|
23
|
+
<a href={url} class="btn-locale">
|
|
24
|
+
{locale.toUpperCase()}
|
|
25
|
+
</a>
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
<button id="theme-toggle" class="btn-theme" aria-label="Toggle theme" title="Toggle theme">
|
|
32
|
+
<span class="theme-icon"></span>
|
|
33
|
+
</button>
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<script>
|
|
37
|
+
function initToolbar() {
|
|
38
|
+
const btn = document.getElementById('theme-toggle');
|
|
39
|
+
if (!btn) return;
|
|
40
|
+
|
|
41
|
+
const applyTheme = (theme: string) => {
|
|
42
|
+
document.documentElement.classList.remove('theme-light', 'theme-dark');
|
|
43
|
+
document.documentElement.classList.add(theme);
|
|
44
|
+
localStorage.setItem('theme', theme);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
btn.addEventListener('click', () => {
|
|
48
|
+
const next = document.documentElement.classList.contains('theme-dark')
|
|
49
|
+
? 'theme-light'
|
|
50
|
+
: 'theme-dark';
|
|
51
|
+
applyTheme(next);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
initToolbar();
|
|
56
|
+
document.addEventListener('astro:after-swap', initToolbar);
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<style>
|
|
60
|
+
.preview-toolbar {
|
|
61
|
+
position: fixed;
|
|
62
|
+
top: 1rem;
|
|
63
|
+
right: 1.5rem;
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 0.5rem;
|
|
67
|
+
z-index: 1000;
|
|
68
|
+
background: var(--bg-surface);
|
|
69
|
+
padding: 0.4rem;
|
|
70
|
+
border-radius: 1rem;
|
|
71
|
+
border: 1px solid var(--border-color);
|
|
72
|
+
backdrop-filter: blur(12px);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.locale-group {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 0.25rem;
|
|
79
|
+
padding-right: 0.5rem;
|
|
80
|
+
border-right: 1px solid var(--border-color);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.locale-current {
|
|
84
|
+
font-size: 0.7rem;
|
|
85
|
+
font-weight: 900;
|
|
86
|
+
color: var(--text-muted);
|
|
87
|
+
padding: 0.4rem 0.6rem;
|
|
88
|
+
letter-spacing: 0.08em;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.btn-locale {
|
|
92
|
+
background: transparent;
|
|
93
|
+
border: 1px solid var(--border-color);
|
|
94
|
+
color: var(--text-main);
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
padding: 0.4rem 0.7rem;
|
|
97
|
+
border-radius: 0.5rem;
|
|
98
|
+
font-weight: 700;
|
|
99
|
+
font-size: 0.7rem;
|
|
100
|
+
text-decoration: none;
|
|
101
|
+
letter-spacing: 0.08em;
|
|
102
|
+
transition: all 0.2s ease;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.btn-locale:hover {
|
|
106
|
+
border-color: var(--accent);
|
|
107
|
+
color: var(--accent);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.btn-theme {
|
|
111
|
+
background: transparent;
|
|
112
|
+
border: none;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
padding: 0.5rem;
|
|
115
|
+
border-radius: 0.6rem;
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
transition: background 0.2s ease;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.btn-theme:hover {
|
|
123
|
+
background: rgba(255, 255, 255, 0.08);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.theme-icon {
|
|
127
|
+
display: block;
|
|
128
|
+
width: 16px;
|
|
129
|
+
height: 16px;
|
|
130
|
+
border-radius: 50%;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
:global(.theme-light) .theme-icon {
|
|
134
|
+
background: #f59e0b;
|
|
135
|
+
box-shadow: 0 0 8px #fbbf24;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
:global(.theme-dark) .theme-icon {
|
|
139
|
+
background: #94a3b8;
|
|
140
|
+
box-shadow: inset -4px -2px 0 #1e293b;
|
|
141
|
+
}
|
|
142
|
+
</style>
|
|
143
|
+
|
package/src/data.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { aquariumCategory } from './category';
|
|
2
|
+
|
|
3
|
+
export type {
|
|
4
|
+
KnownLocale,
|
|
5
|
+
FAQItem,
|
|
6
|
+
BibliographyEntry,
|
|
7
|
+
HowToStep,
|
|
8
|
+
ToolLocaleContent,
|
|
9
|
+
CategoryLocaleContent,
|
|
10
|
+
LocaleLoader,
|
|
11
|
+
LocaleMap,
|
|
12
|
+
AquariumToolEntry,
|
|
13
|
+
AquariumCategoryEntry,
|
|
14
|
+
} from './types';
|
package/src/entries.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { aquariumTankVolumeWaterChangeCalculator } from './tool/aquariumTankVolumeWaterChangeCalculator/entry';
|
|
2
|
+
export type { AquariumTankVolumeWaterChangeCalculatorLocaleContent } from './tool/aquariumTankVolumeWaterChangeCalculator/entry';
|
|
3
|
+
export { aquariumCategory } from './category';
|
|
4
|
+
|
|
5
|
+
import { aquariumTankVolumeWaterChangeCalculator } from './tool/aquariumTankVolumeWaterChangeCalculator/entry';
|
|
6
|
+
|
|
7
|
+
export const ALL_ENTRIES = [aquariumTankVolumeWaterChangeCalculator];
|
package/src/env.d.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { aquariumCategory } from './category';
|
|
2
|
+
export const aquariumCategorySEO = () => import('./category/seo.astro').then((m) => m.default);
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
KnownLocale,
|
|
6
|
+
FAQItem,
|
|
7
|
+
BibliographyEntry,
|
|
8
|
+
HowToStep,
|
|
9
|
+
ToolLocaleContent,
|
|
10
|
+
CategoryLocaleContent,
|
|
11
|
+
LocaleLoader,
|
|
12
|
+
LocaleMap,
|
|
13
|
+
AquariumToolEntry,
|
|
14
|
+
AquariumCategoryEntry,
|
|
15
|
+
ToolDefinition,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
export { ALL_ENTRIES, ALL_TOOLS } from './tools';
|
|
19
|
+
export { AQUARIUM_TANK_VOLUME_WATER_CHANGE_CALCULATOR_TOOL, aquariumTankVolumeWaterChangeCalculator } from './tool/aquariumTankVolumeWaterChangeCalculator/index';
|
|
20
|
+
export type { AquariumTankVolumeWaterChangeCalculatorUI, AquariumTankVolumeWaterChangeCalculatorLocaleContent } from './tool/aquariumTankVolumeWaterChangeCalculator/index';
|