@jjlmoya/utils-drones 1.32.0 → 1.33.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/category/i18n/de.ts +1 -1
- package/src/category/i18n/en.ts +1 -1
- package/src/category/i18n/es.ts +1 -1
- package/src/category/i18n/fr.ts +1 -1
- package/src/category/i18n/id.ts +1 -1
- package/src/category/i18n/it.ts +1 -1
- package/src/category/i18n/nl.ts +1 -1
- package/src/category/i18n/pl.ts +1 -1
- package/src/category/i18n/pt.ts +1 -1
- package/src/category/i18n/sv.ts +1 -1
- package/src/category/i18n/tr.ts +1 -1
- package/src/tests/bibliography_wellformed_export.test.ts +46 -0
- package/src/tests/category_seo_quality.test.ts +76 -0
- package/src/tests/seo_translation_completeness.test.ts +69 -0
- package/src/tests/seo_wellformed_export.test.ts +65 -0
- package/src/tool/drone-battery-c-rating-calculator/i18n/ru.ts +9 -9
- package/src/tool/drone-battery-c-rating-calculator/i18n/sv.ts +4 -4
- package/src/tool/drone-battery-c-rating-calculator/i18n/tr.ts +6 -6
- package/src/tool/drone-motor-propeller-calculator/i18n/de.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/fr.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/id.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/it.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/ja.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/ko.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/nl.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/pl.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/pt.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/ru.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/sv.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/tr.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/zh.ts +2 -2
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/id.ts +1 -1
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/it.ts +1 -1
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/nl.ts +1 -1
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/pt.ts +1 -1
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/ru.ts +1 -1
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/sv.ts +2 -2
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/tr.ts +1 -1
package/package.json
CHANGED
package/src/category/i18n/de.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Drohnen und Funkamateur Tools & Rechner',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Kostenlose Online-Rechner für Drohnenflüge und Funk: Flugzeit, Akkulaufzeit, GPS-Koordinaten und Antennenlänge schnell prüfen.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
package/src/category/i18n/en.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Drone and Ham Radio Tools and Calculators',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Free online tools for drone flight and radio planning: estimate flight time, battery life, GPS coordinates, and antenna length.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
package/src/category/i18n/es.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Herramientas y Calculadoras para Drones y Radioaficionados',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Herramientas online gratuitas para planificar vuelos de drones y radio: calcula tiempo de vuelo, batería, coordenadas GPS y antenas.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
package/src/category/i18n/fr.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Outils et Calculateurs pour Drones et Radioamateurs',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Outils en ligne gratuits pour préparer vos vols et votre radio; durée de vol, autonomie, coordonnées GPS et longueur d\'antenne.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
package/src/category/i18n/id.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Alat dan Kalkulator untuk Drone dan Amatir Radio',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Alat online gratis untuk merencanakan penerbangan drone dan radio: hitung waktu terbang, daya baterai, koordinat GPS, dan panjang antena.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
package/src/category/i18n/it.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Strumenti e Calcolatori per Droni e Radioamatori',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Strumenti online gratuiti per pianificare voli e radio: stima durata del volo, autonomia, coordinate GPS e lunghezza dell\'antenna.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
package/src/category/i18n/nl.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Tools en Calculators voor Drones en Radioamateurs',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Gratis online tools voor dronevluchten en radio: bereken vliegtijd, batterijduur, GPS-coördinaten en antennelengte.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
package/src/category/i18n/pl.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Narzędzia i Kalkulatory dla Dronów i Krótkofalowców',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Bezpłatne narzędzia online do planowania lotów dronem i łączności: czas lotu, bateria, współrzędne GPS oraz długość anteny.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
package/src/category/i18n/pt.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Ferramentas e Calculadoras para Drones e Radioamadores',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Ferramentas online gratuitas para planejar voos e rádio: estime tempo de voo, bateria, coordenadas GPS e comprimento da antena.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
package/src/category/i18n/sv.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Verktyg och Kalkylatorer för Drönare och Radioamatörer',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Gratis onlineverktyg för drönarflygning och radio: beräkna flygtid, batteritid, GPS-koordinater och antennlängd.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
package/src/category/i18n/tr.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'drones',
|
|
5
5
|
title: 'Drone ve Radyo Amatörleri İçin Araçlar ve Hesaplayıcılar',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Drone uçuşu ve telsiz planlaması için ücretsiz araçlar: uçuş süresini, pil ömrünü, GPS koordinatlarını ve anten uzunluğunu hesaplayın.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { readdirSync, readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join, relative } from 'path';
|
|
4
|
+
|
|
5
|
+
function findBibliographyAstroFiles(dir: string): string[] {
|
|
6
|
+
const files: string[] = [];
|
|
7
|
+
if (!existsSync(dir)) return files;
|
|
8
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
9
|
+
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
const fullPath = join(dir, entry.name);
|
|
12
|
+
if (entry.isDirectory()) {
|
|
13
|
+
files.push(...findBibliographyAstroFiles(fullPath));
|
|
14
|
+
} else if (entry.isFile() && entry.name === 'bibliography.astro') {
|
|
15
|
+
files.push(fullPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return files;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const toolDir = join(process.cwd(), 'src', 'tool');
|
|
23
|
+
const bibliographyFiles = findBibliographyAstroFiles(toolDir);
|
|
24
|
+
|
|
25
|
+
describe('Bibliography Component Wellformed Export', () => {
|
|
26
|
+
it('found tool bibliography.astro components', () => {
|
|
27
|
+
expect(bibliographyFiles.length).toBeGreaterThan(0);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
bibliographyFiles.forEach((file) => {
|
|
31
|
+
const relativePath = relative(process.cwd(), file);
|
|
32
|
+
|
|
33
|
+
it(`${relativePath} should use SharedBibliography from @jjlmoya/utils-shared`, () => {
|
|
34
|
+
const content = readFileSync(file, 'utf-8');
|
|
35
|
+
|
|
36
|
+
const usesSharedComponent =
|
|
37
|
+
content.includes('@jjlmoya/utils-shared') &&
|
|
38
|
+
(content.includes('Bibliography') || content.includes('SharedBibliography'));
|
|
39
|
+
|
|
40
|
+
expect(
|
|
41
|
+
usesSharedComponent,
|
|
42
|
+
`File "${relativePath}" does not use the standard Bibliography component from @jjlmoya/utils-shared, resulting in unstyled or raw bibliography output.`,
|
|
43
|
+
).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { CategoryLocaleContent } from '../types';
|
|
3
|
+
|
|
4
|
+
const EXPECTED_LOCALES = [
|
|
5
|
+
'de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh',
|
|
6
|
+
] as const;
|
|
7
|
+
const COMPACT_LOCALES = new Set(['ja', 'ko', 'zh']);
|
|
8
|
+
|
|
9
|
+
const localeModules = import.meta.glob('../category/i18n/*.ts', { eager: true }) as Record<
|
|
10
|
+
string,
|
|
11
|
+
{ content: CategoryLocaleContent }
|
|
12
|
+
>;
|
|
13
|
+
|
|
14
|
+
function textFrom(value: unknown): string {
|
|
15
|
+
if (typeof value === 'string') return value.replace(/<[^>]*>/g, ' ');
|
|
16
|
+
if (Array.isArray(value)) return value.map(textFrom).join(' ');
|
|
17
|
+
if (value && typeof value === 'object') return Object.values(value).map(textFrom).join(' ');
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const STRONG_CLAIM = /\b(?:garantiz\w*|guarante\w*|validat(?:ed)|valid(?:ated|ado|ada|ados|adas|ée|ées)|actualizad\w*)\b/iu;
|
|
22
|
+
|
|
23
|
+
function getContents(): Map<string, CategoryLocaleContent> {
|
|
24
|
+
return new Map(
|
|
25
|
+
Object.entries(localeModules).map(([file, module]) => [file.match(/\/([a-z]{2})\.ts$/u)?.[1] ?? file, module.content]),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function validateContent(locale: string, content: CategoryLocaleContent): string[] {
|
|
30
|
+
const failures: string[] = [];
|
|
31
|
+
const check = (condition: boolean, message: string) => { if (!condition) failures.push(`${locale}: ${message}`); };
|
|
32
|
+
const minimumTitleLength = COMPACT_LOCALES.has(locale) ? 4 : 12;
|
|
33
|
+
const minimumDescriptionLength = COMPACT_LOCALES.has(locale) ? 20 : 60;
|
|
34
|
+
check(content.title.trim().length >= minimumTitleLength && content.title.trim().length <= 70, 'title must use a useful 4/12–70 character length');
|
|
35
|
+
check(content.description.trim().length >= minimumDescriptionLength && content.description.trim().length <= 180, 'description must use a useful 20/60–180 character length');
|
|
36
|
+
check(content.seo.length >= 2, 'SEO needs at least two sections');
|
|
37
|
+
|
|
38
|
+
const seoText = textFrom(content.seo).replace(/\s+/g, ' ').trim();
|
|
39
|
+
const minimumSeoLength = COMPACT_LOCALES.has(locale) ? 120 : 240;
|
|
40
|
+
check(seoText.length >= minimumSeoLength, 'SEO copy must contain the minimum visible copy for its script');
|
|
41
|
+
check(content.seo.filter((section) => section.type === 'title').length >= 1, 'SEO needs a section heading');
|
|
42
|
+
check(!/[�]/u.test(seoText), 'SEO contains replacement characters');
|
|
43
|
+
check(!/ {2,}/u.test(seoText), 'SEO contains duplicated whitespace');
|
|
44
|
+
check(!STRONG_CLAIM.test(seoText), 'SEO contains an unsupported strong claim');
|
|
45
|
+
|
|
46
|
+
check(content.slug.trim().length > 0 && /^[a-z0-9-]+$/u.test(content.slug), 'slug must be a non-empty URL-safe value');
|
|
47
|
+
return failures;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('Category SEO quality contract', () => {
|
|
51
|
+
it('has one content file for every configured locale', () => {
|
|
52
|
+
const contents = getContents();
|
|
53
|
+
expect([...contents.keys()].sort()).toEqual([...EXPECTED_LOCALES].sort());
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('keeps metadata, SEO structure and copy quality above the minimum in every locale', () => {
|
|
57
|
+
const contents = getContents();
|
|
58
|
+
const english = contents.get('en');
|
|
59
|
+
expect(english, 'English category content is required as the structural reference').toBeDefined();
|
|
60
|
+
|
|
61
|
+
const failures = EXPECTED_LOCALES.flatMap((locale) => {
|
|
62
|
+
const content = contents.get(locale);
|
|
63
|
+
return content ? validateContent(locale, content) : [];
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const englishShape = english?.seo.map((section) => section.type);
|
|
67
|
+
for (const locale of EXPECTED_LOCALES) {
|
|
68
|
+
const content = contents.get(locale);
|
|
69
|
+
if (content && JSON.stringify(content.seo.map((section) => section.type)) !== JSON.stringify(englishShape)) {
|
|
70
|
+
failures.push(`${locale}: SEO section types must match English`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
expect(failures, 'category SEO quality failures').toEqual([]);
|
|
75
|
+
}, 30000);
|
|
76
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
|
|
4
|
+
const ASIAN_LOCALES = ['ja', 'ko', 'zh'];
|
|
5
|
+
|
|
6
|
+
function getSimpleSectionLength(section: any): number {
|
|
7
|
+
if (section.type === 'title') return section.text ? section.text.length : 0;
|
|
8
|
+
if (section.type === 'paragraph') return section.html ? section.html.replace(/<[^>]*>/g, '').length : 0;
|
|
9
|
+
if (section.type === 'list') return section.items ? section.items.join('').length : 0;
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getComplexSectionLength(section: any): number {
|
|
14
|
+
if (section.type === 'diagnostic' || section.type === 'tip') {
|
|
15
|
+
return (section.title ? section.title.length : 0) + (section.html ? section.html.replace(/<[^>]*>/g, '').length : 0);
|
|
16
|
+
}
|
|
17
|
+
if (section.type === 'table') {
|
|
18
|
+
return ((section.headers || []).join('').length) + ((section.rows || []).flat().join('').length);
|
|
19
|
+
}
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getSectionLength(section: any): number {
|
|
24
|
+
return getSimpleSectionLength(section) || getComplexSectionLength(section);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function calculateSeoTextLength(seoSections: any[]): number {
|
|
28
|
+
return seoSections.reduce((acc, section) => acc + getSectionLength(section), 0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function checkLocaleSeoLength(toolId: string, locale: string, localeLen: number, enLen: number): void {
|
|
32
|
+
const isAsian = ASIAN_LOCALES.includes(locale);
|
|
33
|
+
const minLength = Math.floor(enLen * (isAsian ? 0.25 : 0.70));
|
|
34
|
+
const msgType = isAsian ? 'suspiciously short' : 'truncated/lazy';
|
|
35
|
+
|
|
36
|
+
expect(
|
|
37
|
+
localeLen,
|
|
38
|
+
`[LAZY SEO TRANSLATION] Tool "${toolId}" locale "${locale}" SEO text is ${msgType} (${localeLen} chars vs EN ${enLen} chars, expected min ${minLength})`,
|
|
39
|
+
).toBeGreaterThanOrEqual(minLength);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function auditSingleLocale(toolId: string, locale: string, loader: any, enSeoLength: number): Promise<void> {
|
|
43
|
+
if (locale === 'en' || !loader) return;
|
|
44
|
+
const content = await loader();
|
|
45
|
+
if (!content.seo || !Array.isArray(content.seo)) return;
|
|
46
|
+
|
|
47
|
+
checkLocaleSeoLength(toolId, locale, calculateSeoTextLength(content.seo), enSeoLength);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('SEO Translation Completeness & Laziness Audit', () => {
|
|
51
|
+
ALL_TOOLS.forEach(({ entry }) => {
|
|
52
|
+
describe(`Tool: ${entry.id}`, () => {
|
|
53
|
+
it('should not have lazy or truncated SEO content compared to English reference', async () => {
|
|
54
|
+
const enLoader = entry.i18n['en' as keyof typeof entry.i18n];
|
|
55
|
+
if (!enLoader) return;
|
|
56
|
+
|
|
57
|
+
const enContent = await enLoader();
|
|
58
|
+
if (!enContent.seo || !Array.isArray(enContent.seo)) return;
|
|
59
|
+
|
|
60
|
+
const enSeoLength = calculateSeoTextLength(enContent.seo);
|
|
61
|
+
|
|
62
|
+
for (const [locale, loader] of Object.entries(entry.i18n)) {
|
|
63
|
+
await auditSingleLocale(entry.id, locale, loader, enSeoLength);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { readdirSync, readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join, relative } from 'path';
|
|
4
|
+
|
|
5
|
+
function findSeoAstroFiles(dir: string): string[] {
|
|
6
|
+
const files: string[] = [];
|
|
7
|
+
if (!existsSync(dir)) return files;
|
|
8
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
9
|
+
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
const fullPath = join(dir, entry.name);
|
|
12
|
+
if (entry.isDirectory()) {
|
|
13
|
+
files.push(...findSeoAstroFiles(fullPath));
|
|
14
|
+
} else if (entry.isFile() && entry.name === 'seo.astro') {
|
|
15
|
+
files.push(fullPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return files;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const toolDir = join(process.cwd(), 'src', 'tool');
|
|
23
|
+
const seoFiles = findSeoAstroFiles(toolDir);
|
|
24
|
+
|
|
25
|
+
describe('SEO Component Wellformed Export', () => {
|
|
26
|
+
it('found tool seo.astro components', () => {
|
|
27
|
+
expect(seoFiles.length).toBeGreaterThan(0);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
seoFiles.forEach((file) => {
|
|
31
|
+
const relativePath = relative(process.cwd(), file);
|
|
32
|
+
|
|
33
|
+
it(`${relativePath} should dynamically load SEO sections and use SEORenderer`, () => {
|
|
34
|
+
const content = readFileSync(file, 'utf-8');
|
|
35
|
+
|
|
36
|
+
const usesSeoRenderer =
|
|
37
|
+
content.includes('@jjlmoya/utils-shared') &&
|
|
38
|
+
(content.includes('SEORenderer') || content.includes('SEOArticle'));
|
|
39
|
+
|
|
40
|
+
const acquiresDynamicContent =
|
|
41
|
+
content.includes('.i18n') ||
|
|
42
|
+
content.includes('loader') ||
|
|
43
|
+
content.includes('await loader') ||
|
|
44
|
+
content.includes('.seo');
|
|
45
|
+
|
|
46
|
+
const isBrokenPattern =
|
|
47
|
+
/sections\s*=\s*\[\s*\]/.test(content) && !acquiresDynamicContent;
|
|
48
|
+
|
|
49
|
+
expect(
|
|
50
|
+
usesSeoRenderer,
|
|
51
|
+
`File "${relativePath}" does not import or use SEORenderer from @jjlmoya/utils-shared.`,
|
|
52
|
+
).toBe(true);
|
|
53
|
+
|
|
54
|
+
expect(
|
|
55
|
+
isBrokenPattern,
|
|
56
|
+
`File "${relativePath}" relies on a static sections=[] prop default without fetching content from entry.i18n, resulting in empty SEO text on consumers.`,
|
|
57
|
+
).toBe(false);
|
|
58
|
+
|
|
59
|
+
expect(
|
|
60
|
+
acquiresDynamicContent,
|
|
61
|
+
`File "${relativePath}" does not fetch dynamic i18n content for SEO rendering.`,
|
|
62
|
+
).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -126,17 +126,17 @@ const seoSections: SEOSection[] = [
|
|
|
126
126
|
'Высокое сопротивление (>12 мОм на банку): Заметная потеря тяги, сильная просадка и быстрый нагрев.',
|
|
127
127
|
],
|
|
128
128
|
},
|
|
129
|
-
{
|
|
130
|
-
type: 'title',
|
|
131
|
-
text: 'Оптимизация батарей для фристайла и лонгрейндж дронов',
|
|
132
|
-
level: 2,
|
|
133
|
-
},
|
|
129
|
+
{
|
|
130
|
+
type: 'title',
|
|
131
|
+
text: 'Оптимизация батарей для фристайла и лонгрейндж дронов',
|
|
132
|
+
level: 2,
|
|
133
|
+
},
|
|
134
134
|
{
|
|
135
135
|
type: 'paragraph',
|
|
136
|
-
html: 'Разные стили полета требуют разных профилей тока. 5-дюймовые фристайл квадрокоптеры создают всплески тока свыше 120 Ампер, тогда как 7-дюймовые дальнолеты ценят равномерную экономичность. Правильный подбор предотвращает отключение питания в воздухе.',
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
type: 'tip',
|
|
136
|
+
html: 'Разные стили полета требуют разных профилей тока. 5-дюймовые фристайл квадрокоптеры создают всплески тока свыше 120 Ампер, тогда как 7-дюймовые дальнолеты ценят равномерную экономичность. Правильный подбор предотвращает отключение питания в воздухе. Сравнивайте рассчитанный рейтинг C с реальным током конкретной сборки, учитывая возраст аккумулятора, температуру и длину проводов. Результат помогает выбрать безопасный диапазон нагрузки, но перед полетом стоит проверить батарею на стенде и следить за ее нагревом.',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'tip',
|
|
140
140
|
title: 'Совет по хранению LiPo',
|
|
141
141
|
html: 'Храните аккумуляторы LiPo при напряжении 3.80V - 3.85V на банку. Оставление батарей полностью заряженными более 48 часов необратимо увеличивает внутреннее сопротивление.',
|
|
142
142
|
},
|
|
@@ -133,10 +133,10 @@ const seoSections: SEOSection[] = [
|
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
type: 'paragraph',
|
|
136
|
-
html: 'Olika flygstilar kräver olika strömprofiler. 5-tums freestyle-drönare skapar toppar på över 120 Ampere, medan 7-tums long range-drönare kräver jämn effektivitet. Rätt anpassning förhindrar plötsliga strömavbrott i luften.',
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
type: 'tip',
|
|
136
|
+
html: 'Olika flygstilar kräver olika strömprofiler. 5-tums freestyle-drönare skapar toppar på över 120 Ampere, medan 7-tums long range-drönare kräver jämn effektivitet. Rätt anpassning förhindrar plötsliga strömavbrott i luften. Jämför det beräknade C-värdet med den verkliga strömmen i din modell och ta hänsyn till batteriets ålder, temperatur och kabellängd. Resultatet hjälper dig att välja ett rimligt belastningsområde, men gör alltid ett kontrollerat test och övervaka cellernas temperatur före flygning.',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'tip',
|
|
140
140
|
title: 'LiPo Skötseltips',
|
|
141
141
|
html: 'Förvara alltid dina LiPo-batterier vid en lagringsspänning på 3.80V till 3.85V per cell när de inte används. Att lämna batterier fulladdade i mer än 48 timmar ökar den inre resistansen permanent.',
|
|
142
142
|
},
|
|
@@ -131,12 +131,12 @@ const seoSections: SEOSection[] = [
|
|
|
131
131
|
text: 'Serbest Uçuş Yarış ve Uzun Menzil Pil Optimizasyonu',
|
|
132
132
|
level: 2,
|
|
133
133
|
},
|
|
134
|
-
{
|
|
135
|
-
type: 'paragraph',
|
|
136
|
-
html: 'Farklı uçuş tarzları farklı güç profilleri gerektirir. 5 inç serbest uçuş dronları 120 Amper üzeri ani akım sıçramaları oluştururken 7 inç uzun menzil dronları sabit verimlilik arar. Doğru pil eşleşmesi havadaki ani kapanmaları önler.',
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
type: 'tip',
|
|
134
|
+
{
|
|
135
|
+
type: 'paragraph',
|
|
136
|
+
html: 'Farklı uçuş tarzları farklı güç profilleri gerektirir. 5 inç serbest uçuş dronları 120 Amper üzeri ani akım sıçramaları oluştururken 7 inç uzun menzil dronları sabit verimlilik arar. Doğru pil eşleşmesi havadaki ani kapanmaları önler. Hesaplanan C değerini gerçek kurulumun akımıyla karşılaştırırken pilin yaşı, sıcaklığı ve kablo uzunluğunu da değerlendirin. Uçuş süresi, kapasite ve gerçek tüketim birlikte ele alınmalıdır. Hesaplama paketleri karşılaştırmak için bir başlangıç noktasıdır; üretici sınırlarını, bağlantıları ve güvenli iniş için bırakılacak enerji payını ayrıca kontrol edin.',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'tip',
|
|
140
140
|
title: 'LiPo Bakım İpucu',
|
|
141
141
|
html: 'Kullanılmadığında LiPo pillerinizi her zaman hücre başına 3.80V ile 3.85V depolama voltajında saklayın. Pilleri 48 saatten fazla tam dolu bırakmak iç direnci kalıcı olarak artırır.',
|
|
142
142
|
},
|
|
@@ -147,7 +147,7 @@ const seo: SEOSection[] = [
|
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
type: 'paragraph',
|
|
150
|
-
html: 'Propellerschub hängt von der Luftdichte und Geometrie ab. Reale Prüfstandsdaten des Herstellers ermöglichen die genaueste Kalibrierung des Rechners.',
|
|
150
|
+
html: 'Propellerschub hängt von der Luftdichte und Geometrie ab. Reale Prüfstandsdaten des Herstellers ermöglichen die genaueste Kalibrierung des Rechners. Vergleichen Sie mehrere Motor-Propeller-Kombinationen unter denselben Bedingungen. Gesamtgewicht, Spannung unter Last und tatsächlicher Wirkungsgrad verändern Schub und Stromaufnahme. Nutzen Sie die Berechnung nicht ungeprüft für ein anderes Modell: Messen Sie Strom und Temperatur am Prüfstand und lassen Sie vor dem Erstflug ausreichend Reserve.',
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
type: 'tip',
|
|
@@ -147,7 +147,7 @@ const seo: SEOSection[] = [
|
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
type: 'paragraph',
|
|
150
|
-
html: 'La poussée dépend de la géométrie de hélice et de la densité de l air. Un point de mesure réel apporte la meilleure calibration.',
|
|
150
|
+
html: 'La poussée dépend de la géométrie de l hélice et de la densité de l air. Un point de mesure réel apporte la meilleure calibration. Utilisez les résultats pour comparer plusieurs couples moteur-hélice dans les mêmes conditions. Le poids total, la tension sous charge et le rendement réel peuvent modifier la poussée et le courant. Faites un essai sur banc, surveillez la température et gardez une marge avant le premier vol.',
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Daya dorong baling baling bergantung pada kerapatan udara dan bentuk bilah. Data uji nyata memberikan kalibrasi paling akurat.',
|
|
140
|
+
html: 'Daya dorong baling baling bergantung pada kerapatan udara dan bentuk bilah. Data uji nyata memberikan kalibrasi paling akurat. Gunakan hasil ini untuk membandingkan motor dan baling-baling dalam kondisi yang sama. Berat total, tegangan saat dibebani, pengendali, dan pemasangan dapat mengubah daya dorong serta arus. Ukur suhu dan konsumsi di bangku uji, sisakan cadangan sebelum penerbangan pertama, dan verifikasi konfigurasi Anda sendiri.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'La spinta dell elica dipende dalla densità dell aria e dalla geometria. Un dato di banco reale offre la calibrazione più accurata.',
|
|
140
|
+
html: 'La spinta dell elica dipende dalla densità dell aria e dalla geometria. Un dato di banco reale offre la calibrazione più accurata. Usa il risultato per confrontare più combinazioni nelle stesse condizioni, senza trasferire automaticamente un dato da un modello all\'altro. Peso, tensione sotto carico, regolatore e installazione cambiano spinta e corrente. Misura temperatura e assorbimento al banco, poi modifica la configurazione per gradi.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: '
|
|
140
|
+
html: 'プロペラの推力は空気密度やブレード形状に依存します。実測データを使用することで高精度な補正が可能です。計算結果は同じ条件でモーターとプロペラを比較するために使い、機体重量、負荷時の電圧、ESC、取り付け状態も確認してください。初飛行前に電流と温度を測定し、余裕を残して設定を調整します。',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: '프로펠러 추력은 공기 밀도와 형상에 따라 달라집니다. 실측 데이터를 사용하면 가장 정확한 교정이 가능합니다.',
|
|
140
|
+
html: '프로펠러 추력은 공기 밀도와 형상에 따라 달라집니다. 실측 데이터를 사용하면 가장 정확한 교정이 가능합니다. 같은 조건에서 모터와 프로펠러를 비교하고 기체 무게, 부하 전압, ESC, 장착 상태도 확인하세요. 첫 비행 전 전류와 온도를 측정하고 충분한 여유를 두고 설정을 조정합니다.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Propellerstuwkracht hangt af van de luchtdichtheid en vorm. Een echt testpunt biedt de nauwkeurigste kalibratie.',
|
|
140
|
+
html: 'Propellerstuwkracht hangt af van de luchtdichtheid en vorm. Een echt testpunt biedt de nauwkeurigste kalibratie. Gebruik de uitkomst om motoren en propellers onder dezelfde omstandigheden te vergelijken. Gewicht, spanning onder belasting, regelaar en installatie veranderen de stuwkracht en stroom. Meet temperatuur en verbruik op de testbank en houd reserve voor de eerste vlucht. Gebruik waarden van een ander model daarom alleen als vergelijking.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Ciąg śmigła zależy od gęstości powietrza i kształtu łopat. Rzeczywisty pomiar z hamowni zapewnia najwyższą dokładność.',
|
|
140
|
+
html: 'Ciąg śmigła zależy od gęstości powietrza i kształtu łopat. Rzeczywisty pomiar z hamowni zapewnia najwyższą dokładność. Użyj wyniku do porównania silników i śmigieł w tych samych warunkach. Masa, napięcie pod obciążeniem, regulator i montaż zmieniają ciąg oraz prąd. Zmierz temperaturę i pobór na stanowisku, a przed pierwszym lotem zostaw bezpieczny zapas. Traktuj dane z innego modelu wyłącznie jako punkt odniesienia i sprawdź całą konfigurację przed użyciem.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'O empuxo da hélice depende da geometria e densidade do ar. Dados de teste medidos oferecem a melhor calibração.',
|
|
140
|
+
html: 'O empuxo da hélice depende da geometria e densidade do ar. Dados de teste medidos oferecem a melhor calibração. Use o resultado para comparar combinações nas mesmas condições, considerando peso, tensão sob carga, controlador e instalação. Meça corrente e temperatura em bancada e ajuste a configuração gradualmente antes do primeiro voo. Não transfira automaticamente um valor de outro modelo: bateria, montagem e eficiência real também alteram o resultado.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Тяга винта зависит от профиля лопастей и плотности воздуха. Реальные стендовые данные обеспечивают точную калибровку.',
|
|
140
|
+
html: 'Тяга винта зависит от профиля лопастей и плотности воздуха. Реальные стендовые данные обеспечивают точную калибровку. Используйте расчет для сравнения моторов и винтов в одинаковых условиях. Масса, напряжение под нагрузкой, регулятор и установка меняют тягу и ток. Измеряйте температуру и потребление на стенде, а перед первым полетом оставляйте запас. Данные другой модели подходят только как ориентир и требуют проверки на вашей сборке.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Propellerdragkraft beror på luftdensitet och form. Uppmätta testbänksdata ger den mest exakta kalibreringen.',
|
|
140
|
+
html: 'Propellerdragkraft beror på luftdensitet och form. Uppmätta testbänksdata ger den mest exakta kalibreringen. Använd resultatet för att jämföra motorer och propellrar under samma förhållanden. Total vikt, spänning under belastning, reglage och montering påverkar dragkraft och ström. Mät temperatur och förbrukning på testbänken, lämna marginal före första flygningen och verifiera alltid den egna modellen. Dokumentera mätningarna så att du kan följa ändringar i konfigurationen.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Pervane itişi hava yoğunluğuna ve geometriye bağlıdır. Ölçülen test verileri en doğru kalibrasyonu sağlar.',
|
|
140
|
+
html: 'Pervane itişi hava yoğunluğuna ve geometriye bağlıdır. Ölçülen test verileri en doğru kalibrasyonu sağlar. Sonucu aynı koşullarda motor ve pervane seçeneklerini karşılaştırmak için kullanın. Toplam ağırlık, yük altındaki voltaj, hız kontrol cihazı ve montaj şekli itişi ve akımı değiştirir. Test standında sıcaklık ile tüketimi ölçün ve ilk uçuştan önce güvenli bir pay bırakın. Başka bir modelin verilerini yalnızca referans kabul ederek kendi kurulumunuzu ayrıca doğrulayın.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -132,12 +132,12 @@ const seo: SEOSection[] = [
|
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
type: 'title',
|
|
135
|
-
text: '
|
|
135
|
+
text: '测试数据的重要性',
|
|
136
136
|
level: 2,
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: '
|
|
140
|
+
html: '螺旋桨推力取决于空气密度和几何形状。使用厂家台测数据能提供最准确的校准。请在相同条件下比较不同的电机和螺旋桨,并同时检查起飞重量、负载电压、电调和安装状态。首次飞行前测量电流与温度,保留足够余量,再逐步调整配置。其他机型的数据只能作为参考,必须结合自己的电池、机架和实际测试确认。记录每次测试结果,便于复核和改进。不要忽略电池电量变化和环境温度,飞行前再次检查连接,确认设备状态正常。',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'Untuk drone dengan TWR di atas 10 banding 1, disarankan mengaktifkan Throttle PID Attenuation (TPA) pada Betaflight guna mencegah osilasi motor pada kecepatan tinggi.',
|
|
152
|
+
html: 'Untuk drone dengan TWR di atas 10 banding 1, disarankan mengaktifkan Throttle PID Attenuation (TPA) pada Betaflight guna mencegah osilasi motor pada kecepatan tinggi. Gunakan rasio dorong terhadap berat untuk membandingkan konfigurasi dalam kondisi yang sama. Perhitungkan berat saat lepas landas, tegangan ketika dibebani, konsumsi nyata, dan cadangan yang dibutuhkan untuk gaya terbang Anda. Uji secara terkendali sebelum terbang dalam kondisi berat.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'Per droni con TWR superiore a 10 a 1 è opportuno abilitare la funzione Throttle PID Attenuation (TPA) in Betaflight per prevenire oscillazioni a pieno gas e preservare la stabilità.',
|
|
152
|
+
html: 'Per droni con TWR superiore a 10 a 1 è opportuno abilitare la funzione Throttle PID Attenuation (TPA) in Betaflight per prevenire oscillazioni a pieno gas e preservare la stabilità. Il rapporto spinta peso è un riferimento per confrontare configurazioni, non una garanzia di comportamento in volo. Considera il peso pronto al decollo, il consumo reale, la temperatura delle celle e il margine necessario per il tuo stile di pilotaggio. Una prova controllata permette di verificare la risposta prima di usare il drone in condizioni impegnative.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'Voor drones met een TWR hoger dan 10 op 1 is het verstandig om Throttle PID Attenuation (TPA) in te stellen in Betaflight om trillingen bij volgas te voorkomen.',
|
|
152
|
+
html: 'Voor drones met een TWR hoger dan 10 op 1 is het verstandig om Throttle PID Attenuation (TPA) in te stellen in Betaflight om trillingen bij volgas te voorkomen. Gebruik de stuwkracht-gewichtsverhouding om configuraties onder dezelfde omstandigheden te vergelijken. Houd rekening met het startgewicht, de spanning onder belasting, het werkelijke verbruik en de gewenste reserve. Een gecontroleerde test laat zien hoe het model reageert voordat je in veeleisende omstandigheden vliegt.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'Para drones com TWR superior a 10 para 1, recomenda-se configurar a atenuação Throttle PID Attenuation (TPA) no Betaflight para evitar vibrações indesejadas em linha reta.',
|
|
152
|
+
html: 'Para drones com TWR superior a 10 para 1, recomenda-se configurar a atenuação Throttle PID Attenuation (TPA) no Betaflight para evitar vibrações indesejadas em linha reta. A relação empuxo-peso serve para comparar configurações, mas não prevê sozinha o comportamento em voo. Considere o peso pronto para decolar, o consumo real, a temperatura das células e a margem necessária para seu estilo de pilotagem. Compare o empuxo em diferentes níveis de aceleração, registre as medições e faça ajustes graduais antes de voar em condições exigentes.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'Для сборок с TWR выше 10 к 1 рекомендуется включить Throttle PID Attenuation (TPA) в Betaflight, чтобы предотвратить высокочастотные колебания на полном газе.',
|
|
152
|
+
html: 'Для сборок с TWR выше 10 к 1 рекомендуется включить Throttle PID Attenuation (TPA) в Betaflight, чтобы предотвратить высокочастотные колебания на полном газе. Используйте тяговооруженность для сравнения конфигураций, но учитывайте полетный вес, просадку напряжения, реальный ток и запас для своего стиля пилотирования. Контролируемый тест помогает проверить реакцию модели перед полетом в сложных условиях.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|
|
@@ -149,12 +149,12 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'För drönare med TWR över 10 till 1 rekommenderas att aktivera Throttle PID Attenuation (TPA) i Betaflight för att
|
|
152
|
+
html: 'För drönare med TWR över 10 till 1 rekommenderas att aktivera Throttle PID Attenuation (TPA) i Betaflight för att minska vibrationer vid fullgas. Använd förhållandet mellan dragkraft och vikt för att jämföra konfigurationer under samma förhållanden. Ta hänsyn till startvikt, spänning under belastning, verklig förbrukning och den reserv som behövs för din flygstil. Ett kontrollerat test visar hur modellen reagerar innan flygning i krävande miljöer.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|
|
156
156
|
title: 'Tips för Betaflight TPA Inställning',
|
|
157
|
-
html: 'Ställ in TPA
|
|
157
|
+
html: 'Ställ in TPA-brytpunkten på 1250 eller 1350 med en dämpning på 0.65 och kontrollera resultatet med en kontrollerad provflygning.',
|
|
158
158
|
},
|
|
159
159
|
];
|
|
160
160
|
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'TWR si 10 a 1 in üzerinde olan dronlar için tam gazdaki titreşimleri önlemek adına Betaflight Throttle PID Attenuation (TPA) özelliğinin etkinleştirilmesi önerilir.',
|
|
152
|
+
html: 'TWR si 10 a 1 in üzerinde olan dronlar için tam gazdaki titreşimleri önlemek adına Betaflight Throttle PID Attenuation (TPA) özelliğinin etkinleştirilmesi önerilir. İtki-ağırlık oranını yapılandırmaları karşılaştırmak için kullanın; kalkış ağırlığını, yük altındaki voltajı, gerçek tüketimi ve uçuş tarzınız için gereken payı da hesaba katın. Kontrollü bir test, zorlu koşullarda uçmadan önce modelin tepkisini doğrulamanıza yardımcı olur. Sonuçları farklı gaz seviyelerinde karşılaştırmak, kontrol ve uçuş süresini daha gerçekçi değerlendirmeyi sağlar.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|