feat: i18n support with first translations
This commit is contained in:
@@ -578,10 +578,10 @@ describe('PreviewServer', () => {
|
||||
expect(firstPageHtml).toContain('<h1 class="archive-heading">Meine Blog Beschreibung</h1>');
|
||||
|
||||
const secondPageHtml = await (await fetch(`${server.getBaseUrl()}/page/2/`)).text();
|
||||
expect(secondPageHtml).toContain('<h1 class="archive-heading">Archiv 1.1.2020 - 2.1.2020</h1>');
|
||||
expect(secondPageHtml).toContain('<h1 class="archive-heading">Archive 1.1.2020 - 2.1.2020</h1>');
|
||||
});
|
||||
|
||||
it('renders month archive heading with German month name on first page', async () => {
|
||||
it('renders month archive heading in the active render language on first page', async () => {
|
||||
const posts = [
|
||||
makePost({ id: 'm-1', slug: 'm-1', title: 'M1', content: 'Body 1', createdAt: new Date('2020-02-05T10:00:00.000Z') }),
|
||||
makePost({ id: 'm-2', slug: 'm-2', title: 'M2', content: 'Body 2', createdAt: new Date('2020-02-04T10:00:00.000Z') }),
|
||||
@@ -604,7 +604,7 @@ describe('PreviewServer', () => {
|
||||
await server.start(0);
|
||||
|
||||
const monthPageHtml = await (await fetch(`${server.getBaseUrl()}/2020/2/`)).text();
|
||||
expect(monthPageHtml).toContain('<h1 class="archive-heading">Archiv Februar 2020</h1>');
|
||||
expect(monthPageHtml).toContain('<h1 class="archive-heading">Archive February 2020</h1>');
|
||||
});
|
||||
|
||||
it('renders tag heading on first page and adds date range on later pages', async () => {
|
||||
|
||||
27
tests/engine/i18nLocaleCompleteness.test.ts
Normal file
27
tests/engine/i18nLocaleCompleteness.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import en from '../../src/main/shared/i18n/locales/en.json';
|
||||
import de from '../../src/main/shared/i18n/locales/de.json';
|
||||
import fr from '../../src/main/shared/i18n/locales/fr.json';
|
||||
import itLocale from '../../src/main/shared/i18n/locales/it.json';
|
||||
import es from '../../src/main/shared/i18n/locales/es.json';
|
||||
|
||||
type LocaleMap = Record<string, string>;
|
||||
|
||||
const locales: Record<string, LocaleMap> = {
|
||||
de: de as LocaleMap,
|
||||
fr: fr as LocaleMap,
|
||||
it: itLocale as LocaleMap,
|
||||
es: es as LocaleMap,
|
||||
};
|
||||
|
||||
describe('main/shared locale completeness', () => {
|
||||
const englishKeys = Object.keys(en as LocaleMap).sort();
|
||||
|
||||
it('all main/shared locales contain exactly the english key set', () => {
|
||||
for (const [locale, messages] of Object.entries(locales)) {
|
||||
const localeKeys = Object.keys(messages).sort();
|
||||
|
||||
expect(localeKeys, `Locale ${locale} is missing or has extra keys`).toEqual(englishKeys);
|
||||
}
|
||||
});
|
||||
});
|
||||
26
tests/engine/i18nRenderLanguage.test.ts
Normal file
26
tests/engine/i18nRenderLanguage.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
resolveSupportedRenderLanguage,
|
||||
resolveRenderLanguageFromProjectPreferences,
|
||||
translateRender,
|
||||
} from '../../src/main/shared/i18n';
|
||||
|
||||
describe('render i18n', () => {
|
||||
it('resolves rendering language from project preferences', () => {
|
||||
expect(resolveRenderLanguageFromProjectPreferences('de')).toBe('de');
|
||||
expect(resolveRenderLanguageFromProjectPreferences('fr-CA')).toBe('fr');
|
||||
expect(resolveRenderLanguageFromProjectPreferences(undefined)).toBe('en');
|
||||
});
|
||||
|
||||
it('normalizes render language values', () => {
|
||||
expect(resolveSupportedRenderLanguage('it')).toBe('it');
|
||||
expect(resolveSupportedRenderLanguage('es-AR')).toBe('es');
|
||||
expect(resolveSupportedRenderLanguage('')).toBe('en');
|
||||
});
|
||||
|
||||
it('translates render keys with fallback', () => {
|
||||
expect(translateRender('de', 'render.pagination.newer')).toBe('neuer');
|
||||
expect(translateRender('es', 'render.pagination.older')).toBe('más antiguo');
|
||||
expect(translateRender('fr', 'missing.key')).toBe('missing.key');
|
||||
});
|
||||
});
|
||||
27
tests/renderer/i18n.test.ts
Normal file
27
tests/renderer/i18n.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
translateUi,
|
||||
resolveSupportedUiLanguage,
|
||||
resolveUiLanguageFromSystemLocale,
|
||||
} from '../../src/renderer/i18n';
|
||||
|
||||
describe('renderer i18n', () => {
|
||||
it('resolves supported ui language from OS locale', () => {
|
||||
expect(resolveUiLanguageFromSystemLocale('de-DE')).toBe('de');
|
||||
expect(resolveUiLanguageFromSystemLocale('fr-CH')).toBe('fr');
|
||||
expect(resolveUiLanguageFromSystemLocale('pt-BR')).toBe('en');
|
||||
});
|
||||
|
||||
it('normalizes explicit ui language values', () => {
|
||||
expect(resolveSupportedUiLanguage('it')).toBe('it');
|
||||
expect(resolveSupportedUiLanguage('es-MX')).toBe('es');
|
||||
expect(resolveSupportedUiLanguage('')).toBe('en');
|
||||
});
|
||||
|
||||
it('returns translated text with english fallback', () => {
|
||||
expect(translateUi('de', 'common.save')).toBe('Speichern');
|
||||
expect(translateUi('fr', 'common.cancel')).toBe('Annuler');
|
||||
expect(translateUi('de', 'settings.language.english')).toBe('Englisch');
|
||||
expect(translateUi('it', 'missing.key')).toBe('missing.key');
|
||||
});
|
||||
});
|
||||
27
tests/renderer/i18nLocaleCompleteness.test.ts
Normal file
27
tests/renderer/i18nLocaleCompleteness.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import en from '../../src/renderer/i18n/locales/en.json';
|
||||
import de from '../../src/renderer/i18n/locales/de.json';
|
||||
import fr from '../../src/renderer/i18n/locales/fr.json';
|
||||
import itLocale from '../../src/renderer/i18n/locales/it.json';
|
||||
import es from '../../src/renderer/i18n/locales/es.json';
|
||||
|
||||
type LocaleMap = Record<string, string>;
|
||||
|
||||
const locales: Record<string, LocaleMap> = {
|
||||
de: de as LocaleMap,
|
||||
fr: fr as LocaleMap,
|
||||
it: itLocale as LocaleMap,
|
||||
es: es as LocaleMap,
|
||||
};
|
||||
|
||||
describe('renderer locale completeness', () => {
|
||||
const englishKeys = Object.keys(en as LocaleMap).sort();
|
||||
|
||||
it('all renderer locales contain exactly the english key set', () => {
|
||||
for (const [locale, messages] of Object.entries(locales)) {
|
||||
const localeKeys = Object.keys(messages).sort();
|
||||
|
||||
expect(localeKeys, `Locale ${locale} is missing or has extra keys`).toEqual(englishKeys);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -40,12 +40,11 @@ describe('Help menu documentation entry', () => {
|
||||
expect(viewGroup?.items.some((item) => item.action === 'toggleFullScreen')).toBe(true);
|
||||
});
|
||||
|
||||
it('renames generateSitemap menu item to Render Site and assigns Command/Ctrl+R', () => {
|
||||
it('assigns Command/Ctrl+R shortcut for generateSitemap menu item', () => {
|
||||
const blogGroup = APP_MENU_GROUPS.find((group) => group.label === 'Blog');
|
||||
const generateSiteItem = blogGroup?.items.find((item) => item.action === 'generateSitemap');
|
||||
|
||||
expect(generateSiteItem).toBeDefined();
|
||||
expect(generateSiteItem?.label).toBe('Render Site');
|
||||
expect(generateSiteItem?.accelerator).toBe('CmdOrCtrl+R');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user