@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-audiovisual",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -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 Tools',
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 Tools',
10
+ title: 'Professionelle audiovisuelle Werkzeuge',
11
11
  items: [
12
12
  'Intelligente Rechner für Timelapses, Effekte und Komposition.',
13
- 'Privacy-Tools zum Bereinigen von EXIF-Metadaten.',
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: 'Professionelle Toolbox für die audiovisuelle Produktion',
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: '12+',
31
+ value: '10+',
32
32
  label: 'Spezialisierte Tools',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
28
28
  type: 'stats',
29
29
  items: [
30
30
  {
31
- value: '12+',
31
+ value: '10+',
32
32
  label: 'Specialized tools',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
28
28
  type: 'stats',
29
29
  items: [
30
30
  {
31
- value: '12+',
31
+ value: '10+',
32
32
  label: 'Outils spécialisés',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -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: '12+',
31
+ value: '10+',
32
32
  label: 'Alat khusus',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -17,7 +17,7 @@ export const content: CategoryLocaleContent = {
17
17
  },
18
18
  {
19
19
  type: 'title',
20
- text: 'Toolbox Professionale per la Produzione Audiovisiva',
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: '12+',
31
+ value: '10+',
32
32
  label: 'Strumenti specializzati',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
28
28
  type: 'stats',
29
29
  items: [
30
30
  {
31
- value: '12+',
31
+ value: '10+',
32
32
  label: '専門ツール',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
28
28
  type: 'stats',
29
29
  items: [
30
30
  {
31
- value: '12+',
31
+ value: '10+',
32
32
  label: '특화된 도구',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -17,7 +17,7 @@ export const content: CategoryLocaleContent = {
17
17
  },
18
18
  {
19
19
  type: 'title',
20
- text: 'Professionele Toolbox voor Audiovisuele Productie',
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: '12+',
31
+ value: '10+',
32
32
  label: 'Specialistische tools',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
28
28
  type: 'stats',
29
29
  items: [
30
30
  {
31
- value: '12+',
31
+ value: '10+',
32
32
  label: 'Specjalistyczne narzędzia',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -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: 'Toolbox Profissional para Produção Audiovisual',
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: '12+',
31
+ value: '10+',
32
32
  label: 'Ferramentas especializadas',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -1,7 +1,7 @@
1
1
  import type { CategoryLocaleContent } from '../../types';
2
2
 
3
3
  export const content: CategoryLocaleContent = {
4
- slug: 'audiovisual-photography',
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: '12+',
31
+ value: '10+',
32
32
  label: 'Специализированных инструментов',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
28
28
  type: 'stats',
29
29
  items: [
30
30
  {
31
- value: '12+',
31
+ value: '10+',
32
32
  label: 'Specialiserade verktyg',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
28
28
  type: 'stats',
29
29
  items: [
30
30
  {
31
- value: '12+',
31
+ value: '10+',
32
32
  label: 'Uzmanlaşmış araçlar',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -28,7 +28,7 @@ export const content: CategoryLocaleContent = {
28
28
  type: 'stats',
29
29
  items: [
30
30
  {
31
- value: '12+',
31
+ value: '10+',
32
32
  label: '专用工具',
33
33
  icon: 'mdi:cog'
34
34
  },
@@ -43,3 +43,4 @@ export const audiovisualCategory: AudiovisualCategoryEntry = {
43
43
  },
44
44
  };
45
45
 
46
+ export const toolsCategory = audiovisualCategory;
@@ -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
+ });