@jjlmoya/utils-developer 1.30.0 → 1.31.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 +70 -70
- package/src/category/seo.astro +1 -1
- package/src/components/PreviewNavSidebar.astro +1 -1
- package/src/components/PreviewToolbar.astro +1 -1
- package/src/data.ts +1 -1
- package/src/env.d.ts +1 -1
- package/src/layouts/PreviewLayout.astro +1 -1
- package/src/pages/[locale]/[slug].astro +162 -162
- package/src/pages/index.astro +1 -1
- package/src/tests/entry_contract.test.ts +8 -2
- package/src/tests/faq_count.test.ts +1 -1
- package/src/tests/i18n_coverage.test.ts +2 -6
- package/src/tests/inverted_punctuation.test.ts +113 -113
- package/src/tests/mocks/astro_mock.js +1 -1
- package/src/tests/no_h1_in_components.test.ts +1 -1
- package/src/tests/promote_this_website_i18n.test.ts +33 -0
- package/src/tests/slug_language_code_format.test.ts +25 -25
- package/src/tests/slug_uniqueness.test.ts +86 -86
- package/src/tests/tool_validation.test.ts +15 -15
- package/src/tool/aspectRatio/i18n/de.ts +164 -164
- package/src/tool/aspectRatio/i18n/ja.ts +1 -1
- package/src/tool/aspectRatio/i18n/ko.ts +1 -1
- package/src/tool/aspectRatio/i18n/pl.ts +164 -164
- package/src/tool/aspectRatio/i18n/ru.ts +164 -164
- package/src/tool/aspectRatio/i18n/zh.ts +165 -165
- package/src/tool/aspectRatio/seo.astro +15 -15
- package/src/tool/calculadoraTiempoDatos/i18n/de.ts +143 -143
- package/src/tool/calculadoraTiempoDatos/i18n/ja.ts +1 -1
- package/src/tool/calculadoraTiempoDatos/i18n/ko.ts +1 -1
- package/src/tool/calculadoraTiempoDatos/i18n/pl.ts +143 -143
- package/src/tool/calculadoraTiempoDatos/i18n/ru.ts +144 -144
- package/src/tool/calculadoraTiempoDatos/i18n/sv.ts +143 -143
- package/src/tool/calculadoraTiempoDatos/i18n/zh.ts +144 -144
- package/src/tool/calculadoraTiempoDatos/seo.astro +15 -15
- package/src/tool/colorConverter/i18n/de.ts +120 -120
- package/src/tool/colorConverter/i18n/ja.ts +1 -1
- package/src/tool/colorConverter/i18n/ko.ts +1 -1
- package/src/tool/colorConverter/i18n/ru.ts +120 -120
- package/src/tool/colorConverter/i18n/zh.ts +1 -1
- package/src/tool/colorConverter/seo.astro +15 -15
- package/src/tool/conversorExcelCsvHtml/i18n/ja.ts +1 -1
- package/src/tool/conversorExcelCsvHtml/i18n/ko.ts +1 -1
- package/src/tool/conversorExcelCsvHtml/i18n/pl.ts +124 -124
- package/src/tool/conversorExcelCsvHtml/i18n/ru.ts +124 -124
- package/src/tool/conversorExcelCsvHtml/i18n/zh.ts +125 -125
- package/src/tool/conversorExcelCsvHtml/seo.astro +15 -15
- package/src/tool/cronGenerator/i18n/ja.ts +1 -1
- package/src/tool/cronGenerator/i18n/ko.ts +1 -1
- package/src/tool/cronGenerator/i18n/zh.ts +183 -183
- package/src/tool/cronGenerator/seo.astro +15 -15
- package/src/tool/cssSpecificityCalculator/i18n/ja.ts +1 -1
- package/src/tool/cssSpecificityCalculator/i18n/ko.ts +1 -1
- package/src/tool/cssSpecificityCalculator/i18n/zh.ts +1 -1
- package/src/tool/cssSpecificityCalculator/seo.astro +15 -15
- package/src/tool/cssToInlineConverter/i18n/ja.ts +1 -1
- package/src/tool/cssToInlineConverter/i18n/ko.ts +1 -1
- package/src/tool/cssToInlineConverter/i18n/zh.ts +1 -1
- package/src/tool/cssToInlineConverter/seo.astro +15 -15
- package/src/tool/duplicateCssRemover/i18n/ja.ts +1 -1
- package/src/tool/duplicateCssRemover/i18n/ko.ts +1 -1
- package/src/tool/duplicateCssRemover/i18n/zh.ts +1 -1
- package/src/tool/duplicateCssRemover/seo.astro +15 -15
- package/src/tool/generadorSecurityTxt/i18n/ja.ts +1 -1
- package/src/tool/generadorSecurityTxt/i18n/ko.ts +1 -1
- package/src/tool/generadorSecurityTxt/i18n/zh.ts +1 -1
- package/src/tool/generadorSecurityTxt/seo.astro +15 -15
- package/src/tool/hashGenerator/i18n/ja.ts +1 -1
- package/src/tool/hashGenerator/i18n/ko.ts +1 -1
- package/src/tool/hashGenerator/seo.astro +15 -15
- package/src/tool/inspectorCertificadosSsl/i18n/ja.ts +1 -1
- package/src/tool/inspectorCertificadosSsl/i18n/ko.ts +1 -1
- package/src/tool/inspectorCertificadosSsl/i18n/zh.ts +1 -1
- package/src/tool/inspectorCertificadosSsl/seo.astro +15 -15
- package/src/tool/jsonFormatter/seo.astro +15 -15
- package/src/tool/jwtDecoder/i18n/es.ts +248 -248
- package/src/tool/jwtDecoder/i18n/fr.ts +248 -248
- package/src/tool/jwtDecoder/i18n/tr.ts +248 -248
- package/src/tool/keycode/seo.astro +15 -15
- package/src/tool/llmCostCalculator/i18n/fr.ts +149 -149
- package/src/tool/llmCostCalculator/i18n/ja.ts +1 -1
- package/src/tool/llmCostCalculator/i18n/ko.ts +1 -1
- package/src/tool/llmCostCalculator/seo.astro +15 -15
- package/src/tool/mobileMockupGenerator/i18n/ja.ts +1 -1
- package/src/tool/mobileMockupGenerator/i18n/ko.ts +1 -1
- package/src/tool/mobileMockupGenerator/i18n/zh.ts +1 -1
- package/src/tool/mobileMockupGenerator/seo.astro +15 -15
- package/src/tool/musicalTypography/i18n/ja.ts +1 -1
- package/src/tool/musicalTypography/i18n/ko.ts +1 -1
- package/src/tool/musicalTypography/i18n/zh.ts +1 -1
- package/src/tool/musicalTypography/seo.astro +15 -15
- package/src/tool/placeholderGenerator/i18n/ja.ts +1 -1
- package/src/tool/placeholderGenerator/i18n/ko.ts +1 -1
- package/src/tool/placeholderGenerator/i18n/zh.ts +1 -1
- package/src/tool/placeholderGenerator/seo.astro +15 -15
- package/src/tool/promoteThisWebsite/entry.ts +13 -0
- package/src/tool/promoteThisWebsite/i18n/de.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/fr.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/id.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/it.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/ja.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/ko.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/nl.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/pl.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/pt.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/ru.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/sv.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/tr.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/zh.ts +95 -0
- package/src/tool/promptLibrary/i18n/ja.ts +1 -1
- package/src/tool/promptLibrary/i18n/ko.ts +1 -1
- package/src/tool/promptLibrary/i18n/zh.ts +1 -1
- package/src/tool/promptLibrary/seo.astro +15 -15
- package/src/tool/readabilityCalculator/i18n/ja.ts +1 -1
- package/src/tool/readabilityCalculator/i18n/ko.ts +1 -1
- package/src/tool/readabilityCalculator/i18n/zh.ts +1 -1
- package/src/tool/readabilityCalculator/seo.astro +15 -15
- package/src/tool/serpPixelSimulator/entry.ts +30 -30
- package/src/tool/serpPixelSimulator/i18n/en.ts +211 -211
- package/src/tool/serpPixelSimulator/i18n/fr.ts +214 -214
- package/src/tool/serpPixelSimulator/i18n/ru.ts +214 -214
- package/src/tool/serpPixelSimulator/logic.ts +18 -18
- package/src/tool/serpPixelSimulator/serp-pixel-simulator.css +195 -195
- package/src/tool/svgSanitizer/i18n/ja.ts +1 -1
- package/src/tool/svgSanitizer/i18n/ko.ts +1 -1
- package/src/tool/svgSanitizer/seo.astro +15 -15
- package/src/tool/svgToCss/i18n/ja.ts +1 -1
- package/src/tool/svgToCss/i18n/ko.ts +1 -1
- package/src/tool/svgToCss/seo.astro +15 -15
- package/src/tool/urlCleaner/i18n/ja.ts +1 -1
- package/src/tool/urlCleaner/i18n/ko.ts +1 -1
- package/src/tool/urlCleaner/i18n/zh.ts +1 -1
- package/src/tool/urlCleaner/seo.astro +15 -15
- package/src/tool/urlEncoderDecoder/i18n/es.ts +146 -146
- package/src/tool/urlEncoderDecoder/i18n/ko.ts +1 -1
- package/src/tool/urlEncoderDecoder/seo.astro +15 -15
- package/src/tool/utmGenerator/i18n/ja.ts +1 -1
- package/src/tool/utmGenerator/i18n/ko.ts +1 -1
- package/src/tool/utmGenerator/i18n/zh.ts +1 -1
- package/src/tool/utmGenerator/seo.astro +15 -15
- package/src/types.ts +1 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
-
import { promptLibrary } from './index';
|
|
4
|
-
import type { KnownLocale } from '../../types';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
locale?: KnownLocale;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const { locale = 'es' } = Astro.props;
|
|
11
|
-
const content = await promptLibrary.i18n[locale]?.();
|
|
12
|
-
if (!content) return null;
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { promptLibrary } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await promptLibrary.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -38,4 +38,4 @@ export const content: ToolLocaleContent<ReadabilityCalculatorUI> = {
|
|
|
38
38
|
{ type: 'list', items: [ '<strong>Lc 90+:</strong>非常小或低粗细文本的理想选择。', '<strong>Lc 75:</strong>主要正文阅读文本的标准。', '<strong>Lc 60:</strong>可读中等大小内容的最小值。', '<strong>Lc 45:</strong>大型或装饰元素的最小值。' ] },
|
|
39
39
|
],
|
|
40
40
|
};
|
|
41
|
-
|
|
41
|
+
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
-
import { readabilityCalculator } from './index';
|
|
4
|
-
import type { KnownLocale } from '../../types';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
locale?: KnownLocale;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const { locale = 'es' } = Astro.props;
|
|
11
|
-
const content = await readabilityCalculator.i18n[locale]?.();
|
|
12
|
-
if (!content) return null;
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { readabilityCalculator } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await readabilityCalculator.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import type { DeveloperToolEntry, ToolLocaleContent } from '../../types';
|
|
2
|
-
|
|
3
|
-
import type { SerpPixelSimulatorUI } from './ui';
|
|
4
|
-
|
|
5
|
-
export type SerpPixelSimulatorLocaleContent = ToolLocaleContent<SerpPixelSimulatorUI>;
|
|
6
|
-
|
|
7
|
-
export const serpPixelSimulator: DeveloperToolEntry<SerpPixelSimulatorUI> = {
|
|
8
|
-
id: 'serp-pixel-simulator',
|
|
9
|
-
icons: {
|
|
10
|
-
bg: 'mdi:google',
|
|
11
|
-
fg: 'mdi:ruler-square',
|
|
12
|
-
},
|
|
13
|
-
i18n: {
|
|
14
|
-
de: async () => (await import('./i18n/de')).content,
|
|
15
|
-
en: async () => (await import('./i18n/en')).content,
|
|
16
|
-
es: async () => (await import('./i18n/es')).content,
|
|
17
|
-
fr: async () => (await import('./i18n/fr')).content,
|
|
18
|
-
id: async () => (await import('./i18n/id')).content,
|
|
19
|
-
it: async () => (await import('./i18n/it')).content,
|
|
20
|
-
ja: async () => (await import('./i18n/ja')).content,
|
|
21
|
-
ko: async () => (await import('./i18n/ko')).content,
|
|
22
|
-
nl: async () => (await import('./i18n/nl')).content,
|
|
23
|
-
pl: async () => (await import('./i18n/pl')).content,
|
|
24
|
-
pt: async () => (await import('./i18n/pt')).content,
|
|
25
|
-
ru: async () => (await import('./i18n/ru')).content,
|
|
26
|
-
sv: async () => (await import('./i18n/sv')).content,
|
|
27
|
-
tr: async () => (await import('./i18n/tr')).content,
|
|
28
|
-
zh: async () => (await import('./i18n/zh')).content,
|
|
29
|
-
},
|
|
30
|
-
};
|
|
1
|
+
import type { DeveloperToolEntry, ToolLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
import type { SerpPixelSimulatorUI } from './ui';
|
|
4
|
+
|
|
5
|
+
export type SerpPixelSimulatorLocaleContent = ToolLocaleContent<SerpPixelSimulatorUI>;
|
|
6
|
+
|
|
7
|
+
export const serpPixelSimulator: DeveloperToolEntry<SerpPixelSimulatorUI> = {
|
|
8
|
+
id: 'serp-pixel-simulator',
|
|
9
|
+
icons: {
|
|
10
|
+
bg: 'mdi:google',
|
|
11
|
+
fg: 'mdi:ruler-square',
|
|
12
|
+
},
|
|
13
|
+
i18n: {
|
|
14
|
+
de: async () => (await import('./i18n/de')).content,
|
|
15
|
+
en: async () => (await import('./i18n/en')).content,
|
|
16
|
+
es: async () => (await import('./i18n/es')).content,
|
|
17
|
+
fr: async () => (await import('./i18n/fr')).content,
|
|
18
|
+
id: async () => (await import('./i18n/id')).content,
|
|
19
|
+
it: async () => (await import('./i18n/it')).content,
|
|
20
|
+
ja: async () => (await import('./i18n/ja')).content,
|
|
21
|
+
ko: async () => (await import('./i18n/ko')).content,
|
|
22
|
+
nl: async () => (await import('./i18n/nl')).content,
|
|
23
|
+
pl: async () => (await import('./i18n/pl')).content,
|
|
24
|
+
pt: async () => (await import('./i18n/pt')).content,
|
|
25
|
+
ru: async () => (await import('./i18n/ru')).content,
|
|
26
|
+
sv: async () => (await import('./i18n/sv')).content,
|
|
27
|
+
tr: async () => (await import('./i18n/tr')).content,
|
|
28
|
+
zh: async () => (await import('./i18n/zh')).content,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -1,213 +1,213 @@
|
|
|
1
|
-
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
-
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
-
import type { SerpPixelSimulatorUI } from '../ui';
|
|
4
|
-
import { bibliography } from '../bibliography';
|
|
5
|
-
|
|
6
|
-
const slug = 'serp-pixel-simulator';
|
|
7
|
-
const title = 'SERP Simulator and SEO Pixel Counter';
|
|
8
|
-
const description = 'Preview Google-style search snippets in real time, measure title and meta description width by pixels, and see exactly where your copy will be trimmed.';
|
|
9
|
-
|
|
10
|
-
const howTo = [
|
|
11
|
-
{
|
|
12
|
-
name: 'Enter the title tag',
|
|
13
|
-
text: 'Type or paste the page title you want to test. The SERP preview and pixel meter update on every keystroke.',
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
name: 'Add the visible URL',
|
|
17
|
-
text: 'Use a realistic domain and path so the snippet resembles the result a searcher would scan.',
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: 'Write the meta description',
|
|
21
|
-
text: 'Add the description copy and watch the pixel bar. When it exceeds the recommended visual width, the preview trims it with an ellipsis.',
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: 'Switch desktop and mobile',
|
|
25
|
-
text: 'Compare title rendering with a desktop or mobile card width before publishing metadata.',
|
|
26
|
-
},
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
const faq = [
|
|
30
|
-
{
|
|
31
|
-
question: 'Why count pixels instead of characters for SEO titles?',
|
|
32
|
-
answer: 'Google search cards are constrained by visual width. A title with many narrow letters can fit more characters than a title with wide letters, uppercase words, or bold-looking glyphs. Pixel measurement gives a closer preview of the visible result.',
|
|
33
|
-
},
|
|
34
|
-
{
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { SerpPixelSimulatorUI } from '../ui';
|
|
4
|
+
import { bibliography } from '../bibliography';
|
|
5
|
+
|
|
6
|
+
const slug = 'serp-pixel-simulator';
|
|
7
|
+
const title = 'SERP Simulator and SEO Pixel Counter';
|
|
8
|
+
const description = 'Preview Google-style search snippets in real time, measure title and meta description width by pixels, and see exactly where your copy will be trimmed.';
|
|
9
|
+
|
|
10
|
+
const howTo = [
|
|
11
|
+
{
|
|
12
|
+
name: 'Enter the title tag',
|
|
13
|
+
text: 'Type or paste the page title you want to test. The SERP preview and pixel meter update on every keystroke.',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'Add the visible URL',
|
|
17
|
+
text: 'Use a realistic domain and path so the snippet resembles the result a searcher would scan.',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'Write the meta description',
|
|
21
|
+
text: 'Add the description copy and watch the pixel bar. When it exceeds the recommended visual width, the preview trims it with an ellipsis.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'Switch desktop and mobile',
|
|
25
|
+
text: 'Compare title rendering with a desktop or mobile card width before publishing metadata.',
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const faq = [
|
|
30
|
+
{
|
|
31
|
+
question: 'Why count pixels instead of characters for SEO titles?',
|
|
32
|
+
answer: 'Google search cards are constrained by visual width. A title with many narrow letters can fit more characters than a title with wide letters, uppercase words, or bold-looking glyphs. Pixel measurement gives a closer preview of the visible result.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
35
|
question: 'Does this predict exactly how Google will truncate my snippet?',
|
|
36
|
-
answer: 'No. Google can rewrite title links and snippets, and rendering can vary by query, device, language and experiment. The tool is designed as a practical visual guardrail for writing metadata that is less likely to be cut off.',
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
question: 'What pixel limits does the simulator use?',
|
|
40
|
-
answer: 'The default desktop title limit is 580 px, the mobile title limit is 600 px, and the meta description guardrail is 920 px. These are writing targets, not official Google limits.',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
question: 'Why does the preview add an ellipsis?',
|
|
44
|
-
answer: 'When the measured text exceeds the available pixel width, the simulator trims the string at the last fitting character and appends three dots, matching the practical behavior SEO teams need to spot lost meaning.',
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
const ui: SerpPixelSimulatorUI = {
|
|
49
|
-
titleLabel: 'Title tag',
|
|
50
|
-
titlePlaceholder: 'GameBob | Indie Development Studio',
|
|
51
|
-
urlLabel: 'Displayed URL',
|
|
52
|
-
urlPlaceholder: 'https://www.gamebob.dev/en/',
|
|
53
|
-
descriptionLabel: 'Meta description',
|
|
54
|
-
descriptionPlaceholder: 'Discover our collection of tools and games designed to elevate your digital workflow and entertainment.',
|
|
55
|
-
deviceLabel: 'Preview mode',
|
|
56
|
-
desktopLabel: 'Desktop',
|
|
57
|
-
mobileLabel: 'Mobile',
|
|
58
|
-
titlePixelsLabel: 'Title width',
|
|
59
|
-
descriptionPixelsLabel: 'Description width',
|
|
60
|
-
charactersLabel: 'characters',
|
|
61
|
-
previewLabel: 'Live Google-style preview',
|
|
62
|
-
tooLongLabel: 'Too wide',
|
|
63
|
-
goodLabel: 'Fits',
|
|
64
|
-
emptyTitle: 'Your title will appear here',
|
|
65
|
-
emptyDescription: 'Your meta description preview will appear here as you type.',
|
|
66
|
-
defaultTitle: 'GameBob | Indie Development Studio',
|
|
67
|
-
defaultUrl: 'https://www.gamebob.dev/en/',
|
|
68
|
-
defaultDescription: 'Discover our collection of tools and games designed to elevate your digital workflow and entertainment.',
|
|
69
|
-
fallbackUrl: 'example.com',
|
|
70
|
-
fallbackFaviconText: 'G',
|
|
71
|
-
pixelUnit: 'px',
|
|
72
|
-
ellipsis: '...',
|
|
73
|
-
fetchButtonLabel: 'Fetch',
|
|
74
|
-
fetchLoadingLabel: 'Fetching...',
|
|
75
|
-
fetchSuccessLabel: 'Metadata loaded from the URL.',
|
|
76
|
-
fetchCorsError: 'The browser could not read this page. It may be blocked by CORS, a redirect, mixed content or a network rule. You can still paste or edit the metadata manually.',
|
|
77
|
-
fetchInvalidUrlError: 'Enter a valid URL before fetching metadata.',
|
|
78
|
-
fetchNoMetadataError: 'The page was fetched, but no title or meta description was found.',
|
|
79
|
-
fetchGenericError: 'Metadata could not be fetched from this URL. Check the address or paste the fields manually.',
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const faqSchema: WithContext<FAQPage> = {
|
|
83
|
-
'@context': 'https://schema.org',
|
|
84
|
-
'@type': 'FAQPage',
|
|
85
|
-
mainEntity: faq.map((item) => ({
|
|
86
|
-
'@type': 'Question',
|
|
87
|
-
name: item.question,
|
|
88
|
-
acceptedAnswer: {
|
|
89
|
-
'@type': 'Answer',
|
|
90
|
-
text: item.answer,
|
|
91
|
-
},
|
|
92
|
-
})),
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const howToSchema: WithContext<HowTo> = {
|
|
96
|
-
'@context': 'https://schema.org',
|
|
97
|
-
'@type': 'HowTo',
|
|
98
|
-
name: title,
|
|
99
|
-
description,
|
|
100
|
-
step: howTo.map((step, index) => ({
|
|
101
|
-
'@type': 'HowToStep',
|
|
102
|
-
position: index + 1,
|
|
103
|
-
name: step.name,
|
|
104
|
-
text: step.text,
|
|
105
|
-
})),
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const appSchema: WithContext<SoftwareApplication> = {
|
|
109
|
-
'@context': 'https://schema.org',
|
|
110
|
-
'@type': 'SoftwareApplication',
|
|
111
|
-
name: title,
|
|
112
|
-
description,
|
|
113
|
-
applicationCategory: 'BusinessApplication',
|
|
114
|
-
operatingSystem: 'Any',
|
|
115
|
-
offers: {
|
|
116
|
-
'@type': 'Offer',
|
|
117
|
-
price: '0',
|
|
118
|
-
priceCurrency: 'EUR',
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
export const content: ToolLocaleContent<SerpPixelSimulatorUI> = {
|
|
123
|
-
slug,
|
|
124
|
-
title,
|
|
125
|
-
description,
|
|
126
|
-
ui,
|
|
127
|
-
faqTitle: 'SERP simulator FAQ',
|
|
128
|
-
faq,
|
|
129
|
-
bibliographyTitle: 'Search result documentation',
|
|
130
|
-
bibliography,
|
|
131
|
-
howTo,
|
|
132
|
-
schemas: [appSchema, faqSchema, howToSchema],
|
|
133
|
-
seo: [
|
|
134
|
-
{
|
|
135
|
-
type: 'title',
|
|
136
|
-
text: 'Stop guessing how your Google result will look',
|
|
137
|
-
level: 2,
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
type: 'paragraph',
|
|
141
|
-
html: 'A title tag can look perfect in a spreadsheet and still break in the search result. Google does not reserve space by character count; it renders text inside a visual card. That means <strong>GameBob | Indie Development Studio</strong> and another title with the same number of characters can occupy very different widths depending on the letters, casing, punctuation and spacing.',
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
type: 'tip',
|
|
145
|
-
title: 'The rule that actually helps',
|
|
146
|
-
html: 'Write the snippet so the important promise survives the ellipsis. Put the page type, the search intent and the strongest reason to click before the pixel limit. Brand names are useful, but they should not push the main benefit out of view.',
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
type: 'title',
|
|
150
|
-
text: 'What the pixel counter is measuring',
|
|
151
|
-
level: 3,
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
type: 'table',
|
|
155
|
-
headers: ['Element', 'What matters', 'How to use the result'],
|
|
156
|
-
rows: [
|
|
157
|
-
['Title tag', 'Rendered width in pixels, not raw character count', 'Keep the primary keyword and click promise visible before truncation.'],
|
|
158
|
-
['Displayed URL', 'Visual trust and topic clarity', 'Use a readable path that reinforces where the result leads.'],
|
|
159
|
-
['Meta description', 'A wider snippet area with query-dependent behavior', 'Front-load the benefit because Google may shorten or rewrite it.'],
|
|
160
|
-
['Device mode', 'Desktop and mobile cards can feel different', 'Check both before shipping metadata for important pages.'],
|
|
161
|
-
],
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
type: 'title',
|
|
165
|
-
text: 'Why character limits are a weak SEO habit',
|
|
166
|
-
level: 3,
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
type: 'paragraph',
|
|
170
|
-
html: 'Traditional advice such as "keep titles under 60 characters" is convenient, but it hides the real problem. Wide letters like W and M, uppercase words, separators, numbers and long brand names all consume different space. Pixel measurement makes the trade-off visible immediately: you can see whether a phrase earns its place or steals room from a stronger message.',
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
type: 'title',
|
|
174
|
-
text: 'A practical workflow for better snippets',
|
|
175
|
-
level: 3,
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
type: 'list',
|
|
179
|
-
items: [
|
|
180
|
-
'<strong>Start with intent:</strong> describe what the user gets, not just what the page is called.',
|
|
181
|
-
'<strong>Test the full title:</strong> paste it into the simulator and watch the bar before publishing.',
|
|
182
|
-
'<strong>Move weak words out:</strong> if the bar turns red, remove filler before cutting valuable terms.',
|
|
183
|
-
'<strong>Check the ellipsis:</strong> if the truncated preview loses meaning, rewrite the title instead of accepting the cut.',
|
|
184
|
-
'<strong>Repeat for the description:</strong> make sure the first sentence carries the value proposition on its own.',
|
|
185
|
-
],
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
type: 'diagnostic',
|
|
189
|
-
variant: 'info',
|
|
190
|
-
title: 'When the bar turns red',
|
|
191
|
-
html: 'A red bar is not a penalty warning. It means the current text is wider than the selected visual target, so the simulator trims it with dots. Treat that as an editorial signal: decide whether the hidden words are disposable, or whether the snippet needs a sharper structure.',
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
type: 'title',
|
|
195
|
-
text: 'Limits, rewrites and real-world expectations',
|
|
196
|
-
level: 3,
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
type: 'paragraph',
|
|
36
|
+
answer: 'No. Google can rewrite title links and snippets, and rendering can vary by query, device, language and experiment. The tool is designed as a practical visual guardrail for writing metadata that is less likely to be cut off.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
question: 'What pixel limits does the simulator use?',
|
|
40
|
+
answer: 'The default desktop title limit is 580 px, the mobile title limit is 600 px, and the meta description guardrail is 920 px. These are writing targets, not official Google limits.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
question: 'Why does the preview add an ellipsis?',
|
|
44
|
+
answer: 'When the measured text exceeds the available pixel width, the simulator trims the string at the last fitting character and appends three dots, matching the practical behavior SEO teams need to spot lost meaning.',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const ui: SerpPixelSimulatorUI = {
|
|
49
|
+
titleLabel: 'Title tag',
|
|
50
|
+
titlePlaceholder: 'GameBob | Indie Development Studio',
|
|
51
|
+
urlLabel: 'Displayed URL',
|
|
52
|
+
urlPlaceholder: 'https://www.gamebob.dev/en/',
|
|
53
|
+
descriptionLabel: 'Meta description',
|
|
54
|
+
descriptionPlaceholder: 'Discover our collection of tools and games designed to elevate your digital workflow and entertainment.',
|
|
55
|
+
deviceLabel: 'Preview mode',
|
|
56
|
+
desktopLabel: 'Desktop',
|
|
57
|
+
mobileLabel: 'Mobile',
|
|
58
|
+
titlePixelsLabel: 'Title width',
|
|
59
|
+
descriptionPixelsLabel: 'Description width',
|
|
60
|
+
charactersLabel: 'characters',
|
|
61
|
+
previewLabel: 'Live Google-style preview',
|
|
62
|
+
tooLongLabel: 'Too wide',
|
|
63
|
+
goodLabel: 'Fits',
|
|
64
|
+
emptyTitle: 'Your title will appear here',
|
|
65
|
+
emptyDescription: 'Your meta description preview will appear here as you type.',
|
|
66
|
+
defaultTitle: 'GameBob | Indie Development Studio',
|
|
67
|
+
defaultUrl: 'https://www.gamebob.dev/en/',
|
|
68
|
+
defaultDescription: 'Discover our collection of tools and games designed to elevate your digital workflow and entertainment.',
|
|
69
|
+
fallbackUrl: 'example.com',
|
|
70
|
+
fallbackFaviconText: 'G',
|
|
71
|
+
pixelUnit: 'px',
|
|
72
|
+
ellipsis: '...',
|
|
73
|
+
fetchButtonLabel: 'Fetch',
|
|
74
|
+
fetchLoadingLabel: 'Fetching...',
|
|
75
|
+
fetchSuccessLabel: 'Metadata loaded from the URL.',
|
|
76
|
+
fetchCorsError: 'The browser could not read this page. It may be blocked by CORS, a redirect, mixed content or a network rule. You can still paste or edit the metadata manually.',
|
|
77
|
+
fetchInvalidUrlError: 'Enter a valid URL before fetching metadata.',
|
|
78
|
+
fetchNoMetadataError: 'The page was fetched, but no title or meta description was found.',
|
|
79
|
+
fetchGenericError: 'Metadata could not be fetched from this URL. Check the address or paste the fields manually.',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
83
|
+
'@context': 'https://schema.org',
|
|
84
|
+
'@type': 'FAQPage',
|
|
85
|
+
mainEntity: faq.map((item) => ({
|
|
86
|
+
'@type': 'Question',
|
|
87
|
+
name: item.question,
|
|
88
|
+
acceptedAnswer: {
|
|
89
|
+
'@type': 'Answer',
|
|
90
|
+
text: item.answer,
|
|
91
|
+
},
|
|
92
|
+
})),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const howToSchema: WithContext<HowTo> = {
|
|
96
|
+
'@context': 'https://schema.org',
|
|
97
|
+
'@type': 'HowTo',
|
|
98
|
+
name: title,
|
|
99
|
+
description,
|
|
100
|
+
step: howTo.map((step, index) => ({
|
|
101
|
+
'@type': 'HowToStep',
|
|
102
|
+
position: index + 1,
|
|
103
|
+
name: step.name,
|
|
104
|
+
text: step.text,
|
|
105
|
+
})),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
109
|
+
'@context': 'https://schema.org',
|
|
110
|
+
'@type': 'SoftwareApplication',
|
|
111
|
+
name: title,
|
|
112
|
+
description,
|
|
113
|
+
applicationCategory: 'BusinessApplication',
|
|
114
|
+
operatingSystem: 'Any',
|
|
115
|
+
offers: {
|
|
116
|
+
'@type': 'Offer',
|
|
117
|
+
price: '0',
|
|
118
|
+
priceCurrency: 'EUR',
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const content: ToolLocaleContent<SerpPixelSimulatorUI> = {
|
|
123
|
+
slug,
|
|
124
|
+
title,
|
|
125
|
+
description,
|
|
126
|
+
ui,
|
|
127
|
+
faqTitle: 'SERP simulator FAQ',
|
|
128
|
+
faq,
|
|
129
|
+
bibliographyTitle: 'Search result documentation',
|
|
130
|
+
bibliography,
|
|
131
|
+
howTo,
|
|
132
|
+
schemas: [appSchema, faqSchema, howToSchema],
|
|
133
|
+
seo: [
|
|
134
|
+
{
|
|
135
|
+
type: 'title',
|
|
136
|
+
text: 'Stop guessing how your Google result will look',
|
|
137
|
+
level: 2,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
type: 'paragraph',
|
|
141
|
+
html: 'A title tag can look perfect in a spreadsheet and still break in the search result. Google does not reserve space by character count; it renders text inside a visual card. That means <strong>GameBob | Indie Development Studio</strong> and another title with the same number of characters can occupy very different widths depending on the letters, casing, punctuation and spacing.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: 'tip',
|
|
145
|
+
title: 'The rule that actually helps',
|
|
146
|
+
html: 'Write the snippet so the important promise survives the ellipsis. Put the page type, the search intent and the strongest reason to click before the pixel limit. Brand names are useful, but they should not push the main benefit out of view.',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: 'title',
|
|
150
|
+
text: 'What the pixel counter is measuring',
|
|
151
|
+
level: 3,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: 'table',
|
|
155
|
+
headers: ['Element', 'What matters', 'How to use the result'],
|
|
156
|
+
rows: [
|
|
157
|
+
['Title tag', 'Rendered width in pixels, not raw character count', 'Keep the primary keyword and click promise visible before truncation.'],
|
|
158
|
+
['Displayed URL', 'Visual trust and topic clarity', 'Use a readable path that reinforces where the result leads.'],
|
|
159
|
+
['Meta description', 'A wider snippet area with query-dependent behavior', 'Front-load the benefit because Google may shorten or rewrite it.'],
|
|
160
|
+
['Device mode', 'Desktop and mobile cards can feel different', 'Check both before shipping metadata for important pages.'],
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
type: 'title',
|
|
165
|
+
text: 'Why character limits are a weak SEO habit',
|
|
166
|
+
level: 3,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: 'paragraph',
|
|
170
|
+
html: 'Traditional advice such as "keep titles under 60 characters" is convenient, but it hides the real problem. Wide letters like W and M, uppercase words, separators, numbers and long brand names all consume different space. Pixel measurement makes the trade-off visible immediately: you can see whether a phrase earns its place or steals room from a stronger message.',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
type: 'title',
|
|
174
|
+
text: 'A practical workflow for better snippets',
|
|
175
|
+
level: 3,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
type: 'list',
|
|
179
|
+
items: [
|
|
180
|
+
'<strong>Start with intent:</strong> describe what the user gets, not just what the page is called.',
|
|
181
|
+
'<strong>Test the full title:</strong> paste it into the simulator and watch the bar before publishing.',
|
|
182
|
+
'<strong>Move weak words out:</strong> if the bar turns red, remove filler before cutting valuable terms.',
|
|
183
|
+
'<strong>Check the ellipsis:</strong> if the truncated preview loses meaning, rewrite the title instead of accepting the cut.',
|
|
184
|
+
'<strong>Repeat for the description:</strong> make sure the first sentence carries the value proposition on its own.',
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
type: 'diagnostic',
|
|
189
|
+
variant: 'info',
|
|
190
|
+
title: 'When the bar turns red',
|
|
191
|
+
html: 'A red bar is not a penalty warning. It means the current text is wider than the selected visual target, so the simulator trims it with dots. Treat that as an editorial signal: decide whether the hidden words are disposable, or whether the snippet needs a sharper structure.',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
type: 'title',
|
|
195
|
+
text: 'Limits, rewrites and real-world expectations',
|
|
196
|
+
level: 3,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: 'paragraph',
|
|
200
200
|
html: 'No simulator can know the exact snippet Google will show. Google may rewrite title links, bold query terms, choose page text instead of the meta description, or display different snippets for different searches. This tool is best used as a fast writing and QA step: it catches obvious visual overflow before the page reaches production.',
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
type: 'summary',
|
|
204
|
-
title: 'Best use of this SERP simulator',
|
|
205
|
-
items: [
|
|
206
|
-
'Use the pixel bar to catch visual overflow before publishing metadata.',
|
|
207
|
-
'Keep the main search intent and click promise visible before any ellipsis.',
|
|
208
|
-
'Fetch metadata from URLs that allow CORS, then edit the result manually when needed.',
|
|
209
|
-
'Treat the preview as a writing guardrail, because Google can still rewrite snippets per query.',
|
|
210
|
-
],
|
|
211
|
-
},
|
|
212
|
-
],
|
|
213
|
-
};
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
type: 'summary',
|
|
204
|
+
title: 'Best use of this SERP simulator',
|
|
205
|
+
items: [
|
|
206
|
+
'Use the pixel bar to catch visual overflow before publishing metadata.',
|
|
207
|
+
'Keep the main search intent and click promise visible before any ellipsis.',
|
|
208
|
+
'Fetch metadata from URLs that allow CORS, then edit the result manually when needed.',
|
|
209
|
+
'Treat the preview as a writing guardrail, because Google can still rewrite snippets per query.',
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
};
|