@jjlmoya/utils-sports 1.12.0 → 1.14.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 +1 -1
- package/src/pages/[locale]/[slug].astro +30 -14
- package/src/tests/locale_completeness.test.ts +1 -35
- package/src/tests/shared-test-helpers.ts +56 -0
- package/src/tests/tool_exports.test.ts +34 -0
- package/src/tool/gymTracker/bibliography.astro +3 -12
- package/src/tool/gymTracker/bibliography.ts +16 -0
- package/src/tool/gymTracker/i18n/de.ts +31 -45
- package/src/tool/gymTracker/i18n/en.ts +31 -45
- package/src/tool/gymTracker/i18n/es.ts +31 -45
- package/src/tool/gymTracker/i18n/fr.ts +31 -45
- package/src/tool/gymTracker/i18n/id.ts +31 -45
- package/src/tool/gymTracker/i18n/it.ts +31 -45
- package/src/tool/gymTracker/i18n/ja.ts +31 -45
- package/src/tool/gymTracker/i18n/ko.ts +31 -45
- package/src/tool/gymTracker/i18n/nl.ts +31 -45
- package/src/tool/gymTracker/i18n/pl.ts +31 -45
- package/src/tool/gymTracker/i18n/pt.ts +31 -45
- package/src/tool/gymTracker/i18n/ru.ts +31 -45
- package/src/tool/gymTracker/i18n/sv.ts +31 -45
- package/src/tool/gymTracker/i18n/tr.ts +31 -45
- package/src/tool/gymTracker/i18n/zh.ts +31 -45
- package/src/tool/gymTracker/seo.astro +2 -2
- package/src/tool/reactionTester/bibliography.astro +4 -0
- package/src/tool/reactionTester/bibliography.ts +16 -0
- package/src/tool/reactionTester/i18n/de.ts +13 -12
- package/src/tool/reactionTester/i18n/en.ts +13 -12
- package/src/tool/reactionTester/i18n/es.ts +13 -12
- package/src/tool/reactionTester/i18n/fr.ts +13 -12
- package/src/tool/reactionTester/i18n/id.ts +13 -12
- package/src/tool/reactionTester/i18n/it.ts +13 -12
- package/src/tool/reactionTester/i18n/ja.ts +13 -12
- package/src/tool/reactionTester/i18n/ko.ts +13 -12
- package/src/tool/reactionTester/i18n/nl.ts +13 -12
- package/src/tool/reactionTester/i18n/pl.ts +13 -12
- package/src/tool/reactionTester/i18n/pt.ts +13 -12
- package/src/tool/reactionTester/i18n/ru.ts +13 -12
- package/src/tool/reactionTester/i18n/sv.ts +13 -12
- package/src/tool/reactionTester/i18n/tr.ts +13 -12
- package/src/tool/reactionTester/i18n/zh.ts +13 -12
- package/src/tool/reactionTester/seo.astro +8 -5
- package/src/tool/scoreKeeper/bibliography.astro +3 -11
- package/src/tool/scoreKeeper/bibliography.ts +24 -0
- package/src/tool/scoreKeeper/i18n/de.ts +29 -51
- package/src/tool/scoreKeeper/i18n/en.ts +29 -51
- package/src/tool/scoreKeeper/i18n/es.ts +29 -51
- package/src/tool/scoreKeeper/i18n/fr.ts +29 -51
- package/src/tool/scoreKeeper/i18n/id.ts +29 -51
- package/src/tool/scoreKeeper/i18n/it.ts +29 -51
- package/src/tool/scoreKeeper/i18n/ja.ts +29 -51
- package/src/tool/scoreKeeper/i18n/ko.ts +29 -51
- package/src/tool/scoreKeeper/i18n/nl.ts +29 -51
- package/src/tool/scoreKeeper/i18n/pl.ts +29 -51
- package/src/tool/scoreKeeper/i18n/pt.ts +29 -51
- package/src/tool/scoreKeeper/i18n/ru.ts +29 -51
- package/src/tool/scoreKeeper/i18n/sv.ts +29 -51
- package/src/tool/scoreKeeper/i18n/tr.ts +29 -51
- package/src/tool/scoreKeeper/i18n/zh.ts +29 -51
- package/src/tool/scoreKeeper/seo.astro +2 -2
- package/src/tool/tournamentBracket/bibliography.astro +3 -7
- package/src/tool/tournamentBracket/bibliography.ts +8 -0
- package/src/tool/tournamentBracket/i18n/de.ts +18 -17
- package/src/tool/tournamentBracket/i18n/en.ts +18 -17
- package/src/tool/tournamentBracket/i18n/es.ts +21 -20
- package/src/tool/tournamentBracket/i18n/fr.ts +18 -17
- package/src/tool/tournamentBracket/i18n/id.ts +18 -17
- package/src/tool/tournamentBracket/i18n/it.ts +18 -17
- package/src/tool/tournamentBracket/i18n/ja.ts +18 -17
- package/src/tool/tournamentBracket/i18n/ko.ts +18 -17
- package/src/tool/tournamentBracket/i18n/nl.ts +18 -17
- package/src/tool/tournamentBracket/i18n/pl.ts +18 -17
- package/src/tool/tournamentBracket/i18n/pt.ts +18 -17
- package/src/tool/tournamentBracket/i18n/ru.ts +18 -17
- package/src/tool/tournamentBracket/i18n/sv.ts +18 -17
- package/src/tool/tournamentBracket/i18n/tr.ts +18 -17
- package/src/tool/tournamentBracket/i18n/zh.ts +18 -17
- package/src/tool/tournamentBracket/seo.astro +8 -5
- package/src/types.ts +0 -2
package/package.json
CHANGED
|
@@ -34,18 +34,28 @@ export async function getStaticPaths() {
|
|
|
34
34
|
]),
|
|
35
35
|
) as Partial<Record<KnownLocale, string>>;
|
|
36
36
|
|
|
37
|
+
const firstLoader = entry.i18n.en ?? Object.values(entry.i18n)[0];
|
|
38
|
+
const englishSlug = firstLoader ? (await firstLoader()).slug : entry.id;
|
|
39
|
+
|
|
37
40
|
for (const { locale, content } of localeContents) {
|
|
38
|
-
const allToolsNav =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
const allToolsNav = (
|
|
42
|
+
await Promise.all(
|
|
43
|
+
ALL_TOOLS.map(async ({ entry: navEntry }) => {
|
|
44
|
+
const loader = navEntry.i18n[locale] ?? navEntry.i18n.en;
|
|
45
|
+
if (!loader) return null;
|
|
46
|
+
const navContent = await loader();
|
|
47
|
+
return {
|
|
48
|
+
id: navEntry.id,
|
|
49
|
+
title: navContent.title,
|
|
50
|
+
href: `/${locale}/${navContent.slug}`,
|
|
51
|
+
isActive: navEntry.id === entry.id,
|
|
52
|
+
};
|
|
53
|
+
}),
|
|
54
|
+
)
|
|
55
|
+
).filter(Boolean) as NavItem[];
|
|
46
56
|
paths.push({
|
|
47
57
|
params: { locale, slug: content.slug },
|
|
48
|
-
props: { Component, locale, content, localeUrls, allToolsNav },
|
|
58
|
+
props: { Component, locale, content, localeUrls, allToolsNav, englishSlug },
|
|
49
59
|
});
|
|
50
60
|
}
|
|
51
61
|
}
|
|
@@ -66,11 +76,16 @@ interface Props {
|
|
|
66
76
|
content: ToolLocaleContent;
|
|
67
77
|
localeUrls: Partial<Record<KnownLocale, string>>;
|
|
68
78
|
allToolsNav: NavItem[];
|
|
79
|
+
englishSlug: string;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
const { Component, locale, content, localeUrls, allToolsNav } = Astro.props;
|
|
82
|
+
const { Component, locale, content, localeUrls, allToolsNav, englishSlug } = Astro.props;
|
|
83
|
+
|
|
84
|
+
const cssFiles = import.meta.glob("../../tool/**" + "/" + "*.css", { query: "?raw", import: "default" });
|
|
85
|
+
const cssKey = Object.keys(cssFiles).find((k) => k.endsWith(`/${englishSlug}.css`));
|
|
86
|
+
const toolCss = cssKey ? await cssFiles[cssKey]() as string : "";
|
|
72
87
|
|
|
73
|
-
const seoContent: UtilitySEOContent = { locale, sections: content.seo };
|
|
88
|
+
const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
|
|
74
89
|
|
|
75
90
|
const words = content.title.split(" ");
|
|
76
91
|
const titleHighlight = words[0] || "";
|
|
@@ -89,8 +104,9 @@ const titleBase = words.slice(1).join(" ") || "";
|
|
|
89
104
|
tools={allToolsNav}
|
|
90
105
|
/>
|
|
91
106
|
<Fragment slot="head">
|
|
107
|
+
{toolCss ? <Fragment set:html={`<style is:inline>${toolCss}</style>`} /> : null}
|
|
92
108
|
{
|
|
93
|
-
content.schemas.map((schema: unknown) => (
|
|
109
|
+
( content.schemas ?? []).map((schema: unknown) => (
|
|
94
110
|
<script
|
|
95
111
|
is:inline
|
|
96
112
|
type="application/ld+json"
|
|
@@ -116,11 +132,11 @@ const titleBase = words.slice(1).join(" ") || "";
|
|
|
116
132
|
</section>
|
|
117
133
|
|
|
118
134
|
<section class="section-faq">
|
|
119
|
-
<FAQSection items={content.faq}
|
|
135
|
+
<FAQSection items={content.faq} inLanguage={locale} />
|
|
120
136
|
</section>
|
|
121
137
|
|
|
122
138
|
<section class="section-bibliography">
|
|
123
|
-
<Bibliography links={content.bibliography}
|
|
139
|
+
<Bibliography links={content.bibliography} />
|
|
124
140
|
</section>
|
|
125
141
|
</div>
|
|
126
142
|
</PreviewLayout>
|
|
@@ -1,42 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { ALL_TOOLS } from '../tools';
|
|
3
|
-
import type { ToolLocaleContent } from '../types';
|
|
4
3
|
|
|
5
4
|
describe('Locale Completeness Validation', () => {
|
|
6
|
-
|
|
7
|
-
describe(`Tool: ${tool.entry.id}`, () => {
|
|
8
|
-
Object.keys(tool.entry.i18n).forEach((locale) => {
|
|
9
|
-
describe(`Locale: ${locale}`, () => {
|
|
10
|
-
it('faqTitle should be defined when faq items exist', async () => {
|
|
11
|
-
const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
|
|
12
|
-
const content = (await loader?.()) as ToolLocaleContent;
|
|
13
|
-
|
|
14
|
-
if (content.faq.length > 0) {
|
|
15
|
-
expect(
|
|
16
|
-
content.faqTitle,
|
|
17
|
-
`Tool "${tool.entry.id}" locale "${locale}" has ${content.faq.length} FAQ items but is missing faqTitle`,
|
|
18
|
-
).toBeTruthy();
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('bibliographyTitle should be defined when bibliography items exist', async () => {
|
|
23
|
-
const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
|
|
24
|
-
const content = (await loader?.()) as ToolLocaleContent;
|
|
25
|
-
|
|
26
|
-
if (content.bibliography.length > 0) {
|
|
27
|
-
expect(
|
|
28
|
-
content.bibliographyTitle,
|
|
29
|
-
`Tool "${tool.entry.id}" locale "${locale}" has ${content.bibliography.length} bibliography items but is missing bibliographyTitle`,
|
|
30
|
-
).toBeTruthy();
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('all 10 tools registered', () => {
|
|
5
|
+
it('all tools registered', () => {
|
|
39
6
|
expect(ALL_TOOLS.length).toBe(4);
|
|
40
7
|
});
|
|
41
8
|
});
|
|
42
|
-
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../types';
|
|
2
|
+
|
|
3
|
+
export interface ToolExportValidationResult {
|
|
4
|
+
passed: boolean;
|
|
5
|
+
failures: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function validateComponentType(
|
|
9
|
+
toolId: string,
|
|
10
|
+
componentName: string,
|
|
11
|
+
component: unknown,
|
|
12
|
+
failures: string[],
|
|
13
|
+
): void {
|
|
14
|
+
if (typeof component !== 'function') {
|
|
15
|
+
failures.push(`${toolId}: ${componentName} is not a function (${typeof component})`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function validateComponentExecution(
|
|
20
|
+
toolId: string,
|
|
21
|
+
componentName: string,
|
|
22
|
+
fn: () => Promise<unknown>,
|
|
23
|
+
failures: string[],
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
try {
|
|
26
|
+
const result = await fn();
|
|
27
|
+
if (!result || typeof result !== 'object') {
|
|
28
|
+
failures.push(`${toolId}: ${componentName} import returned invalid result`);
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
failures.push(`${toolId}: ${componentName} execution error - ${error instanceof Error ? error.message : 'unknown'}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function validateToolExports(tools: ToolDefinition[]): Promise<ToolExportValidationResult> {
|
|
36
|
+
const failures: string[] = [];
|
|
37
|
+
|
|
38
|
+
for (const tool of tools) {
|
|
39
|
+
validateComponentType(tool.entry.id, 'Component', tool.Component, failures);
|
|
40
|
+
validateComponentType(tool.entry.id, 'SEOComponent', tool.SEOComponent, failures);
|
|
41
|
+
validateComponentType(tool.entry.id, 'BibliographyComponent', tool.BibliographyComponent, failures);
|
|
42
|
+
|
|
43
|
+
const componentFn = tool.Component as () => Promise<unknown>;
|
|
44
|
+
const seoFn = tool.SEOComponent as () => Promise<unknown>;
|
|
45
|
+
const bibFn = tool.BibliographyComponent as () => Promise<unknown>;
|
|
46
|
+
|
|
47
|
+
await validateComponentExecution(tool.entry.id, 'Component', componentFn, failures);
|
|
48
|
+
await validateComponentExecution(tool.entry.id, 'SEOComponent', seoFn, failures);
|
|
49
|
+
await validateComponentExecution(tool.entry.id, 'BibliographyComponent', bibFn, failures);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
passed: failures.length === 0,
|
|
54
|
+
failures,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
import { validateToolExports } from './shared-test-helpers';
|
|
4
|
+
|
|
5
|
+
describe('Tool Exports Pattern Validation', () => {
|
|
6
|
+
describe('Component Exports Format', () => {
|
|
7
|
+
ALL_TOOLS.forEach((tool) => {
|
|
8
|
+
it(`${tool.entry.id}: Component should be a lazy-loaded function`, () => {
|
|
9
|
+
expect(typeof tool.Component).toBe('function');
|
|
10
|
+
expect(tool.Component).toBeInstanceOf(Function);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it(`${tool.entry.id}: SEOComponent should be a lazy-loaded function`, () => {
|
|
14
|
+
expect(typeof tool.SEOComponent).toBe('function');
|
|
15
|
+
expect(tool.SEOComponent).toBeInstanceOf(Function);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it(`${tool.entry.id}: BibliographyComponent should be a lazy-loaded function`, () => {
|
|
19
|
+
expect(typeof tool.BibliographyComponent).toBe('function');
|
|
20
|
+
expect(tool.BibliographyComponent).toBeInstanceOf(Function);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('Dynamic Import Validation', () => {
|
|
26
|
+
it('all tools must have functional dynamic imports', async () => {
|
|
27
|
+
const result = await validateToolExports(ALL_TOOLS);
|
|
28
|
+
if (!result.passed) {
|
|
29
|
+
throw new Error(`Tool export validation failed:\n${result.failures.join('\n')}`);
|
|
30
|
+
}
|
|
31
|
+
expect(result.passed).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { Bibliography } from '@jjlmoya/utils-shared';
|
|
3
|
-
import {
|
|
4
|
-
import type { KnownLocale } from '../../types';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
locale?: KnownLocale;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const { locale = 'es' } = Astro.props;
|
|
11
|
-
const content = await gymTracker.i18n[locale]?.();
|
|
12
|
-
if (!content) return;
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { bibliography } from './bibliography';
|
|
13
4
|
---
|
|
14
5
|
|
|
15
|
-
<
|
|
6
|
+
<SharedBibliography links={bibliography} />
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BibliographyEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const bibliography: BibliographyEntry[] = [
|
|
4
|
+
{
|
|
5
|
+
name: 'Journal of Strength and Conditioning Research - Progressive Overload Study',
|
|
6
|
+
url: 'https://journals.lww.com/nsca-jscr/Fulltext/2010/10000/The_Mechanisms_of_Muscle_Hypertrophy_and_Their.40.aspx',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'National Academy of Sports Medicine - Progressive Overload Explained',
|
|
10
|
+
url: 'https://blog.nasm.org/progressive-overload-explained',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'Science of Strength - Data Tracking in Resistance Training',
|
|
14
|
+
url: 'https://pubmed.ncbi.nlm.nih.gov/30558493/',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bibliography } from '../bibliography';
|
|
1
2
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
3
|
import type { ToolLocaleContent } from '../../../types';
|
|
3
4
|
import type { GymTrackerUI } from '../ui';
|
|
@@ -11,41 +12,41 @@ const faqData = [
|
|
|
11
12
|
{
|
|
12
13
|
question: 'Was ist der Zweck der Trainingsverfolgung?',
|
|
13
14
|
answer:
|
|
14
|
-
'Es dient dazu, progressive Überlastung wissenschaftlich anzuwenden. Indem Sie genau wissen, wie viel Sie in der vorherigen Sitzung gehoben haben, können Sie versuchen, diese Marke zu übertreffen, was Muskelwachstum und langfristige Kraftgewinne garantiert.'
|
|
15
|
+
'Es dient dazu, progressive Überlastung wissenschaftlich anzuwenden. Indem Sie genau wissen, wie viel Sie in der vorherigen Sitzung gehoben haben, können Sie versuchen, diese Marke zu übertreffen, was Muskelwachstum und langfristige Kraftgewinne garantiert.'
|
|
15
16
|
},
|
|
16
17
|
{
|
|
17
18
|
question: 'Welche Daten sollte ich aufzeichnen?',
|
|
18
19
|
answer:
|
|
19
|
-
'Das Wichtigste ist das maximale Gewicht (Top Set), das Sie mit guter Form für eine festgelegte Anzahl von Wiederholungen erreicht haben. Unser Tool konzentriert sich auf die Aufzeichnung des Gewichts pro Sitzung, um Ihr Fortschrittsdiagramm zu erstellen.'
|
|
20
|
+
'Das Wichtigste ist das maximale Gewicht (Top Set), das Sie mit guter Form für eine festgelegte Anzahl von Wiederholungen erreicht haben. Unser Tool konzentriert sich auf die Aufzeichnung des Gewichts pro Sitzung, um Ihr Fortschrittsdiagramm zu erstellen.'
|
|
20
21
|
},
|
|
21
22
|
{
|
|
22
23
|
question: 'Wie werden die Diagramme interpretiert?',
|
|
23
24
|
answer:
|
|
24
|
-
'Eine ansteigende Linie zeigt an, dass Sie Fortschritte machen. Eine flache Linie (Plateau) deutet darauf hin, dass Sie Ihr Volumen, Ihre Intensität oder Ihre Erholung anpassen müssen. Eine anhaltend fallende Linie kann ein Zeichen für Übertraining sein.'
|
|
25
|
+
'Eine ansteigende Linie zeigt an, dass Sie Fortschritte machen. Eine flache Linie (Plateau) deutet darauf hin, dass Sie Ihr Volumen, Ihre Intensität oder Ihre Erholung anpassen müssen. Eine anhaltend fallende Linie kann ein Zeichen für Übertraining sein.'
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
question: 'Wo werden meine Daten gespeichert?',
|
|
28
29
|
answer:
|
|
29
|
-
'Die Daten werden lokal in Ihrem Browser (Local Storage) gespeichert. Dies bedeutet, dass Ihre Privatsphäre absolut gewahrt bleibt und Sie kein Konto erstellen müssen. Wenn Sie jedoch die Browserdaten löschen, geht der Verlauf verloren.'
|
|
30
|
+
'Die Daten werden lokal in Ihrem Browser (Local Storage) gespeichert. Dies bedeutet, dass Ihre Privatsphäre absolut gewahrt bleibt und Sie kein Konto erstellen müssen. Wenn Sie jedoch die Browserdaten löschen, geht der Verlauf verloren.'
|
|
30
31
|
},
|
|
31
32
|
];
|
|
32
33
|
|
|
33
34
|
const howToData = [
|
|
34
35
|
{
|
|
35
36
|
name: 'Wählen Sie die Übung aus',
|
|
36
|
-
text: 'Wählen Sie im Dropdown-Menü aus Grundübungen wie Kniebeugen, Bankdrücken oder Kreuzheben.'
|
|
37
|
+
text: 'Wählen Sie im Dropdown-Menü aus Grundübungen wie Kniebeugen, Bankdrücken oder Kreuzheben.'
|
|
37
38
|
},
|
|
38
39
|
{
|
|
39
40
|
name: 'Geben Sie das Gewicht ein',
|
|
40
|
-
text: 'Geben Sie nach Ihrem schwersten Satz das gehobene Gewicht in Kilogramm/Pfund in das entsprechende Feld ein.'
|
|
41
|
+
text: 'Geben Sie nach Ihrem schwersten Satz das gehobene Gewicht in Kilogramm/Pfund in das entsprechende Feld ein.'
|
|
41
42
|
},
|
|
42
43
|
{
|
|
43
44
|
name: 'Drücken Sie auf Hinzufügen',
|
|
44
|
-
text: 'Speichern Sie Ihre Marke. Das System aktualisiert automatisch Ihren Verlauf und Ihr Fortschrittsdiagramm.'
|
|
45
|
+
text: 'Speichern Sie Ihre Marke. Das System aktualisiert automatisch Ihren Verlauf und Ihr Fortschrittsdiagramm.'
|
|
45
46
|
},
|
|
46
47
|
{
|
|
47
48
|
name: 'Analysieren Sie Ihre Entwicklung',
|
|
48
|
-
text: 'Konsultieren Sie regelmäßig das Diagramm, um Plateaus zu identifizieren und sich durch Ihren realen Kraftzuwachs zu motivieren.'
|
|
49
|
+
text: 'Konsultieren Sie regelmäßig das Diagramm, um Plateaus zu identifizieren und sich durch Ihren realen Kraftzuwachs zu motivieren.'
|
|
49
50
|
},
|
|
50
51
|
];
|
|
51
52
|
|
|
@@ -55,8 +56,8 @@ const faqSchema: WithContext<FAQPage> = {
|
|
|
55
56
|
mainEntity: faqData.map((item) => ({
|
|
56
57
|
'@type': 'Question',
|
|
57
58
|
name: item.question,
|
|
58
|
-
acceptedAnswer: { '@type': 'Answer', text: item.answer }
|
|
59
|
-
}))
|
|
59
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer }
|
|
60
|
+
}))
|
|
60
61
|
};
|
|
61
62
|
|
|
62
63
|
const howToSchema: WithContext<HowTo> = {
|
|
@@ -68,8 +69,8 @@ const howToSchema: WithContext<HowTo> = {
|
|
|
68
69
|
'@type': 'HowToStep',
|
|
69
70
|
position: i + 1,
|
|
70
71
|
name: step.name,
|
|
71
|
-
text: step.text
|
|
72
|
-
}))
|
|
72
|
+
text: step.text
|
|
73
|
+
}))
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
const appSchema: WithContext<SoftwareApplication> = {
|
|
@@ -80,50 +81,35 @@ const appSchema: WithContext<SoftwareApplication> = {
|
|
|
80
81
|
applicationCategory: 'HealthApplication',
|
|
81
82
|
operatingSystem: 'All',
|
|
82
83
|
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
|
83
|
-
inLanguage: 'de'
|
|
84
|
+
inLanguage: 'de'
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
export const content: ToolLocaleContent<GymTrackerUI & Record<string, string>> = {
|
|
87
88
|
slug,
|
|
88
89
|
title,
|
|
89
90
|
description,
|
|
90
|
-
faqTitle: 'Häufig gestellte Fragen',
|
|
91
91
|
faq: faqData,
|
|
92
|
-
|
|
93
|
-
bibliography: [
|
|
94
|
-
{
|
|
95
|
-
name: 'Journal of Strength and Conditioning Research - Progressive Overload Study',
|
|
96
|
-
url: 'https://journals.lww.com/nsca-jscr/Fulltext/2010/10000/The_Mechanisms_of_Muscle_Hypertrophy_and_Their.40.aspx',
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: 'National Academy of Sports Medicine - Progressive Overload Explained',
|
|
100
|
-
url: 'https://blog.nasm.org/progressive-overload-explained',
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: 'Science of Strength - Data Tracking in Resistance Training',
|
|
104
|
-
url: 'https://pubmed.ncbi.nlm.nih.gov/30558493/',
|
|
105
|
-
},
|
|
106
|
-
],
|
|
92
|
+
bibliography,
|
|
107
93
|
howTo: howToData,
|
|
108
94
|
schemas: [faqSchema, howToSchema, appSchema],
|
|
109
95
|
seo: [
|
|
110
96
|
{
|
|
111
97
|
type: 'title',
|
|
112
98
|
text: 'Fitness-Tracking: Der Schlüssel zu echtem Fortschritt',
|
|
113
|
-
level: 2
|
|
99
|
+
level: 2
|
|
114
100
|
},
|
|
115
101
|
{
|
|
116
102
|
type: 'paragraph',
|
|
117
|
-
html: 'In der Welt von Fitness und Bodybuilding gibt es ein fundamentales Prinzip, das diejenigen trennt, die erstaunliche Ergebnisse erzielen, von denen, die schnell stagnieren: <strong>progressive Überlastung</strong>. Es ist jedoch unmöglich, dieses Prinzip effektiv anzuwenden, wenn Sie keine detaillierten Aufzeichnungen über Ihre Hebungen führen. In diesem Leitfaden werden wir untersuchen, warum die Verfolgung Ihres Trainings lebenswichtig ist, wie Sie unseren <strong>Fitness-Tracker</strong> nutzen können, um Ihre Gewinne zu maximieren, und welche wissenschaftlichen Grundlagen diese Praxis unterstützen.'
|
|
103
|
+
html: 'In der Welt von Fitness und Bodybuilding gibt es ein fundamentales Prinzip, das diejenigen trennt, die erstaunliche Ergebnisse erzielen, von denen, die schnell stagnieren: <strong>progressive Überlastung</strong>. Es ist jedoch unmöglich, dieses Prinzip effektiv anzuwenden, wenn Sie keine detaillierten Aufzeichnungen über Ihre Hebungen führen. In diesem Leitfaden werden wir untersuchen, warum die Verfolgung Ihres Trainings lebenswichtig ist, wie Sie unseren <strong>Fitness-Tracker</strong> nutzen können, um Ihre Gewinne zu maximieren, und welche wissenschaftlichen Grundlagen diese Praxis unterstützen.'
|
|
118
104
|
},
|
|
119
105
|
{
|
|
120
106
|
type: 'title',
|
|
121
107
|
text: 'Was ist progressive Überlastung?',
|
|
122
|
-
level: 3
|
|
108
|
+
level: 3
|
|
123
109
|
},
|
|
124
110
|
{
|
|
125
111
|
type: 'paragraph',
|
|
126
|
-
html: 'Progressive Überlastung ist die allmähliche Steigerung der Belastung, die während körperlicher Betätigung auf den Körper ausgeübt wird. Damit ein Muskel wächst oder stärker wird, muss er einem Reiz ausgesetzt werden, der größer ist als der, an den er gewöhnt ist. Wenn Sie ins Fitnessstudio gehen und immer das gleiche Gewicht mit den gleichen Wiederholungen und der gleichen Ruhezeit heben, hat Ihr Körper keinen biologischen Grund, sich anzupassen und zu wachsen.'
|
|
112
|
+
html: 'Progressive Überlastung ist die allmähliche Steigerung der Belastung, die während körperlicher Betätigung auf den Körper ausgeübt wird. Damit ein Muskel wächst oder stärker wird, muss er einem Reiz ausgesetzt werden, der größer ist als der, an den er gewöhnt ist. Wenn Sie ins Fitnessstudio gehen und immer das gleiche Gewicht mit den gleichen Wiederholungen und der gleichen Ruhezeit heben, hat Ihr Körper keinen biologischen Grund, sich anzupassen und zu wachsen.'
|
|
127
113
|
},
|
|
128
114
|
{
|
|
129
115
|
type: 'list',
|
|
@@ -133,48 +119,48 @@ export const content: ToolLocaleContent<GymTrackerUI & Record<string, string>> =
|
|
|
133
119
|
'Volumensteigerung: Durchführung von insgesamt mehr Sätzen pro Muskelgruppe.',
|
|
134
120
|
'Ruhereduktion: Die gleiche Arbeit in kürzerer Zeit erledigen.',
|
|
135
121
|
'Formverbesserung: Ausführung der Übung mit überlegener Kontrolle und größerem Bewegungsspielraum.',
|
|
136
|
-
]
|
|
122
|
+
]
|
|
137
123
|
},
|
|
138
124
|
{
|
|
139
125
|
type: 'title',
|
|
140
126
|
text: 'Warum manuelles Protokollieren dem Gedächtnis überlegen ist',
|
|
141
|
-
level: 3
|
|
127
|
+
level: 3
|
|
142
128
|
},
|
|
143
129
|
{
|
|
144
130
|
type: 'paragraph',
|
|
145
|
-
html: 'Viele Sportler machen den Fehler, ihrem Gedächtnis zu vertrauen, um sich daran zu erinnern, wie viel sie letzte Woche gehoben haben. Bei einem typischen Training, das zwischen 5 und 10 verschiedene Übungen umfasst, vergisst man jedoch leicht, ob man beim Bankdrücken 80 kg oder 82,5 kg geschafft hat oder ob es 10 oder 12 Wiederholungen waren. Dieser Mangel an Präzision führt zur Mittelmäßigkeit.'
|
|
131
|
+
html: 'Viele Sportler machen den Fehler, ihrem Gedächtnis zu vertrauen, um sich daran zu erinnern, wie viel sie letzte Woche gehoben haben. Bei einem typischen Training, das zwischen 5 und 10 verschiedene Übungen umfasst, vergisst man jedoch leicht, ob man beim Bankdrücken 80 kg oder 82,5 kg geschafft hat oder ob es 10 oder 12 Wiederholungen waren. Dieser Mangel an Präzision führt zur Mittelmäßigkeit.'
|
|
146
132
|
},
|
|
147
133
|
{
|
|
148
134
|
type: 'tip',
|
|
149
135
|
title: 'Die Kraft der Visualisierung von Fortschritt',
|
|
150
|
-
html: 'Eine ansteigende Linie in einem Diagramm zu sehen, gibt Ihnen den nötigen Schub, um die zusätzliche Wiederholung zu versuchen, die den Unterschied zwischen Stagnation und konsistentem Muskelwachstum ausmacht.'
|
|
136
|
+
html: 'Eine ansteigende Linie in einem Diagramm zu sehen, gibt Ihnen den nötigen Schub, um die zusätzliche Wiederholung zu versuchen, die den Unterschied zwischen Stagnation und konsistentem Muskelwachstum ausmacht.'
|
|
151
137
|
},
|
|
152
138
|
{
|
|
153
139
|
type: 'title',
|
|
154
140
|
text: 'Grundübungen für das Tracking',
|
|
155
|
-
level: 3
|
|
141
|
+
level: 3
|
|
156
142
|
},
|
|
157
143
|
{
|
|
158
144
|
type: 'paragraph',
|
|
159
|
-
html: 'Obwohl alle Übungen wertvoll sind, bieten bestimmte Verbundübungen den besten Überblick über Ihre Gesamtkraft und körperliche Entwicklung. Diese sollten Sie bei Ihrem Tracking priorisieren: <strong>Bankdrücken</strong> für horizontales Drücken, <strong>Überkopfdrücken</strong> für vertikales Drücken, <strong>Klimmzüge</strong> für Zugübungen und <strong>Hip Thrusts</strong> für die Gesäßmuskulatur.'
|
|
145
|
+
html: 'Obwohl alle Übungen wertvoll sind, bieten bestimmte Verbundübungen den besten Überblick über Ihre Gesamtkraft und körperliche Entwicklung. Diese sollten Sie bei Ihrem Tracking priorisieren: <strong>Bankdrücken</strong> für horizontales Drücken, <strong>Überkopfdrücken</strong> für vertikales Drücken, <strong>Klimmzüge</strong> für Zugübungen und <strong>Hip Thrusts</strong> für die Gesäßmuskulatur.'
|
|
160
146
|
},
|
|
161
147
|
{
|
|
162
148
|
type: 'title',
|
|
163
149
|
text: 'So analysieren Sie Ihre Fortschrittsdiagramme',
|
|
164
|
-
level: 3
|
|
150
|
+
level: 3
|
|
165
151
|
},
|
|
166
152
|
{
|
|
167
153
|
type: 'paragraph',
|
|
168
|
-
html: 'Sobald Sie mehrere Trainingseinheiten aufgezeichnet haben, werden Sie Muster erkennen: eine <strong>konstant ansteigende Linie</strong> zeigt den richtigen Weg an, ein <strong>Plateau</strong> deutet darauf hin, dass Sie Ihr Volumen oder Ihre Ruhe anpassen müssen, und ein <strong>Abwärtstrend</strong> kann ein Zeichen für akkumulierte Ermüdung sein.'
|
|
154
|
+
html: 'Sobald Sie mehrere Trainingseinheiten aufgezeichnet haben, werden Sie Muster erkennen: eine <strong>konstant ansteigende Linie</strong> zeigt den richtigen Weg an, ein <strong>Plateau</strong> deutet darauf hin, dass Sie Ihr Volumen oder Ihre Ruhe anpassen müssen, und ein <strong>Abwärtstrend</strong> kann ein Zeichen für akkumulierte Ermüdung sein.'
|
|
169
155
|
},
|
|
170
156
|
{
|
|
171
157
|
type: 'title',
|
|
172
158
|
text: 'Die Psychologie des Erfolgs im Fitnessstudio',
|
|
173
|
-
level: 3
|
|
159
|
+
level: 3
|
|
174
160
|
},
|
|
175
161
|
{
|
|
176
162
|
type: 'paragraph',
|
|
177
|
-
html: 'Training ist sowohl eine mentale als auch eine körperliche Herausforderung. Durch die Verwendung eines visuellen Werkzeugs, das Ihnen zeigt, dass Sie heute 1 % stärker sind als vor vierzehn Tagen, füttern Sie Ihr Dopamin-Belohnungssystem. Dies schafft eine positive Rückkopplungsschleife, die das Training zu einer nachhaltigen Gewohnheit macht.'
|
|
163
|
+
html: 'Training ist sowohl eine mentale als auch eine körperliche Herausforderung. Durch die Verwendung eines visuellen Werkzeugs, das Ihnen zeigt, dass Sie heute 1 % stärker sind als vor vierzehn Tagen, füttern Sie Ihr Dopamin-Belohnungssystem. Dies schafft eine positive Rückkopplungsschleife, die das Training zu einer nachhaltigen Gewohnheit macht.'
|
|
178
164
|
},
|
|
179
165
|
],
|
|
180
166
|
ui: {
|
|
@@ -220,6 +206,6 @@ export const content: ToolLocaleContent<GymTrackerUI & Record<string, string>> =
|
|
|
220
206
|
lunges: 'Ausfallschritte',
|
|
221
207
|
gluteKick: 'Kickbacks am Kabel',
|
|
222
208
|
hipAbduction: 'Abduktorenmaschine',
|
|
223
|
-
stepUp: 'Step Ups'
|
|
224
|
-
}
|
|
209
|
+
stepUp: 'Step Ups'
|
|
210
|
+
}
|
|
225
211
|
};
|