@jjlmoya/utils-converters 1.18.0 → 1.20.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 +2 -2
- package/src/tests/seo_parity.test.ts +60 -0
- package/src/tests/translation_copy.test.ts +123 -0
- package/src/tool/pngAJpg/i18n/de.ts +40 -40
- package/src/tool/pngAJpg/i18n/en.ts +99 -32
- package/src/tool/pngAJpg/i18n/fr.ts +95 -22
- package/src/tool/pngAJpg/i18n/id.ts +36 -36
- package/src/tool/pngAJpg/i18n/it.ts +35 -35
- package/src/tool/pngAJpg/i18n/ja.ts +80 -84
- package/src/tool/pngAJpg/i18n/ko.ts +80 -84
- package/src/tool/pngAJpg/i18n/nl.ts +40 -40
- package/src/tool/pngAJpg/i18n/pl.ts +35 -35
- package/src/tool/pngAJpg/i18n/pt.ts +32 -32
- package/src/tool/pngAJpg/i18n/ru.ts +80 -84
- package/src/tool/pngAJpg/i18n/sv.ts +35 -35
- package/src/tool/pngAJpg/i18n/tr.ts +33 -33
- package/src/tool/pngAJpg/i18n/zh.ts +80 -84
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jjlmoya/utils-converters",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"check": "astro check",
|
|
29
29
|
"type-check": "astro check",
|
|
30
30
|
"test": "vitest run",
|
|
31
|
-
"preversion": "npm run lint && npm run test",
|
|
31
|
+
"preversion": "npm run lint && npm run test && npm run build",
|
|
32
32
|
"postversion": "git push && git push --tags",
|
|
33
33
|
"patch": "npm version patch",
|
|
34
34
|
"minor": "npm version minor",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_ENTRIES } from '../entries';
|
|
3
|
+
import type { KnownLocale } from '../types';
|
|
4
|
+
|
|
5
|
+
interface ExpectedCounts {
|
|
6
|
+
seo: number;
|
|
7
|
+
faq: number;
|
|
8
|
+
howTo: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function countItems(arr: unknown[] | undefined): number {
|
|
12
|
+
return arr?.length ?? 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function verifyLocaleParity(
|
|
16
|
+
entry: typeof ALL_ENTRIES[number],
|
|
17
|
+
loc: KnownLocale,
|
|
18
|
+
expected: ExpectedCounts,
|
|
19
|
+
): Promise<void> {
|
|
20
|
+
const locContent = await entry.i18n[loc]?.();
|
|
21
|
+
expect(locContent, `Locale ${loc} missing content`).toBeDefined();
|
|
22
|
+
|
|
23
|
+
const locSeoCount = countItems(locContent?.seo);
|
|
24
|
+
const locFaqCount = countItems(locContent?.faq);
|
|
25
|
+
const locHowToCount = countItems(locContent?.howTo);
|
|
26
|
+
|
|
27
|
+
expect(
|
|
28
|
+
locSeoCount,
|
|
29
|
+
`Locale ${loc} SEO sections count (${locSeoCount}) must match EN (${expected.seo})`,
|
|
30
|
+
).toBe(expected.seo);
|
|
31
|
+
expect(
|
|
32
|
+
locFaqCount,
|
|
33
|
+
`Locale ${loc} FAQ items count (${locFaqCount}) must match EN (${expected.faq})`,
|
|
34
|
+
).toBe(expected.faq);
|
|
35
|
+
expect(
|
|
36
|
+
locHowToCount,
|
|
37
|
+
`Locale ${loc} HowTo steps count (${locHowToCount}) must match EN (${expected.howTo})`,
|
|
38
|
+
).toBe(expected.howTo);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('SEO & i18n Structural Parity Suite', () => {
|
|
42
|
+
ALL_ENTRIES.forEach((entry) => {
|
|
43
|
+
describe(`Tool: ${entry.id}`, () => {
|
|
44
|
+
it('all 15 locales should have identical SEO section counts and types as English', async () => {
|
|
45
|
+
const enContent = await entry.i18n.en?.();
|
|
46
|
+
expect(enContent).toBeDefined();
|
|
47
|
+
const expected: ExpectedCounts = {
|
|
48
|
+
seo: countItems(enContent?.seo),
|
|
49
|
+
faq: countItems(enContent?.faq),
|
|
50
|
+
howTo: countItems(enContent?.howTo),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const locales = Object.keys(entry.i18n) as KnownLocale[];
|
|
54
|
+
for (const loc of locales) {
|
|
55
|
+
await verifyLocaleParity(entry, loc, expected);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ALL_ENTRIES } from '../entries';
|
|
3
|
+
|
|
4
|
+
const COPY_THRESHOLD = 0.9;
|
|
5
|
+
|
|
6
|
+
const STRUCTURAL_KEYS = new Set([
|
|
7
|
+
'@context',
|
|
8
|
+
'@type',
|
|
9
|
+
'applicationCategory',
|
|
10
|
+
'columns',
|
|
11
|
+
'highlight',
|
|
12
|
+
'icon',
|
|
13
|
+
'level',
|
|
14
|
+
'operatingSystem',
|
|
15
|
+
'position',
|
|
16
|
+
'positive',
|
|
17
|
+
'price',
|
|
18
|
+
'priceCurrency',
|
|
19
|
+
'slug',
|
|
20
|
+
'trend',
|
|
21
|
+
'type',
|
|
22
|
+
'url',
|
|
23
|
+
'value',
|
|
24
|
+
'variant',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
function normalizeText(value: string): string {
|
|
28
|
+
return value
|
|
29
|
+
.replace(/<[^>]*>/g, ' ')
|
|
30
|
+
.replace(/&(?:amp|lt|gt|quot|apos|nbsp);/gi, ' ')
|
|
31
|
+
.replace(/[\u2018\u2019]/g, "'")
|
|
32
|
+
.replace(/[\u201c\u201d]/g, '"')
|
|
33
|
+
.replace(/\s+/g, ' ')
|
|
34
|
+
.trim()
|
|
35
|
+
.toLocaleLowerCase();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function collectText(value: unknown, path: string, parts: string[]): void {
|
|
39
|
+
if (typeof value === 'string') {
|
|
40
|
+
const normalized = normalizeText(value);
|
|
41
|
+
if (normalized.length >= 2) parts.push(normalized);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
value.forEach((item, index) => collectText(item, `${path}[${index}]`, parts));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!value || typeof value !== 'object') return;
|
|
51
|
+
|
|
52
|
+
Object.entries(value).forEach(([key, child]) => {
|
|
53
|
+
if (STRUCTURAL_KEYS.has(key)) return;
|
|
54
|
+
collectText(child, `${path}.${key}`, parts);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function localeCorpus(content: unknown): string {
|
|
59
|
+
if (!content || typeof content !== 'object') return '';
|
|
60
|
+
|
|
61
|
+
const record = content as Record<string, unknown>;
|
|
62
|
+
const parts: string[] = [];
|
|
63
|
+
collectText(record.title, 'title', parts);
|
|
64
|
+
collectText(record.description, 'description', parts);
|
|
65
|
+
collectText(record.faqTitle, 'faqTitle', parts);
|
|
66
|
+
collectText(record.faq, 'faq', parts);
|
|
67
|
+
collectText(record.seo, 'seo', parts);
|
|
68
|
+
collectText(record.schemas, 'schemas', parts);
|
|
69
|
+
return parts.join(' ');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function tokenCounts(text: string): Map<string, number> {
|
|
73
|
+
const counts = new Map<string, number>();
|
|
74
|
+
for (const token of text.match(/[\p{L}\p{N}]+/gu) ?? []) {
|
|
75
|
+
counts.set(token, (counts.get(token) ?? 0) + 1);
|
|
76
|
+
}
|
|
77
|
+
return counts;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function copySimilarity(left: string, right: string): number {
|
|
81
|
+
const leftCounts = tokenCounts(left);
|
|
82
|
+
const rightCounts = tokenCounts(right);
|
|
83
|
+
const leftTotal = [...leftCounts.values()].reduce((sum, count) => sum + count, 0);
|
|
84
|
+
const rightTotal = [...rightCounts.values()].reduce((sum, count) => sum + count, 0);
|
|
85
|
+
if (leftTotal === 0 || rightTotal === 0) return 0;
|
|
86
|
+
|
|
87
|
+
let shared = 0;
|
|
88
|
+
for (const [token, count] of leftCounts) {
|
|
89
|
+
shared += Math.min(count, rightCounts.get(token) ?? 0);
|
|
90
|
+
}
|
|
91
|
+
return (2 * shared) / (leftTotal + rightTotal);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe('Locales must not copy another locale wholesale', () => {
|
|
95
|
+
ALL_ENTRIES.forEach((entry) => {
|
|
96
|
+
it(`${entry.id} is not at least ${COPY_THRESHOLD * 100}% identical to another locale`, async () => {
|
|
97
|
+
const corpora = new Map<string, string>();
|
|
98
|
+
|
|
99
|
+
for (const [locale, loader] of Object.entries(entry.i18n)) {
|
|
100
|
+
if (!loader) continue;
|
|
101
|
+
corpora.set(locale, localeCorpus(await loader()));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const locales = [...corpora.keys()];
|
|
105
|
+
const violations: string[] = [];
|
|
106
|
+
|
|
107
|
+
for (let leftIndex = 0; leftIndex < locales.length; leftIndex += 1) {
|
|
108
|
+
for (let rightIndex = leftIndex + 1; rightIndex < locales.length; rightIndex += 1) {
|
|
109
|
+
const left = locales[leftIndex];
|
|
110
|
+
const right = locales[rightIndex];
|
|
111
|
+
const similarity = copySimilarity(corpora.get(left) ?? '', corpora.get(right) ?? '');
|
|
112
|
+
|
|
113
|
+
if (similarity >= COPY_THRESHOLD) {
|
|
114
|
+
violations.push(`${left} ↔ ${right}: ${(similarity * 100).toFixed(1)}%`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
expect(violations, `Locale copy threshold exceeded in ${entry.id}`).toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
@@ -21,24 +21,24 @@ const ui: ImageConverterUI = {
|
|
|
21
21
|
|
|
22
22
|
const faq: PngAJpgLocaleContent['faq'] = [
|
|
23
23
|
{
|
|
24
|
-
question: '
|
|
24
|
+
question: '¿Por qué elegir nuestro convertidor local de PNG a JPG?',
|
|
25
25
|
answer:
|
|
26
|
-
'
|
|
26
|
+
'A diferencia de las herramientas convencionales, nuestra utilidad procesa los archivos íntegramente en tu navegador. Tus imágenes nunca tocan un disco duro ajeno, garantizando la total soberanía y privacidad de tus datos.',
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
question: 'JPG
|
|
29
|
+
question: '¿JPG o PNG? ¿Cuál es mejor para mi caso?',
|
|
30
30
|
answer:
|
|
31
|
-
'PNG
|
|
31
|
+
'El PNG es ideal para logotipos y elementos con transparencia. Sin embargo, el JPG es el estándar de oro para fotografías y banners web, ya que logra pesos mucho más reducidos, mejorando drásticamente la velocidad de carga de un sitio.',
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
question: '
|
|
34
|
+
question: '¿Cómo funciona la conversión técnica sin subir nada?',
|
|
35
35
|
answer:
|
|
36
|
-
'
|
|
36
|
+
'Utilizamos la potencia del Canvas de HTML5. El navegador recrea la imagen en un lienzo virtual invisible, rellena las transparencias con blanco y genera un flujo de bytes que descargas directamente al instante.',
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
|
-
question: '
|
|
39
|
+
question: '¿Es seguro para documentos confidenciales?',
|
|
40
40
|
answer:
|
|
41
|
-
'
|
|
41
|
+
'Sí, es la opción más segura para profesionales de la banca, salud o derecho. Al ser una "caja negra" que solo usa tu RAM, lo que pasa dentro muere al cerrar la pestaña, evitando filtraciones en nubes de terceros.',
|
|
42
42
|
},
|
|
43
43
|
];
|
|
44
44
|
|
|
@@ -60,110 +60,110 @@ const howTo: PngAJpgLocaleContent['howTo'] = [
|
|
|
60
60
|
const seo: PngAJpgLocaleContent['seo'] = [
|
|
61
61
|
{
|
|
62
62
|
type: 'title',
|
|
63
|
-
text: 'PNG
|
|
63
|
+
text: 'Convertidor de PNG a JPG: La Guía Definitiva para la Optimización de Imágenes',
|
|
64
64
|
level: 2,
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
type: 'paragraph',
|
|
68
|
-
html: '
|
|
68
|
+
html: 'En el mundo del diseño digital y el desarrollo web, la eficiencia lo es todo. El formato PNG (Portable Network Graphics) es amado por su capacidad de mantener transparencias y su compresión sin pérdida, pero tiene un gran enemigo: el peso del archivo. Cuando necesitas que tu sitio web vuele o que tus emails carguen instantáneamente, el paso de PNG a JPG es la decisión técnica más inteligente.',
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
type: 'title',
|
|
72
|
-
text: 'JPG
|
|
72
|
+
text: '¿JPG o PNG?',
|
|
73
73
|
level: 3,
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
type: 'paragraph',
|
|
77
|
-
html: '
|
|
77
|
+
html: 'No existe un formato mejor que otro, sino una herramienta para cada necesidad. El PNG es un formato sin pérdida, ideal para maquetas de interfaces, logotipos con texto pequeño y elementos visuales que requieren un fondo transparente. Sin embargo, esta fidelidad tiene un coste: archivos que pueden ser 5 o 10 veces más pesados que su equivalente comprimido.',
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
type: 'paragraph',
|
|
81
|
-
html: 'JPG (Joint Photographic Experts Group)
|
|
81
|
+
html: 'El JPG (Joint Photographic Experts Group), por otro lado, utiliza algoritmos de discretización para eliminar información que el ojo humano apenas percibe, logrando pesos pluma. Es el estándar de oro para fotografías, banners publicitarios y redes sociales. Al convertir tus PNG a JPG, estás traduciendo fidelidad geométrica por velocidad de red.',
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
type: 'title',
|
|
85
|
-
text: '
|
|
85
|
+
text: 'Comparativa de Arquitectura: Local vs Nube',
|
|
86
86
|
level: 3,
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
type: 'comparative',
|
|
90
90
|
items: [
|
|
91
91
|
{
|
|
92
|
-
title: 'Cloud
|
|
93
|
-
description: '
|
|
92
|
+
title: 'Convertidores Cloud',
|
|
93
|
+
description: 'Herramientas tradicionales que suben tus fotos a un servidor remoto.',
|
|
94
94
|
icon: 'mdi:cloud-upload',
|
|
95
95
|
pointIcon: 'mdi:close-circle-outline',
|
|
96
96
|
points: [
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'
|
|
100
|
-
'
|
|
97
|
+
'Latencia de red (Upload/Download)',
|
|
98
|
+
'Riesgo de filtración de datos privados',
|
|
99
|
+
'Límites de tamaño por archivo',
|
|
100
|
+
'Publicidad y rastreadores',
|
|
101
101
|
],
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
|
-
title: '
|
|
105
|
-
description: '
|
|
104
|
+
title: 'Nuestra Arquitectura Local',
|
|
105
|
+
description: 'Procesamiento directo en tu hardware mediante tecnología Vanilla JS.',
|
|
106
106
|
icon: 'mdi:laptop-mac',
|
|
107
107
|
highlight: true,
|
|
108
108
|
points: [
|
|
109
|
-
'
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
109
|
+
'Velocidad instantánea sin red',
|
|
110
|
+
'Privacidad garantizada (0 bytes enviados)',
|
|
111
|
+
'Sin límites de MB por archivo',
|
|
112
|
+
'Interfaz profesional y limpia',
|
|
113
113
|
],
|
|
114
114
|
},
|
|
115
115
|
],
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
type: 'title',
|
|
119
|
-
text: '
|
|
119
|
+
text: 'Cómo funciona la conversión técnica',
|
|
120
120
|
level: 3,
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
type: 'paragraph',
|
|
124
|
-
html: '
|
|
124
|
+
html: 'Probablemente te preguntes cómo es posible convertir una imagen sin enviarla a un servidor. La magia reside en la potencia de los navegadores modernos. Cuando seleccionas un archivo, generamos un Blob que solo existe en tu RAM. Ese Blob se dibuja en un elemento HTML5 Canvas invisible.',
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
type: 'paragraph',
|
|
128
|
-
html: '
|
|
128
|
+
html: 'Dado que el JPG no soporta transparencias, nuestro algoritmo rellena el fondo con un color blanco sólido antes de "pintar" el PNG encima. Una vez compuesta la imagen, ejecutamos el método de exportación nativo, generando un flujo de bytes que tu ordenador descarga directamente.',
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
131
|
type: 'tip',
|
|
132
|
-
title: 'SEO
|
|
133
|
-
html: 'Google
|
|
132
|
+
title: 'Consejo SEO: El Peso Ideal',
|
|
133
|
+
html: 'Google penaliza activamente los sitios web lentos. Si tu Largest Contentful Paint (LCP) es alto por culpa de un PNG de cabecera de 2MB, convertirlo a un JPG de 200KB puede mejorar tus métricas de PageSpeed instantáneamente sin diferencias visuales.',
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
type: 'title',
|
|
137
|
-
text: '
|
|
137
|
+
text: 'Seguridad para Empresas y Profesionales',
|
|
138
138
|
level: 3,
|
|
139
139
|
},
|
|
140
140
|
{
|
|
141
141
|
type: 'paragraph',
|
|
142
|
-
html: '
|
|
142
|
+
html: 'Si trabajas en sectores sensibles como la banca, la salud o el derecho, subir archivos a conversores online es una violación de seguridad. Nuestra herramienta funciona como una "caja negra": lo que pasa dentro se queda en tu RAM. Es la única forma segura de trabajar con documentos confidenciales.',
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
type: 'title',
|
|
146
|
-
text: '
|
|
146
|
+
text: 'Compatibilidad del Resultado',
|
|
147
147
|
level: 3,
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
type: 'list',
|
|
151
151
|
icon: 'mdi:check-circle',
|
|
152
152
|
items: [
|
|
153
|
-
'
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
'
|
|
153
|
+
'Visores de Windows, macOS y dispositivos móviles.',
|
|
154
|
+
'Redes sociales (Instagram, LinkedIn, etc).',
|
|
155
|
+
'Herramientas de ofimática (Word, PowerPoint).',
|
|
156
|
+
'Gestores de contenido (WordPress, Shopify).',
|
|
157
157
|
],
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
type: 'title',
|
|
161
|
-
text: '
|
|
161
|
+
text: 'Conclusión: Optimiza como un Pro',
|
|
162
162
|
level: 3,
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
type: 'paragraph',
|
|
166
|
-
html: '
|
|
166
|
+
html: 'Este convertidor no es solo una página más; es una pieza de ingeniería diseñada para facilitarte la vida. Ya seas un desarrollador o un usuario doméstico, aquí tienes la solución definitiva para ahorrar megabytes y mantener tus datos a salvo.',
|
|
167
167
|
},
|
|
168
168
|
];
|
|
169
169
|
|
|
@@ -6,7 +6,7 @@ import type { PngAJpgLocaleContent } from '../index';
|
|
|
6
6
|
const slug = 'png-to-jpg-converter';
|
|
7
7
|
const title = 'Convert PNG to JPG Online';
|
|
8
8
|
const description =
|
|
9
|
-
'Convert PNG images to JPG directly in your browser. No
|
|
9
|
+
'Convert PNG images to JPG format directly in your browser. No server uploads. Fast batch image optimization and guaranteed privacy.';
|
|
10
10
|
|
|
11
11
|
const ui: ImageConverterUI = {
|
|
12
12
|
dragText: 'Drag PNG files...',
|
|
@@ -21,85 +21,152 @@ const ui: ImageConverterUI = {
|
|
|
21
21
|
|
|
22
22
|
const faq: PngAJpgLocaleContent['faq'] = [
|
|
23
23
|
{
|
|
24
|
-
question: 'Why
|
|
24
|
+
question: 'Why choose our local PNG to JPG converter?',
|
|
25
25
|
answer:
|
|
26
|
-
'
|
|
26
|
+
'Unlike conventional online converters, our tool processes all images locally inside your web browser. Your files never touch external servers, guaranteeing total data sovereignty and security.',
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
question: '
|
|
29
|
+
question: 'JPG vs PNG: Which format is best for your use case?',
|
|
30
30
|
answer:
|
|
31
|
-
'
|
|
31
|
+
'PNG is ideal for logos, icons, and graphics requiring transparent backgrounds. JPG is the gold standard for photos and web banners because of its higher compression ratio and significantly smaller file sizes.',
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
question: '
|
|
34
|
+
question: 'How does client-side technical conversion work?',
|
|
35
35
|
answer:
|
|
36
|
-
'
|
|
36
|
+
'We utilize HTML5 Canvas element technology. The browser draws the PNG onto an invisible virtual canvas, fills transparent pixels with solid white, and exports a clean JPEG byte stream directly to your memory.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
question: 'Is it safe for confidential documents and enterprise data?',
|
|
40
|
+
answer:
|
|
41
|
+
'Yes, it is the safest choice for banking, legal, and healthcare professionals. Since processing stays entirely within browser RAM, data vanishes completely when the browser tab is closed.',
|
|
37
42
|
},
|
|
38
43
|
];
|
|
39
44
|
|
|
40
45
|
const howTo: PngAJpgLocaleContent['howTo'] = [
|
|
41
46
|
{
|
|
42
|
-
name: '
|
|
43
|
-
text: '
|
|
47
|
+
name: 'File Selection',
|
|
48
|
+
text: 'Drag your PNG files into the processing box or click to select them from your file explorer.',
|
|
44
49
|
},
|
|
45
50
|
{
|
|
46
|
-
name: '
|
|
47
|
-
text: '
|
|
51
|
+
name: 'Instant Local Conversion',
|
|
52
|
+
text: 'Observe as each file turns to "Ready" status while your browser converts the images locally.',
|
|
48
53
|
},
|
|
49
54
|
{
|
|
50
|
-
name: '
|
|
51
|
-
text: '
|
|
55
|
+
name: 'Optimized Download',
|
|
56
|
+
text: 'Download your newly generated JPG files individually or click "Download All (.zip)" to save everything at once.',
|
|
52
57
|
},
|
|
53
58
|
];
|
|
54
59
|
|
|
55
60
|
const seo: PngAJpgLocaleContent['seo'] = [
|
|
56
61
|
{
|
|
57
62
|
type: 'title',
|
|
58
|
-
text: '
|
|
63
|
+
text: 'PNG to JPG Converter: The Definitive Image Optimization Guide',
|
|
64
|
+
level: 2,
|
|
59
65
|
},
|
|
60
66
|
{
|
|
61
67
|
type: 'paragraph',
|
|
62
|
-
html:
|
|
63
|
-
|
|
68
|
+
html: 'In digital design and web development, efficiency is everything. The PNG (Portable Network Graphics) format is loved for transparency and lossless compression, but its main drawback is file size. When you need fast websites or instant email attachments, converting PNG to JPG is the smartest choice.',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: 'title',
|
|
72
|
+
text: 'JPG or PNG?',
|
|
73
|
+
level: 3,
|
|
64
74
|
},
|
|
65
75
|
{
|
|
66
76
|
type: 'paragraph',
|
|
67
|
-
html:
|
|
68
|
-
'JPG (or JPEG) uses a lossy compression algorithm that drastically reduces file size. For photographs and images where a small loss of detail is acceptable, JPG is the standard choice. A 2 MB PNG can become a 200 KB JPG with virtually identical visual quality.',
|
|
77
|
+
html: 'There is no universally superior format, only the right tool for each job. PNG is lossless and ideal for UI mockups, logos with small text, and graphics requiring transparent backgrounds. However, this fidelity results in files 5 to 10 times larger than compressed equivalents.',
|
|
69
78
|
},
|
|
70
79
|
{
|
|
71
80
|
type: 'paragraph',
|
|
72
|
-
html:
|
|
73
|
-
'Our PNG to JPG converter works entirely in your web browser using the HTML5 Canvas API. The process is simple: the PNG image is loaded into memory, drawn on a virtual canvas, and exported as JPG at optimal quality. Transparent areas in the PNG are replaced with a solid white background, as the JPG format does not support the alpha channel.',
|
|
81
|
+
html: 'JPG (Joint Photographic Experts Group) uses lossy compression algorithms to remove details barely noticeable to the human eye, delivering lightweight files. It is the gold standard for photos, web banners, and social media. Converting PNG to JPG trades micro-fidelity for network speed.',
|
|
74
82
|
},
|
|
75
83
|
{
|
|
76
|
-
type: '
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
type: 'title',
|
|
85
|
+
text: 'Architecture Comparison: Local vs Cloud',
|
|
86
|
+
level: 3,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'comparative',
|
|
90
|
+
items: [
|
|
91
|
+
{
|
|
92
|
+
title: 'Cloud Converters',
|
|
93
|
+
description: 'Traditional tools that upload your files to remote servers.',
|
|
94
|
+
icon: 'mdi:cloud-upload',
|
|
95
|
+
pointIcon: 'mdi:close-circle-outline',
|
|
96
|
+
points: [
|
|
97
|
+
'Network latency (Upload/Download)',
|
|
98
|
+
'Privacy leak risks',
|
|
99
|
+
'File size upload limits',
|
|
100
|
+
'Ads and tracking scripts',
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
title: 'Our Local Architecture',
|
|
105
|
+
description: 'Direct browser hardware processing via Vanilla JS.',
|
|
106
|
+
icon: 'mdi:laptop-mac',
|
|
107
|
+
highlight: true,
|
|
108
|
+
points: [
|
|
109
|
+
'Instant speed without network delays',
|
|
110
|
+
'Guaranteed privacy (0 bytes uploaded)',
|
|
111
|
+
'No file size limits',
|
|
112
|
+
'Clean, ad-free interface',
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'title',
|
|
119
|
+
text: 'How Local Technical Conversion Works',
|
|
120
|
+
level: 3,
|
|
79
121
|
},
|
|
80
122
|
{
|
|
81
123
|
type: 'paragraph',
|
|
82
|
-
html:
|
|
83
|
-
'JPG can reduce the size of a photographic image by 70% to 90% compared to PNG, depending on image complexity and the quality settings applied.',
|
|
124
|
+
html: 'You might wonder how images are converted without a server. The process leverages modern browser APIs. When you select a file, a local Blob is instantiated in your RAM and drawn onto an invisible HTML5 Canvas element.',
|
|
84
125
|
},
|
|
85
126
|
{
|
|
86
127
|
type: 'paragraph',
|
|
87
|
-
html:
|
|
88
|
-
|
|
128
|
+
html: 'Since JPG does not support alpha channels, our algorithm fills transparent areas with a solid white background before rendering. The canvas exports a native JPEG byte stream directly to your device storage.',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: 'tip',
|
|
132
|
+
title: 'SEO Performance Tip',
|
|
133
|
+
html: 'Search engines penalize slow websites. Converting a 2MB hero PNG into a 200KB JPG significantly improves Largest Contentful Paint (LCP) and PageSpeed metrics without noticeable visual loss.',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: 'title',
|
|
137
|
+
text: 'Security for Enterprise & Sensitive Data',
|
|
138
|
+
level: 3,
|
|
89
139
|
},
|
|
90
140
|
{
|
|
91
141
|
type: 'paragraph',
|
|
92
|
-
html:
|
|
93
|
-
|
|
142
|
+
html: 'For financial, medical, or legal professionals, uploading files to third-party conversion sites is a compliance risk. Our local web worker model operates in an isolated environment where data vanishes when the tab closes.',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'title',
|
|
146
|
+
text: 'Output Format Compatibility',
|
|
147
|
+
level: 3,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type: 'list',
|
|
151
|
+
icon: 'mdi:check-circle',
|
|
152
|
+
items: [
|
|
153
|
+
'Built-in image viewers on Windows, macOS, iOS and Android.',
|
|
154
|
+
'Social platforms (Instagram, LinkedIn, X, Facebook).',
|
|
155
|
+
'Office productivity suites (Microsoft Office, Google Workspace).',
|
|
156
|
+
'Content Management Systems (WordPress, Shopify, Webflow).',
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
type: 'title',
|
|
161
|
+
text: 'Conclusion: Optimize Like a Pro',
|
|
162
|
+
level: 3,
|
|
94
163
|
},
|
|
95
164
|
{
|
|
96
165
|
type: 'paragraph',
|
|
97
|
-
html:
|
|
98
|
-
'Convert PNG to JPG instantly, for free, and privately directly in your browser. No registration, no limits, and without compromising the security of your files.',
|
|
166
|
+
html: 'This converter is an engineering solution built for speed and privacy. Whether you are a web developer or home user, it provides the ultimate way to save bandwidth while keeping data safe.',
|
|
99
167
|
},
|
|
100
168
|
];
|
|
101
169
|
|
|
102
|
-
|
|
103
170
|
export const content: PngAJpgLocaleContent = {
|
|
104
171
|
slug,
|
|
105
172
|
title,
|