@jjlmoya/utils-converters 1.19.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 +1 -1
- package/src/tests/translation_copy.test.ts +123 -0
- package/src/tool/pngAJpg/i18n/fr.ts +40 -40
- package/src/tool/pngAJpg/i18n/id.ts +40 -40
- package/src/tool/pngAJpg/i18n/it.ts +40 -40
- package/src/tool/pngAJpg/i18n/pl.ts +40 -40
- package/src/tool/pngAJpg/i18n/pt.ts +40 -40
- package/src/tool/pngAJpg/i18n/sv.ts +40 -40
- package/src/tool/pngAJpg/i18n/tr.ts +40 -40
package/package.json
CHANGED
|
@@ -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: 'Pourquoi choisir notre convertisseur local PNG vers JPG ?',
|
|
25
25
|
answer:
|
|
26
|
-
'
|
|
26
|
+
'Contrairement aux services classiques, cet outil traite les fichiers entièrement dans le navigateur. Vos images ne touchent jamais un disque distant: vous gardez le contrôle et la confidentialité de vos données.',
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
question: '
|
|
29
|
+
question: 'JPG ou PNG: quel format choisir ?',
|
|
30
30
|
answer:
|
|
31
|
-
'
|
|
31
|
+
'Le PNG convient aux logos et aux éléments transparents. Le JPG est préférable pour les photos et les bannières web, car ses fichiers sont bien plus légers et accélèrent le chargement des pages.',
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
question: '
|
|
34
|
+
question: 'Comment convertir une image sans rien envoyer ?',
|
|
35
35
|
answer:
|
|
36
|
-
|
|
36
|
+
"Nous utilisons le Canvas HTML5. Le navigateur recrée l'image dans une zone virtuelle invisible, remplit la transparence en blanc et produit directement les octets JPEG à télécharger.",
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
|
-
question: '
|
|
39
|
+
question: 'Est-ce sûr pour des documents confidentiels ?',
|
|
40
40
|
answer:
|
|
41
|
-
|
|
41
|
+
"Oui. Pour la banque, la santé ou le droit, il est préférable de ne pas envoyer les images à un service externe: le traitement reste dans la RAM et s'arrête à la fermeture de l'onglet.",
|
|
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: '
|
|
63
|
+
text: 'Convertisseur PNG vers JPG: guide pratique d\'optimisation des images',
|
|
64
64
|
level: 2,
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
type: 'paragraph',
|
|
68
|
-
html: '
|
|
68
|
+
html: 'Dans le design numérique et le développement web, la rapidité compte. Le PNG conserve la transparence et la qualité sans perte, mais produit parfois des fichiers lourds. Pour des pages rapides et des pièces jointes légères, le JPG est souvent plus adapté.',
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
type: 'title',
|
|
72
|
-
text: '
|
|
72
|
+
text: 'JPG ou PNG: lequel choisir ?',
|
|
73
73
|
level: 3,
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
type: 'paragraph',
|
|
77
|
-
html:
|
|
77
|
+
html: "Aucun format n'est meilleur dans tous les cas. Le PNG convient aux interfaces, logos et fonds transparents, mais sa compression sans perte peut donner des fichiers beaucoup plus volumineux.",
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
type: 'paragraph',
|
|
81
|
-
html:
|
|
81
|
+
html: "Le JPG supprime les détails peu visibles et produit des images légères. Il convient aux photos, bannières et réseaux sociaux: une légère perte en échange d'un chargement plus rapide.",
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
type: 'title',
|
|
85
|
-
text: '
|
|
85
|
+
text: 'Comparaison technique: traitement local et cloud',
|
|
86
86
|
level: 3,
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
type: 'comparative',
|
|
90
90
|
items: [
|
|
91
91
|
{
|
|
92
|
-
title: '
|
|
93
|
-
description: '
|
|
92
|
+
title: 'Convertisseurs cloud',
|
|
93
|
+
description: 'Services classiques qui envoient vos photos vers un serveur distant.',
|
|
94
94
|
icon: 'mdi:cloud-upload',
|
|
95
95
|
pointIcon: 'mdi:close-circle-outline',
|
|
96
96
|
points: [
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'
|
|
100
|
-
'
|
|
97
|
+
'Latence réseau (envoi/téléchargement)',
|
|
98
|
+
'Risque de fuite de données privées',
|
|
99
|
+
'Limite de taille par fichier',
|
|
100
|
+
'Publicités et traceurs',
|
|
101
101
|
],
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
|
-
title: '
|
|
105
|
-
description: '
|
|
104
|
+
title: 'Notre architecture locale',
|
|
105
|
+
description: 'Traitement direct sur votre appareil avec du JavaScript natif.',
|
|
106
106
|
icon: 'mdi:laptop-mac',
|
|
107
107
|
highlight: true,
|
|
108
108
|
points: [
|
|
109
|
-
'
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
109
|
+
'Vitesse immédiate sans réseau',
|
|
110
|
+
'Confidentialité garantie (zéro octet envoyé)',
|
|
111
|
+
'Aucune limite de Mo par fichier',
|
|
112
|
+
'Interface professionnelle et claire',
|
|
113
113
|
],
|
|
114
114
|
},
|
|
115
115
|
],
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
type: 'title',
|
|
119
|
-
text: '
|
|
119
|
+
text: 'Fonctionnement de la conversion technique',
|
|
120
120
|
level: 3,
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
type: 'paragraph',
|
|
124
|
-
html:
|
|
124
|
+
html: "La conversion sans serveur s'appuie sur les API modernes du navigateur. Le fichier devient un Blob temporaire en RAM, puis il est dessiné dans un Canvas HTML5 invisible.",
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
type: 'paragraph',
|
|
128
|
-
html:
|
|
128
|
+
html: "Le JPG ne gère pas la transparence: les zones transparentes sont donc remplies de blanc avant le rendu. Le Canvas génère ensuite le flux JPEG enregistré directement sur l'appareil.",
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
131
|
type: 'tip',
|
|
132
|
-
title: '
|
|
133
|
-
html: '
|
|
132
|
+
title: 'Conseil SEO: le poids idéal',
|
|
133
|
+
html: 'Une image plus légère peut améliorer le LCP et les indicateurs PageSpeed. Réduire un PNG volumineux en JPG est particulièrement utile pour les en-têtes et les pages riches en images.',
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
type: 'title',
|
|
137
|
-
text: '
|
|
137
|
+
text: 'Sécurité pour les entreprises et les professionnels',
|
|
138
138
|
level: 3,
|
|
139
139
|
},
|
|
140
140
|
{
|
|
141
141
|
type: 'paragraph',
|
|
142
|
-
html: '
|
|
142
|
+
html: 'Pour la banque, la santé ou le droit, envoyer des fichiers à un service externe peut présenter un risque. Ici le traitement reste dans la RAM du navigateur, sans transfert vers un cloud.',
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
type: 'title',
|
|
146
|
-
text: '
|
|
146
|
+
text: 'Compatibilité du résultat',
|
|
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
|
+
'Visionneuses Windows, macOS et appareils mobiles.',
|
|
154
|
+
'Réseaux sociaux (Instagram, LinkedIn, etc.).',
|
|
155
|
+
'Outils bureautiques (Word, PowerPoint).',
|
|
156
|
+
'Gestionnaires de contenu (WordPress, Shopify).',
|
|
157
157
|
],
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
type: 'title',
|
|
161
|
-
text: '
|
|
161
|
+
text: 'Conclusion: optimisez avec méthode',
|
|
162
162
|
level: 3,
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
type: 'paragraph',
|
|
166
|
-
html: '
|
|
166
|
+
html: 'Ce convertisseur associe rapidité et confidentialité. Développeur ou particulier, vous pouvez alléger vos images sans remettre vos fichiers à un service tiers.',
|
|
167
167
|
},
|
|
168
168
|
];
|
|
169
169
|
|
|
@@ -21,24 +21,24 @@ const ui: ImageConverterUI = {
|
|
|
21
21
|
|
|
22
22
|
const faq: PngAJpgLocaleContent['faq'] = [
|
|
23
23
|
{
|
|
24
|
-
question: '
|
|
24
|
+
question: 'Mengapa memilih konverter PNG ke JPG lokal kami?',
|
|
25
25
|
answer:
|
|
26
|
-
'
|
|
26
|
+
'Berbeda dari layanan biasa, alat ini memproses file sepenuhnya di browser. Gambar Anda tidak dikirim ke disk jarak jauh, sehingga kendali dan privasi data tetap terjaga.',
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
question: '
|
|
29
|
+
question: 'JPG atau PNG: mana yang cocok untuk saya?',
|
|
30
30
|
answer:
|
|
31
|
-
'
|
|
31
|
+
'PNG cocok untuk logo dan elemen transparan. JPG lebih sesuai untuk foto dan banner web karena ukurannya jauh lebih kecil sehingga halaman dimuat lebih cepat.',
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
question: '
|
|
34
|
+
question: 'Bagaimana konversi bekerja tanpa mengunggah file?',
|
|
35
35
|
answer:
|
|
36
|
-
'
|
|
36
|
+
'Kami menggunakan Canvas HTML5. Browser membuat ulang gambar pada kanvas virtual yang tidak terlihat, mengisi area transparan dengan putih, lalu menghasilkan data JPEG untuk diunduh.',
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
|
-
question: '
|
|
39
|
+
question: 'Apakah aman untuk dokumen rahasia?',
|
|
40
40
|
answer:
|
|
41
|
-
'
|
|
41
|
+
'Ya. Untuk dokumen perbankan, kesehatan, atau hukum, pemrosesan lokal lebih aman: data hanya berada di RAM perangkat dan berhenti saat tab ditutup.',
|
|
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: '
|
|
63
|
+
text: 'Konverter PNG ke JPG: panduan praktis mengoptimalkan gambar',
|
|
64
64
|
level: 2,
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
type: 'paragraph',
|
|
68
|
-
html: '
|
|
68
|
+
html: 'Dalam desain digital dan pengembangan web, kecepatan sangat penting. PNG mempertahankan transparansi dan kualitas tanpa kehilangan, tetapi ukurannya bisa besar. Untuk halaman cepat dan lampiran ringan, JPG sering menjadi pilihan praktis.',
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
type: 'title',
|
|
72
|
-
text: '
|
|
72
|
+
text: 'JPG atau PNG: mana yang sebaiknya dipilih?',
|
|
73
73
|
level: 3,
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
type: 'paragraph',
|
|
77
|
-
html: '
|
|
77
|
+
html: 'Tidak ada format terbaik untuk semua kebutuhan. PNG cocok untuk antarmuka, logo, dan latar transparan, tetapi kompresi tanpa kehilangan dapat menghasilkan file yang jauh lebih besar.',
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
type: 'paragraph',
|
|
81
|
-
html: '
|
|
81
|
+
html: 'JPG mengurangi detail yang sulit dilihat mata sehingga ukuran gambar lebih ringan. Format ini cocok untuk foto, banner, dan media sosial ketika kecepatan unduh lebih penting.',
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
type: 'title',
|
|
85
|
-
text: '
|
|
85
|
+
text: 'Perbandingan teknis: proses lokal dan cloud',
|
|
86
86
|
level: 3,
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
type: 'comparative',
|
|
90
90
|
items: [
|
|
91
91
|
{
|
|
92
|
-
title: '
|
|
93
|
-
description: '
|
|
92
|
+
title: 'Konverter berbasis cloud',
|
|
93
|
+
description: 'Layanan tradisional yang mengunggah foto ke server jarak jauh.',
|
|
94
94
|
icon: 'mdi:cloud-upload',
|
|
95
95
|
pointIcon: 'mdi:close-circle-outline',
|
|
96
96
|
points: [
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'
|
|
100
|
-
'
|
|
97
|
+
'Latensi jaringan (unggah/unduh)',
|
|
98
|
+
'Risiko kebocoran data pribadi',
|
|
99
|
+
'Batas ukuran file',
|
|
100
|
+
'Iklan dan pelacak',
|
|
101
101
|
],
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
|
-
title: '
|
|
105
|
-
description: '
|
|
104
|
+
title: 'Arsitektur lokal kami',
|
|
105
|
+
description: 'Pemrosesan langsung pada perangkat dengan JavaScript asli.',
|
|
106
106
|
icon: 'mdi:laptop-mac',
|
|
107
107
|
highlight: true,
|
|
108
108
|
points: [
|
|
109
|
-
'
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
109
|
+
'Kecepatan langsung tanpa jaringan',
|
|
110
|
+
'Privasi terjamin (nol byte dikirim)',
|
|
111
|
+
'Tanpa batas MB per file',
|
|
112
|
+
'Antarmuka bersih dan profesional',
|
|
113
113
|
],
|
|
114
114
|
},
|
|
115
115
|
],
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
type: 'title',
|
|
119
|
-
text: '
|
|
119
|
+
text: 'Cara kerja konversi teknis',
|
|
120
120
|
level: 3,
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
type: 'paragraph',
|
|
124
|
-
html: '
|
|
124
|
+
html: 'Konversi tanpa server memanfaatkan API browser modern. Saat file dipilih, browser membuat Blob sementara di RAM lalu menggambarnya pada Canvas HTML5 yang tidak terlihat.',
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
type: 'paragraph',
|
|
128
|
-
html: '
|
|
128
|
+
html: 'JPG tidak mendukung transparansi, sehingga area transparan diisi putih sebelum gambar dirender. Canvas kemudian menghasilkan data JPEG yang langsung disimpan di perangkat.',
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
131
|
type: 'tip',
|
|
132
|
-
title: '
|
|
133
|
-
html: '
|
|
132
|
+
title: 'Tips SEO: ukuran yang ideal',
|
|
133
|
+
html: 'Gambar yang lebih ringan dapat memperbaiki LCP dan metrik PageSpeed. Mengubah PNG besar menjadi JPG yang lebih kecil berguna untuk header dan halaman dengan banyak gambar.',
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
type: 'title',
|
|
137
|
-
text: '
|
|
137
|
+
text: 'Keamanan untuk perusahaan dan profesional',
|
|
138
138
|
level: 3,
|
|
139
139
|
},
|
|
140
140
|
{
|
|
141
141
|
type: 'paragraph',
|
|
142
|
-
html: '
|
|
142
|
+
html: 'Untuk perbankan, kesehatan, dan hukum, mengirim file ke layanan eksternal dapat berisiko. Di sini pemrosesan berlangsung di RAM browser tanpa mengunggah data ke cloud.',
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
type: 'title',
|
|
146
|
-
text: '
|
|
146
|
+
text: 'Kompatibilitas hasil',
|
|
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
|
+
'Penampil Windows, macOS, dan perangkat seluler.',
|
|
154
|
+
'Media sosial (Instagram, LinkedIn, dan lainnya).',
|
|
155
|
+
'Perangkat lunak kantor (Word, PowerPoint).',
|
|
156
|
+
'Pengelola konten (WordPress, Shopify).',
|
|
157
157
|
],
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
type: 'title',
|
|
161
|
-
text: '
|
|
161
|
+
text: 'Kesimpulan: optimalkan dengan tepat',
|
|
162
162
|
level: 3,
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
type: 'paragraph',
|
|
166
|
-
html: '
|
|
166
|
+
html: 'Konverter ini menggabungkan kecepatan dan privasi. Baik Anda pengembang maupun pengguna rumahan, Anda dapat mengecilkan gambar tanpa menyerahkan file ke layanan pihak ketiga.',
|
|
167
167
|
},
|
|
168
168
|
];
|
|
169
169
|
|