@jjlmoya/utils-audiovisual 1.9.0 → 1.11.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 +5 -5
- package/src/category/i18n/en.ts +1 -1
- package/src/category/i18n/fr.ts +1 -1
- package/src/category/i18n/id.ts +2 -2
- package/src/category/i18n/it.ts +2 -2
- package/src/category/i18n/ja.ts +1 -1
- package/src/category/i18n/ko.ts +1 -1
- package/src/category/i18n/nl.ts +2 -2
- package/src/category/i18n/pl.ts +1 -1
- package/src/category/i18n/pt.ts +3 -3
- package/src/category/i18n/ru.ts +2 -2
- package/src/category/i18n/sv.ts +1 -1
- package/src/category/i18n/tr.ts +1 -1
- package/src/category/i18n/zh.ts +1 -1
- package/src/category/index.ts +1 -0
- package/src/tests/category_validation.test.ts +73 -0
- package/src/tests/slug_language_code_format.test.ts +24 -0
package/package.json
CHANGED
package/src/category/i18n/de.ts
CHANGED
|
@@ -2,22 +2,22 @@ import type { CategoryLocaleContent } from '../../types';
|
|
|
2
2
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
4
|
slug: 'audiovisuell-fotografie',
|
|
5
|
-
title: 'Audiovisuelle und Fotografie
|
|
5
|
+
title: 'Audiovisuelle und Fotografie Werkzeuge',
|
|
6
6
|
description: 'Professionelle Tools und Rechner für Filmemacher, Fotografen und Ersteller digitaler audiovisueller Inhalte.',
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'summary',
|
|
10
|
-
title: 'Professionelle audiovisuelle
|
|
10
|
+
title: 'Professionelle audiovisuelle Werkzeuge',
|
|
11
11
|
items: [
|
|
12
12
|
'Intelligente Rechner für Timelapses, Effekte und Komposition.',
|
|
13
|
-
'
|
|
13
|
+
'Datenschutz-Tools zum Bereinigen von EXIF-Metadaten.',
|
|
14
14
|
'Untertitelsynchronisierung und Timing-Bearbeitung.',
|
|
15
15
|
'Auflösungsanalyse, Druckqualität und optimaler Betrachtungsabstand.'
|
|
16
16
|
],
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'title',
|
|
20
|
-
text: '
|
|
20
|
+
text: 'Professioneller Werkzeugkasten für die audiovisuelle Produktion',
|
|
21
21
|
level: 2,
|
|
22
22
|
},
|
|
23
23
|
{
|
|
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
|
|
|
28
28
|
type: 'stats',
|
|
29
29
|
items: [
|
|
30
30
|
{
|
|
31
|
-
value: '
|
|
31
|
+
value: '10+',
|
|
32
32
|
label: 'Spezialisierte Tools',
|
|
33
33
|
icon: 'mdi:cog'
|
|
34
34
|
},
|
package/src/category/i18n/en.ts
CHANGED
package/src/category/i18n/fr.ts
CHANGED
package/src/category/i18n/id.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CategoryLocaleContent } from '../../types';
|
|
2
2
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
|
-
slug: 'audiovisual-fotografi',
|
|
4
|
+
slug: 'audiovisual-dan-fotografi',
|
|
5
5
|
title: 'Alat Audiovisual dan Fotografi',
|
|
6
6
|
description: 'Alat dan kalkulator profesional untuk pembuat film, fotografer, dan pembuat konten audiovisual digital.',
|
|
7
7
|
seo: [
|
|
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
|
|
|
28
28
|
type: 'stats',
|
|
29
29
|
items: [
|
|
30
30
|
{
|
|
31
|
-
value: '
|
|
31
|
+
value: '10+',
|
|
32
32
|
label: 'Alat khusus',
|
|
33
33
|
icon: 'mdi:cog'
|
|
34
34
|
},
|
package/src/category/i18n/it.ts
CHANGED
|
@@ -17,7 +17,7 @@ export const content: CategoryLocaleContent = {
|
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'title',
|
|
20
|
-
text: '
|
|
20
|
+
text: 'Cassetta degli Attrezzi Professionale per la Produzione Audiovisiva',
|
|
21
21
|
level: 2,
|
|
22
22
|
},
|
|
23
23
|
{
|
|
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
|
|
|
28
28
|
type: 'stats',
|
|
29
29
|
items: [
|
|
30
30
|
{
|
|
31
|
-
value: '
|
|
31
|
+
value: '10+',
|
|
32
32
|
label: 'Strumenti specializzati',
|
|
33
33
|
icon: 'mdi:cog'
|
|
34
34
|
},
|
package/src/category/i18n/ja.ts
CHANGED
package/src/category/i18n/ko.ts
CHANGED
package/src/category/i18n/nl.ts
CHANGED
|
@@ -17,7 +17,7 @@ export const content: CategoryLocaleContent = {
|
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'title',
|
|
20
|
-
text: 'Professionele
|
|
20
|
+
text: 'Professionele Gereedschapskist voor Audiovisuele Productie',
|
|
21
21
|
level: 2,
|
|
22
22
|
},
|
|
23
23
|
{
|
|
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
|
|
|
28
28
|
type: 'stats',
|
|
29
29
|
items: [
|
|
30
30
|
{
|
|
31
|
-
value: '
|
|
31
|
+
value: '10+',
|
|
32
32
|
label: 'Specialistische tools',
|
|
33
33
|
icon: 'mdi:cog'
|
|
34
34
|
},
|
package/src/category/i18n/pl.ts
CHANGED
package/src/category/i18n/pt.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CategoryLocaleContent } from '../../types';
|
|
2
2
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
|
-
slug: 'audiovisual-fotografia',
|
|
4
|
+
slug: 'audiovisual-e-fotografia',
|
|
5
5
|
title: 'Ferramentas Audiovisuais e Fotografia',
|
|
6
6
|
description: 'Ferramentas profissionais e calculadoras para cineastas, fotógrafos e criadores de conteúdo audiovisual digital.',
|
|
7
7
|
seo: [
|
|
@@ -17,7 +17,7 @@ export const content: CategoryLocaleContent = {
|
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'title',
|
|
20
|
-
text: '
|
|
20
|
+
text: 'Caixa de Ferramentas Profissional para Produção Audiovisual',
|
|
21
21
|
level: 2,
|
|
22
22
|
},
|
|
23
23
|
{
|
|
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
|
|
|
28
28
|
type: 'stats',
|
|
29
29
|
items: [
|
|
30
30
|
{
|
|
31
|
-
value: '
|
|
31
|
+
value: '10+',
|
|
32
32
|
label: 'Ferramentas especializadas',
|
|
33
33
|
icon: 'mdi:cog'
|
|
34
34
|
},
|
package/src/category/i18n/ru.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CategoryLocaleContent } from '../../types';
|
|
2
2
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
|
-
slug: '
|
|
4
|
+
slug: 'audiovizualnye-i-foto-instrumenty',
|
|
5
5
|
title: 'Аудиовизуальные и фото инструменты',
|
|
6
6
|
description: 'Профессиональные инструменты и калькуляторы для кинематографистов, фотографов и создателей цифрового аудиовизуального контента.',
|
|
7
7
|
seo: [
|
|
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
|
|
|
28
28
|
type: 'stats',
|
|
29
29
|
items: [
|
|
30
30
|
{
|
|
31
|
-
value: '
|
|
31
|
+
value: '10+',
|
|
32
32
|
label: 'Специализированных инструментов',
|
|
33
33
|
icon: 'mdi:cog'
|
|
34
34
|
},
|
package/src/category/i18n/sv.ts
CHANGED
package/src/category/i18n/tr.ts
CHANGED
package/src/category/i18n/zh.ts
CHANGED
package/src/category/index.ts
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { audiovisualCategory } from '../data';
|
|
3
|
+
import type { CategoryLocaleContent } from '../types';
|
|
4
|
+
|
|
5
|
+
const EXPECTED_LOCALES = [
|
|
6
|
+
'de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const sharingLocales = ['ja', 'ko', 'zh'];
|
|
10
|
+
|
|
11
|
+
describe('Category Validation', () => {
|
|
12
|
+
it('should have all 15 required locales', () => {
|
|
13
|
+
const registeredLocales = Object.keys(audiovisualCategory.i18n);
|
|
14
|
+
EXPECTED_LOCALES.forEach((locale) => {
|
|
15
|
+
expect(
|
|
16
|
+
registeredLocales,
|
|
17
|
+
`Category is missing locale "${locale}"`,
|
|
18
|
+
).toContain(locale);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('Category Slug Validation', () => {
|
|
23
|
+
it('every locale should have a unique, translated slug and follow format rules', async () => {
|
|
24
|
+
const slugs = new Map<string, string>();
|
|
25
|
+
const locales = Object.keys(audiovisualCategory.i18n);
|
|
26
|
+
|
|
27
|
+
let enSlug = '';
|
|
28
|
+
if (locales.includes('en')) {
|
|
29
|
+
const enLoader = audiovisualCategory.i18n['en' as keyof typeof audiovisualCategory.i18n];
|
|
30
|
+
const enContent = (await enLoader?.()) as CategoryLocaleContent;
|
|
31
|
+
enSlug = enContent.slug;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const locale of locales) {
|
|
35
|
+
const loader = audiovisualCategory.i18n[locale as keyof typeof audiovisualCategory.i18n];
|
|
36
|
+
const content = (await loader?.()) as CategoryLocaleContent;
|
|
37
|
+
|
|
38
|
+
expect(
|
|
39
|
+
content.slug,
|
|
40
|
+
`Category locale "${locale}" has an invalid slug ("${content.slug}"). Slugs must be transliterated (only a-z, 0-9, and -).`,
|
|
41
|
+
).toMatch(/^[a-z0-9-]+$/);
|
|
42
|
+
|
|
43
|
+
expect(
|
|
44
|
+
content.slug,
|
|
45
|
+
`Category locale "${locale}" slug ("${content.slug}") cannot end with a 2-letter language code (e.g., -ja, -ru, -ko).`,
|
|
46
|
+
).not.toMatch(/-[a-z]{2}$/);
|
|
47
|
+
|
|
48
|
+
if (locale !== 'en') {
|
|
49
|
+
if (sharingLocales.includes(locale)) {
|
|
50
|
+
expect(
|
|
51
|
+
content.slug,
|
|
52
|
+
`Category locale "${locale}" must use the same slug as "en" ("${enSlug}").`,
|
|
53
|
+
).toBe(enSlug);
|
|
54
|
+
} else {
|
|
55
|
+
expect(
|
|
56
|
+
content.slug,
|
|
57
|
+
`Category locale "${locale}" has the same slug as "en" ("${enSlug}"). Cada slug tiene que estar en su propio idioma`,
|
|
58
|
+
).not.toBe(enSlug);
|
|
59
|
+
|
|
60
|
+
if (slugs.has(content.slug)) {
|
|
61
|
+
const previousLocale = slugs.get(content.slug);
|
|
62
|
+
expect(
|
|
63
|
+
false,
|
|
64
|
+
`Category locales "${locale}" and "${previousLocale}" share the same slug ("${content.slug}"). Cada slug tiene que estar en su propia idioma`,
|
|
65
|
+
).toBe(true);
|
|
66
|
+
}
|
|
67
|
+
slugs.set(content.slug, locale);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
import type { ToolLocaleContent } from '../types';
|
|
4
|
+
|
|
5
|
+
describe('Slug Language Code Format Validation', () => {
|
|
6
|
+
ALL_TOOLS.forEach((tool) => {
|
|
7
|
+
describe(`Tool: ${tool.entry.id}`, () => {
|
|
8
|
+
it('slug should not end with 2-letter language codes like -ja, -ru, -ko', async () => {
|
|
9
|
+
const locales = Object.keys(tool.entry.i18n);
|
|
10
|
+
|
|
11
|
+
for (const locale of locales) {
|
|
12
|
+
const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
|
|
13
|
+
const content = (await loader?.()) as ToolLocaleContent;
|
|
14
|
+
|
|
15
|
+
const endsWithLanguageCode = /-[a-z]{2}$/.test(content.slug) && !content.slug.endsWith('-tv') && !content.slug.endsWith('-hd');
|
|
16
|
+
expect(
|
|
17
|
+
endsWithLanguageCode,
|
|
18
|
+
`Tool "${tool.entry.id}" locale "${locale}" slug ("${content.slug}") cannot end with a 2-letter language code (e.g., -ja, -ru, -ko).`,
|
|
19
|
+
).toBe(false);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|