feat: i18n support with first translations
This commit is contained in:
@@ -5,6 +5,7 @@ import type { MediaData } from './MediaEngine';
|
||||
import type { PostData } from './PostEngine';
|
||||
import { PICO_THEME_NAMES } from '../shared/picoThemes';
|
||||
import { TAG_CLOUD_RUNTIME_JS } from './assets/tagCloudRuntime';
|
||||
import { resolveRenderLanguageFromProjectPreferences, translateRender } from '../shared/i18n';
|
||||
|
||||
export interface HtmlRewriteContext {
|
||||
canonicalPostPathBySlug: Map<string, string>;
|
||||
@@ -87,6 +88,8 @@ export interface SinglePostTemplateContext {
|
||||
export interface NotFoundTemplateContext {
|
||||
page_title: string;
|
||||
language: string;
|
||||
not_found_message?: string;
|
||||
not_found_back_label?: string;
|
||||
pico_stylesheet_href?: string;
|
||||
html_theme_attribute?: string;
|
||||
}
|
||||
@@ -336,7 +339,9 @@ export function renderGalleryMacro(
|
||||
postId: string,
|
||||
mediaItems: MediaData[],
|
||||
linkedMediaIds: Set<string> | null,
|
||||
renderLanguage: string,
|
||||
): string {
|
||||
const language = resolveRenderLanguageFromProjectPreferences(renderLanguage);
|
||||
const requestedColumns = parseIntegerParam(params.columns);
|
||||
const columns = requestedColumns && requestedColumns >= 1 && requestedColumns <= 6 ? requestedColumns : 3;
|
||||
const caption = params.caption ? `<figcaption class="gallery-caption">${escapeHtml(params.caption)}</figcaption>` : '';
|
||||
@@ -361,12 +366,16 @@ export function renderGalleryMacro(
|
||||
return `<a class="gallery-item" href="${mediaPath}" data-lightbox="${groupName}" data-title="${title}"><img src="${mediaPath}" alt="${alt}" loading="lazy" /></a>`;
|
||||
}).join('');
|
||||
|
||||
const content = galleryItems || '<div class="gallery-empty">No linked images found.</div>';
|
||||
const content = galleryItems || `<div class="gallery-empty">${escapeHtml(translateRender(language, 'render.gallery.empty'))}</div>`;
|
||||
return `<div class="macro-gallery gallery-cols-${columns}" data-post-id="${escapeHtml(postId)}" data-columns="${columns}" data-lightbox="true"><div class="gallery-container gallery-lightbox">${content}</div>${caption}</div>`;
|
||||
}
|
||||
|
||||
export function renderPhotoArchiveMacro(params: Record<string, string>, mediaItems: MediaData[]): string {
|
||||
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
export function renderPhotoArchiveMacro(
|
||||
params: Record<string, string>,
|
||||
mediaItems: MediaData[],
|
||||
renderLanguage: string,
|
||||
): string {
|
||||
const language = resolveRenderLanguageFromProjectPreferences(renderLanguage);
|
||||
const yearParam = parseIntegerParam(params.year);
|
||||
const monthParam = parseIntegerParam(params.month);
|
||||
|
||||
@@ -393,11 +402,12 @@ export function renderPhotoArchiveMacro(params: Record<string, string>, mediaIte
|
||||
const buckets = buildPhotoArchiveBuckets(renderableMedia, params);
|
||||
|
||||
if (buckets.length === 0) {
|
||||
return `<div class="${rootClasses.join(' ')}" ${dataAttrs.join(' ')}><div class="photo-archive-container"><div class="photo-archive-empty">No photos found for this archive.</div></div></div>`;
|
||||
const emptyLabel = escapeHtml(translateRender(language, 'render.photoArchive.empty'));
|
||||
return `<div class="${rootClasses.join(' ')}" ${dataAttrs.join(' ')}><div class="photo-archive-container"><div class="photo-archive-empty">${emptyLabel}</div></div></div>`;
|
||||
}
|
||||
|
||||
const monthsHtml = buckets.map((bucket) => {
|
||||
const monthName = monthNames[bucket.month - 1] || String(bucket.month);
|
||||
const monthName = translateRender(language, `render.month.${bucket.month}`);
|
||||
const label = `${monthName} ${bucket.year}`;
|
||||
const groupName = `photo-archive-${bucket.year}-${String(bucket.month).padStart(2, '0')}`;
|
||||
|
||||
@@ -414,7 +424,8 @@ export function renderPhotoArchiveMacro(params: Record<string, string>, mediaIte
|
||||
return `<div class="${rootClasses.join(' ')}" ${dataAttrs.join(' ')}><div class="photo-archive-container">${monthsHtml}</div></div>`;
|
||||
}
|
||||
|
||||
export function renderTagCloudMacro(params: Record<string, string>, tagUsage: TagUsageEntry[]): string {
|
||||
export function renderTagCloudMacro(params: Record<string, string>, tagUsage: TagUsageEntry[], renderLanguage: string): string {
|
||||
const language = resolveRenderLanguageFromProjectPreferences(renderLanguage);
|
||||
const widthParam = parseIntegerParam(params.width);
|
||||
const heightParam = parseIntegerParam(params.height);
|
||||
const orientation = normalizeTagCloudOrientation(params.orientation);
|
||||
@@ -422,7 +433,7 @@ export function renderTagCloudMacro(params: Record<string, string>, tagUsage: Ta
|
||||
const height = heightParam && heightParam >= 180 && heightParam <= 900 ? heightParam : 420;
|
||||
|
||||
if (tagUsage.length === 0) {
|
||||
return `<div class="macro-tag-cloud" data-tag-cloud="true" data-orientation="${orientation}" data-color-distribution="quantile" data-color-easing="0.7" data-color-theme="pico"><div class="tag-cloud-empty">No tags found.</div></div>`;
|
||||
return `<div class="macro-tag-cloud" data-tag-cloud="true" data-orientation="${orientation}" data-color-distribution="quantile" data-color-easing="0.7" data-color-theme="pico"><div class="tag-cloud-empty">${escapeHtml(translateRender(language, 'render.tagCloud.empty'))}</div></div>`;
|
||||
}
|
||||
|
||||
const minCount = Math.min(...tagUsage.map((entry) => entry.count));
|
||||
@@ -445,7 +456,8 @@ export function renderTagCloudMacro(params: Record<string, string>, tagUsage: Ta
|
||||
|
||||
const wordsJson = escapeHtml(JSON.stringify(words));
|
||||
|
||||
return `<div class="macro-tag-cloud" data-tag-cloud="true" data-orientation="${orientation}" data-color-distribution="quantile" data-color-easing="0.7" data-color-theme="pico" data-tag-cloud-words="${wordsJson}" data-width="${width}" data-height="${height}"><svg class="tag-cloud-canvas" viewBox="0 0 ${width} ${height}" preserveAspectRatio="xMidYMid meet" aria-label="Tag cloud"></svg></div>`;
|
||||
const ariaLabel = escapeHtml(translateRender(language, 'render.tagCloud.ariaLabel'));
|
||||
return `<div class="macro-tag-cloud" data-tag-cloud="true" data-orientation="${orientation}" data-color-distribution="quantile" data-color-easing="0.7" data-color-theme="pico" data-tag-cloud-words="${wordsJson}" data-width="${width}" data-height="${height}"><svg class="tag-cloud-canvas" viewBox="0 0 ${width} ${height}" preserveAspectRatio="xMidYMid meet" aria-label="${ariaLabel}"></svg></div>`;
|
||||
}
|
||||
|
||||
export function isExternalOrSpecialUrl(value: string): boolean {
|
||||
@@ -551,33 +563,36 @@ export function renderMacro(
|
||||
mediaItems: MediaData[],
|
||||
linkedMediaIds: Set<string> | null,
|
||||
tagUsage: TagUsageEntry[],
|
||||
renderLanguage: string,
|
||||
): string {
|
||||
const normalizedName = normalizeMacroName(name);
|
||||
|
||||
if (normalizedName === 'youtube') {
|
||||
const language = resolveRenderLanguageFromProjectPreferences(renderLanguage);
|
||||
const id = escapeHtml(params.id || '');
|
||||
const title = escapeHtml(params.title || 'YouTube video');
|
||||
const title = escapeHtml(params.title || translateRender(language, 'render.video.youtubeTitle'));
|
||||
if (!id) return '';
|
||||
return `<div class="macro-youtube"><iframe src="https://www.youtube.com/embed/${id}?rel=0" title="${title}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>`;
|
||||
}
|
||||
|
||||
if (normalizedName === 'vimeo') {
|
||||
const language = resolveRenderLanguageFromProjectPreferences(renderLanguage);
|
||||
const id = escapeHtml(params.id || '');
|
||||
const title = escapeHtml(params.title || 'Vimeo video');
|
||||
const title = escapeHtml(params.title || translateRender(language, 'render.video.vimeoTitle'));
|
||||
if (!id) return '';
|
||||
return `<div class="macro-vimeo"><iframe src="https://player.vimeo.com/video/${id}" title="${title}" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe></div>`;
|
||||
}
|
||||
|
||||
if (normalizedName === 'gallery') {
|
||||
return renderGalleryMacro(params, postId, mediaItems, linkedMediaIds);
|
||||
return renderGalleryMacro(params, postId, mediaItems, linkedMediaIds, renderLanguage);
|
||||
}
|
||||
|
||||
if (normalizedName === 'photo_archive') {
|
||||
return renderPhotoArchiveMacro(params, mediaItems);
|
||||
return renderPhotoArchiveMacro(params, mediaItems, renderLanguage);
|
||||
}
|
||||
|
||||
if (normalizedName === 'tag_cloud') {
|
||||
return renderTagCloudMacro(params, tagUsage);
|
||||
return renderTagCloudMacro(params, tagUsage, renderLanguage);
|
||||
}
|
||||
|
||||
return '';
|
||||
@@ -677,9 +692,23 @@ export class PageRenderer {
|
||||
cache: true,
|
||||
});
|
||||
|
||||
this.liquid.registerFilter('markdown', async (value: unknown, postIdArg: unknown, canonicalPostsArg: unknown, canonicalMediaArg: unknown) => {
|
||||
this.liquid.registerFilter('i18n', (keyArg: unknown, renderLanguageArg: unknown) => {
|
||||
const key = typeof keyArg === 'string' ? keyArg : '';
|
||||
const resolved = resolveRenderLanguageFromProjectPreferences(
|
||||
typeof renderLanguageArg === 'string' ? renderLanguageArg : 'en',
|
||||
);
|
||||
|
||||
if (!key) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return translateRender(resolved, key);
|
||||
});
|
||||
|
||||
this.liquid.registerFilter('markdown', async (value: unknown, postIdArg: unknown, canonicalPostsArg: unknown, canonicalMediaArg: unknown, renderLanguageArg: unknown) => {
|
||||
const content = typeof value === 'string' ? value : '';
|
||||
const postId = typeof postIdArg === 'string' ? postIdArg : '';
|
||||
const renderLanguage = typeof renderLanguageArg === 'string' ? renderLanguageArg : 'en';
|
||||
const rewriteContext: HtmlRewriteContext = {
|
||||
canonicalPostPathBySlug: recordToMap(canonicalPostsArg),
|
||||
canonicalMediaPathBySourcePath: recordToMap(canonicalMediaArg),
|
||||
@@ -703,7 +732,7 @@ export class PageRenderer {
|
||||
|
||||
const withMacros = content.replace(/\[\[(\w+)(?:\s+([^\]]+))?\]\]/g, (_match, macroName: string, rawParams: string | undefined) => {
|
||||
const params = parseMacroParams(rawParams);
|
||||
return renderMacro(macroName.toLowerCase(), params, postId, mediaItems, linkedMediaIds, tagUsage);
|
||||
return renderMacro(macroName.toLowerCase(), params, postId, mediaItems, linkedMediaIds, tagUsage, renderLanguage);
|
||||
});
|
||||
|
||||
const markdownHtml = await marked.parse(withMacros, { async: true, gfm: true, breaks: false });
|
||||
@@ -964,6 +993,10 @@ export class PageRenderer {
|
||||
}
|
||||
|
||||
async renderNotFound(context: NotFoundTemplateContext): Promise<string> {
|
||||
return this.liquid.renderFile('not-found', context);
|
||||
return this.liquid.renderFile('not-found', {
|
||||
...context,
|
||||
not_found_message: context.not_found_message,
|
||||
not_found_back_label: context.not_found_back_label,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
<section class="not-found" data-template="not-found">
|
||||
<article>
|
||||
<h1>404</h1>
|
||||
<p>The requested preview page could not be found.</p>
|
||||
<p><a href="/" role="button">Back to preview home</a></p>
|
||||
{% assign default_not_found_message = 'render.notFound.message' | i18n: language %}
|
||||
{% assign default_not_found_back = 'render.notFound.back' | i18n: language %}
|
||||
<p>{{ not_found_message | default: default_not_found_message }}</p>
|
||||
<p><a href="/" role="button">{{ not_found_back_label | default: default_not_found_back }}</a></p>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -4,26 +4,23 @@
|
||||
<body>
|
||||
<main>
|
||||
{% if archive_context %}
|
||||
{% assign month_names_de = "Januar|Februar|März|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember" | split: "|" %}
|
||||
{% if show_archive_range_heading and min_date and max_date %}
|
||||
{% if archive_context.kind == 'tag' or archive_context.kind == 'category' %}
|
||||
<h1 class="archive-heading">{{ archive_context.name }} - {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1>
|
||||
{% else %}
|
||||
<h1 class="archive-heading">Archiv {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1>
|
||||
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if archive_context.kind == 'tag' or archive_context.kind == 'category' %}
|
||||
<h1 class="archive-heading">{{ archive_context.name }}</h1>
|
||||
{% elsif archive_context.kind == 'month' and archive_context.month and archive_context.year %}
|
||||
{% assign month_index = archive_context.month | minus: 1 %}
|
||||
{% assign month_name = month_names_de[month_index] %}
|
||||
<h1 class="archive-heading">Archiv {{ month_name }} {{ archive_context.year }}</h1>
|
||||
{% assign month_key = 'render.month.' | append: archive_context.month %}
|
||||
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ month_key | i18n: language }} {{ archive_context.year }}</h1>
|
||||
{% elsif archive_context.kind == 'year' and archive_context.year %}
|
||||
<h1 class="archive-heading">Archiv {{ archive_context.year }}</h1>
|
||||
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ archive_context.year }}</h1>
|
||||
{% elsif archive_context.kind == 'day' and archive_context.day and archive_context.month and archive_context.year %}
|
||||
{% assign day_month_index = archive_context.month | minus: 1 %}
|
||||
{% assign day_month_name = month_names_de[day_month_index] %}
|
||||
<h1 class="archive-heading">Archiv {{ archive_context.day }}. {{ day_month_name }} {{ archive_context.year }}</h1>
|
||||
{% assign day_month_key = 'render.month.' | append: archive_context.month %}
|
||||
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ archive_context.day }}. {{ day_month_key | i18n: language }} {{ archive_context.year }}</h1>
|
||||
{% else %}
|
||||
<h1 class="archive-heading">{{ page_title }}</h1>
|
||||
{% endif %}
|
||||
@@ -41,7 +38,7 @@
|
||||
{% if post.show_title %}
|
||||
<h2 class="post-title">{{ post.title }}</h2>
|
||||
{% endif %}
|
||||
{{ post.content | markdown: post.id, canonical_post_path_by_slug, canonical_media_path_by_source_path }}
|
||||
{{ post.content | markdown: post.id, canonical_post_path_by_slug, canonical_media_path_by_source_path, language }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -52,7 +49,7 @@
|
||||
{% if post.show_title %}
|
||||
<h2 class="post-title">{{ post.title }}</h2>
|
||||
{% endif %}
|
||||
{{ post.content | markdown: post.id, canonical_post_path_by_slug, canonical_media_path_by_source_path }}
|
||||
{{ post.content | markdown: post.id, canonical_post_path_by_slug, canonical_media_path_by_source_path, language }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
@@ -64,15 +61,15 @@
|
||||
</section>
|
||||
|
||||
{% if has_prev_page or has_next_page %}
|
||||
<nav class="preview-pagination" aria-label="Pagination">
|
||||
<nav class="preview-pagination" aria-label="{{ 'render.pagination.label' | i18n: language }}">
|
||||
{% if has_prev_page %}
|
||||
<a href="{{ prev_page_href }}" class="preview-pagination-link" aria-label="Newer posts">neuer</a>
|
||||
<a href="{{ prev_page_href }}" class="preview-pagination-link" aria-label="{{ 'render.pagination.newer' | i18n: language }}">{{ 'render.pagination.newer' | i18n: language }}</a>
|
||||
{% else %}
|
||||
<span class="spacer"></span>
|
||||
{% endif %}
|
||||
|
||||
{% if has_next_page %}
|
||||
<a href="{{ next_page_href }}" class="preview-pagination-link" aria-label="Older posts">älter</a>
|
||||
<a href="{{ next_page_href }}" class="preview-pagination-link" aria-label="{{ 'render.pagination.older' | i18n: language }}">{{ 'render.pagination.older' | i18n: language }}</a>
|
||||
{% else %}
|
||||
<span class="spacer"></span>
|
||||
{% endif %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<main>
|
||||
<article class="single-post" data-template="single-post">
|
||||
<h1>{{ post.title }}</h1>
|
||||
<div class="post">{{ post.content | markdown: post.id, canonical_post_path_by_slug, canonical_media_path_by_source_path }}</div>
|
||||
<div class="post">{{ post.content | markdown: post.id, canonical_post_path_by_slug, canonical_media_path_by_source_path, language }}</div>
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
@@ -222,6 +222,10 @@ export function registerIpcHandlers(): void {
|
||||
return engine.deleteProjectWithData(id);
|
||||
});
|
||||
|
||||
safeHandle('app:getSystemLanguage', async () => {
|
||||
return app.getLocale();
|
||||
});
|
||||
|
||||
safeHandle('projects:get', async (_, id: string) => {
|
||||
const engine = getProjectEngine();
|
||||
return engine.getProject(id);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { getMediaEngine } from './engine/MediaEngine';
|
||||
import { getPostEngine } from './engine/PostEngine';
|
||||
import { PreviewServer } from './engine/PreviewServer';
|
||||
import { APP_MENU_ACTION_EVENT_MAP, APP_MENU_GROUPS, APP_MENU_ITEM_IDS, type AppMenuAction, type AppMenuItemDefinition } from './shared/menuCommands';
|
||||
import { resolveUiLanguageFromSystemLocale, translateMenu } from './shared/i18n';
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
let previewServer: PreviewServer | null = null;
|
||||
@@ -170,6 +171,8 @@ async function startPreviewServerOnAppStart(): Promise<void> {
|
||||
}
|
||||
|
||||
function createApplicationMenu(): Menu {
|
||||
const systemLocale = typeof app.getLocale === 'function' ? app.getLocale() : 'en';
|
||||
const uiLanguage = resolveUiLanguageFromSystemLocale(systemLocale);
|
||||
const commandDefinitions = APP_MENU_GROUPS
|
||||
.flatMap(group => group.items)
|
||||
.filter(item => !item.separator)
|
||||
@@ -258,22 +261,32 @@ function createApplicationMenu(): Menu {
|
||||
}
|
||||
};
|
||||
|
||||
const getMenuItemLabel = (action: AppMenuAction, fallback: string): string => {
|
||||
return translateMenu(uiLanguage, `menu.item.${action}`) || fallback;
|
||||
};
|
||||
|
||||
const getMenuGroupLabel = (groupLabel: string): string => {
|
||||
return translateMenu(uiLanguage, `menu.group.${groupLabel.toLowerCase()}`) || groupLabel;
|
||||
};
|
||||
|
||||
const buildSharedMenuItem = (action: AppMenuAction): MenuItemConstructorOptions => {
|
||||
const definition = commandDefinitions[action];
|
||||
if (!definition) {
|
||||
throw new Error(`Unknown shared menu action: ${action}`);
|
||||
}
|
||||
|
||||
const translatedLabel = getMenuItemLabel(action, definition.label);
|
||||
|
||||
if (definition.role) {
|
||||
return {
|
||||
label: definition.label,
|
||||
label: translatedLabel,
|
||||
role: definition.role,
|
||||
accelerator: definition.accelerator,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: definition.label,
|
||||
label: translatedLabel,
|
||||
accelerator: definition.accelerator,
|
||||
id: definition.id,
|
||||
enabled: definition.enabled,
|
||||
@@ -302,23 +315,23 @@ function createApplicationMenu(): Menu {
|
||||
|
||||
const template: MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: 'File',
|
||||
label: getMenuGroupLabel('File'),
|
||||
submenu: buildSharedGroupMenuItems('File'),
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
label: getMenuGroupLabel('Edit'),
|
||||
submenu: buildSharedGroupMenuItems('Edit'),
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
label: getMenuGroupLabel('View'),
|
||||
submenu: buildSharedGroupMenuItems('View'),
|
||||
},
|
||||
{
|
||||
label: 'Blog',
|
||||
label: getMenuGroupLabel('Blog'),
|
||||
submenu: buildSharedGroupMenuItems('Blog'),
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
label: getMenuGroupLabel('Help'),
|
||||
submenu: buildSharedGroupMenuItems('Help'),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -137,6 +137,7 @@ export const electronAPI: ElectronAPI = {
|
||||
// App
|
||||
app: {
|
||||
getDataPaths: () => ipcRenderer.invoke('app:getDataPaths'),
|
||||
getSystemLanguage: () => ipcRenderer.invoke('app:getSystemLanguage'),
|
||||
getTitleBarMetrics: () => ipcRenderer.invoke('app:getTitleBarMetrics'),
|
||||
openFolder: (folderPath: string) => ipcRenderer.invoke('app:openFolder', folderPath),
|
||||
showItemInFolder: (itemPath: string) => ipcRenderer.invoke('app:showItemInFolder', itemPath),
|
||||
|
||||
@@ -516,6 +516,7 @@ export interface ElectronAPI {
|
||||
};
|
||||
app: {
|
||||
getDataPaths: () => Promise<{ database: string; posts: string; media: string }>;
|
||||
getSystemLanguage: () => Promise<string>;
|
||||
getTitleBarMetrics: () => Promise<{ macosLeftInset: number } | null>;
|
||||
openFolder: (folderPath: string) => Promise<string>;
|
||||
showItemInFolder: (itemPath: string) => Promise<void>;
|
||||
|
||||
55
src/main/shared/i18n.ts
Normal file
55
src/main/shared/i18n.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import enJson from './i18n/locales/en.json';
|
||||
import deJson from './i18n/locales/de.json';
|
||||
import frJson from './i18n/locales/fr.json';
|
||||
import itJson from './i18n/locales/it.json';
|
||||
import esJson from './i18n/locales/es.json';
|
||||
|
||||
export type SupportedLanguage = 'en' | 'de' | 'fr' | 'it' | 'es';
|
||||
|
||||
type TranslationMap = Record<string, string>;
|
||||
|
||||
const en = enJson as TranslationMap;
|
||||
const de = { ...en, ...(deJson as TranslationMap) };
|
||||
const fr = { ...en, ...(frJson as TranslationMap) };
|
||||
const it = { ...en, ...(itJson as TranslationMap) };
|
||||
const es = { ...en, ...(esJson as TranslationMap) };
|
||||
|
||||
const catalog: Record<SupportedLanguage, TranslationMap> = { en, de, fr, it, es };
|
||||
|
||||
function normalizeLanguage(input: string | undefined | null): SupportedLanguage {
|
||||
const normalized = (input || '').trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return 'en';
|
||||
}
|
||||
|
||||
const base = normalized.split('-')[0];
|
||||
if (base === 'en' || base === 'de' || base === 'fr' || base === 'it' || base === 'es') {
|
||||
return base;
|
||||
}
|
||||
|
||||
return 'en';
|
||||
}
|
||||
|
||||
export function resolveSupportedRenderLanguage(language: string | undefined | null): SupportedLanguage {
|
||||
return normalizeLanguage(language);
|
||||
}
|
||||
|
||||
export function resolveRenderLanguageFromProjectPreferences(mainLanguage: string | undefined | null): SupportedLanguage {
|
||||
return normalizeLanguage(mainLanguage);
|
||||
}
|
||||
|
||||
export function resolveSupportedUiLanguage(language: string | undefined | null): SupportedLanguage {
|
||||
return normalizeLanguage(language);
|
||||
}
|
||||
|
||||
export function resolveUiLanguageFromSystemLocale(systemLocale: string | undefined | null): SupportedLanguage {
|
||||
return normalizeLanguage(systemLocale);
|
||||
}
|
||||
|
||||
export function translateRender(language: SupportedLanguage, key: string): string {
|
||||
return catalog[language]?.[key] ?? catalog.en[key] ?? key;
|
||||
}
|
||||
|
||||
export function translateMenu(language: SupportedLanguage, key: string): string {
|
||||
return catalog[language]?.[key] ?? catalog.en[key] ?? key;
|
||||
}
|
||||
67
src/main/shared/i18n/locales/de.json
Normal file
67
src/main/shared/i18n/locales/de.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"menu.group.file": "Datei",
|
||||
"menu.group.edit": "Bearbeiten",
|
||||
"menu.group.view": "Ansicht",
|
||||
"menu.group.blog": "Blogbereich",
|
||||
"menu.group.help": "Hilfe",
|
||||
"menu.item.newPost": "Neuer Beitrag",
|
||||
"menu.item.importMedia": "Medien importieren...",
|
||||
"menu.item.save": "Speichern",
|
||||
"menu.item.openInBrowser": "Im Browser öffnen",
|
||||
"menu.item.openDataFolder": "Datenordner öffnen",
|
||||
"menu.item.quit": "Beenden",
|
||||
"menu.item.undo": "Rückgängig",
|
||||
"menu.item.redo": "Wiederholen",
|
||||
"menu.item.cut": "Ausschneiden",
|
||||
"menu.item.copy": "Kopieren",
|
||||
"menu.item.paste": "Einfügen",
|
||||
"menu.item.delete": "Löschen",
|
||||
"menu.item.selectAll": "Alles auswählen",
|
||||
"menu.item.find": "Suchen",
|
||||
"menu.item.replace": "Ersetzen",
|
||||
"menu.item.viewPosts": "Beiträge",
|
||||
"menu.item.viewMedia": "Medien",
|
||||
"menu.item.toggleSidebar": "Seitenleiste umschalten",
|
||||
"menu.item.togglePanel": "Panel umschalten",
|
||||
"menu.item.toggleDevTools": "Entwicklerwerkzeuge umschalten",
|
||||
"menu.item.reload": "Neu laden",
|
||||
"menu.item.forceReload": "Erzwungen neu laden",
|
||||
"menu.item.resetZoom": "Tatsächliche Größe",
|
||||
"menu.item.zoomIn": "Vergrößern",
|
||||
"menu.item.zoomOut": "Verkleinern",
|
||||
"menu.item.toggleFullScreen": "Vollbild umschalten",
|
||||
"menu.item.publishSelected": "Ausgewählte veröffentlichen",
|
||||
"menu.item.previewPost": "Beitragsvorschau",
|
||||
"menu.item.rebuildDatabase": "Datenbank aus Dateien neu aufbauen",
|
||||
"menu.item.reindexText": "Suchtext neu indizieren",
|
||||
"menu.item.metadataDiff": "Metadaten-Diff-Werkzeug",
|
||||
"menu.item.generateSitemap": "Site rendern",
|
||||
"menu.item.about": "Über Blogging Desktop Server",
|
||||
"menu.item.openDocumentation": "Dokumentation öffnen",
|
||||
"menu.item.viewOnGitHub": "Auf GitHub ansehen",
|
||||
"menu.item.reportIssue": "Problem melden",
|
||||
"render.archive": "Archiv",
|
||||
"render.pagination.label": "Seitennummerierung",
|
||||
"render.pagination.newer": "neuer",
|
||||
"render.pagination.older": "älter",
|
||||
"render.notFound.message": "Die angeforderte Vorschauseite konnte nicht gefunden werden.",
|
||||
"render.notFound.back": "Zurück zur Vorschau-Startseite",
|
||||
"render.photoArchive.empty": "Keine Fotos für dieses Archiv gefunden.",
|
||||
"render.gallery.empty": "Keine verknüpften Bilder gefunden.",
|
||||
"render.tagCloud.empty": "Keine Tags gefunden.",
|
||||
"render.tagCloud.ariaLabel": "Tag-Wolke",
|
||||
"render.video.youtubeTitle": "YouTube-Video",
|
||||
"render.video.vimeoTitle": "Vimeo-Video",
|
||||
"render.month.1": "Januar",
|
||||
"render.month.2": "Februar",
|
||||
"render.month.3": "März",
|
||||
"render.month.4": "Apr.",
|
||||
"render.month.5": "Mai",
|
||||
"render.month.6": "Juni",
|
||||
"render.month.7": "Juli",
|
||||
"render.month.8": "Aug.",
|
||||
"render.month.9": "Sept.",
|
||||
"render.month.10": "Oktober",
|
||||
"render.month.11": "Nov.",
|
||||
"render.month.12": "Dezember"
|
||||
}
|
||||
67
src/main/shared/i18n/locales/en.json
Normal file
67
src/main/shared/i18n/locales/en.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"menu.group.file": "File",
|
||||
"menu.group.edit": "Edit",
|
||||
"menu.group.view": "View",
|
||||
"menu.group.blog": "Blog",
|
||||
"menu.group.help": "Help",
|
||||
"menu.item.newPost": "New Post",
|
||||
"menu.item.importMedia": "Import Media...",
|
||||
"menu.item.save": "Save",
|
||||
"menu.item.openInBrowser": "Open in Browser",
|
||||
"menu.item.openDataFolder": "Open Data Folder",
|
||||
"menu.item.quit": "Quit",
|
||||
"menu.item.undo": "Undo",
|
||||
"menu.item.redo": "Redo",
|
||||
"menu.item.cut": "Cut",
|
||||
"menu.item.copy": "Copy",
|
||||
"menu.item.paste": "Paste",
|
||||
"menu.item.delete": "Delete",
|
||||
"menu.item.selectAll": "Select All",
|
||||
"menu.item.find": "Find",
|
||||
"menu.item.replace": "Replace",
|
||||
"menu.item.viewPosts": "Posts",
|
||||
"menu.item.viewMedia": "Media",
|
||||
"menu.item.toggleSidebar": "Toggle Sidebar",
|
||||
"menu.item.togglePanel": "Toggle Panel",
|
||||
"menu.item.toggleDevTools": "Toggle Developer Tools",
|
||||
"menu.item.reload": "Reload",
|
||||
"menu.item.forceReload": "Force Reload",
|
||||
"menu.item.resetZoom": "Actual Size",
|
||||
"menu.item.zoomIn": "Zoom In",
|
||||
"menu.item.zoomOut": "Zoom Out",
|
||||
"menu.item.toggleFullScreen": "Toggle Full Screen",
|
||||
"menu.item.publishSelected": "Publish Selected",
|
||||
"menu.item.previewPost": "Preview Post",
|
||||
"menu.item.rebuildDatabase": "Rebuild Database from Files",
|
||||
"menu.item.reindexText": "Reindex Search Text",
|
||||
"menu.item.metadataDiff": "Metadata Diff Tool",
|
||||
"menu.item.generateSitemap": "Render Site",
|
||||
"menu.item.about": "About Blogging Desktop Server",
|
||||
"menu.item.openDocumentation": "Open Documentation",
|
||||
"menu.item.viewOnGitHub": "View on GitHub",
|
||||
"menu.item.reportIssue": "Report Issue",
|
||||
"render.archive": "Archive",
|
||||
"render.pagination.label": "Pagination",
|
||||
"render.pagination.newer": "newer",
|
||||
"render.pagination.older": "older",
|
||||
"render.notFound.message": "The requested preview page could not be found.",
|
||||
"render.notFound.back": "Back to preview home",
|
||||
"render.photoArchive.empty": "No photos found for this archive.",
|
||||
"render.gallery.empty": "No linked images found.",
|
||||
"render.tagCloud.empty": "No tags found.",
|
||||
"render.tagCloud.ariaLabel": "Tag cloud",
|
||||
"render.video.youtubeTitle": "YouTube video",
|
||||
"render.video.vimeoTitle": "Vimeo video",
|
||||
"render.month.1": "January",
|
||||
"render.month.2": "February",
|
||||
"render.month.3": "March",
|
||||
"render.month.4": "April",
|
||||
"render.month.5": "May",
|
||||
"render.month.6": "June",
|
||||
"render.month.7": "July",
|
||||
"render.month.8": "August",
|
||||
"render.month.9": "September",
|
||||
"render.month.10": "October",
|
||||
"render.month.11": "November",
|
||||
"render.month.12": "December"
|
||||
}
|
||||
67
src/main/shared/i18n/locales/es.json
Normal file
67
src/main/shared/i18n/locales/es.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"menu.group.file": "Archivo",
|
||||
"menu.group.edit": "Editar",
|
||||
"menu.group.view": "Ver",
|
||||
"menu.group.blog": "Bitácora",
|
||||
"menu.group.help": "Ayuda",
|
||||
"menu.item.newPost": "Nueva entrada",
|
||||
"menu.item.importMedia": "Importar medios...",
|
||||
"menu.item.save": "Guardar",
|
||||
"menu.item.openInBrowser": "Abrir en el navegador",
|
||||
"menu.item.openDataFolder": "Abrir carpeta de datos",
|
||||
"menu.item.quit": "Salir",
|
||||
"menu.item.undo": "Deshacer",
|
||||
"menu.item.redo": "Rehacer",
|
||||
"menu.item.cut": "Cortar",
|
||||
"menu.item.copy": "Copiar",
|
||||
"menu.item.paste": "Pegar",
|
||||
"menu.item.delete": "Eliminar",
|
||||
"menu.item.selectAll": "Seleccionar todo",
|
||||
"menu.item.find": "Buscar",
|
||||
"menu.item.replace": "Reemplazar",
|
||||
"menu.item.viewPosts": "Entradas",
|
||||
"menu.item.viewMedia": "Medios",
|
||||
"menu.item.toggleSidebar": "Alternar barra lateral",
|
||||
"menu.item.togglePanel": "Alternar panel",
|
||||
"menu.item.toggleDevTools": "Alternar herramientas de desarrollo",
|
||||
"menu.item.reload": "Recargar",
|
||||
"menu.item.forceReload": "Forzar recarga",
|
||||
"menu.item.resetZoom": "Tamaño real",
|
||||
"menu.item.zoomIn": "Acercar",
|
||||
"menu.item.zoomOut": "Alejar",
|
||||
"menu.item.toggleFullScreen": "Alternar pantalla completa",
|
||||
"menu.item.publishSelected": "Publicar selección",
|
||||
"menu.item.previewPost": "Vista previa de entrada",
|
||||
"menu.item.rebuildDatabase": "Reconstruir Database from Files",
|
||||
"menu.item.reindexText": "Reindex Buscar Text",
|
||||
"menu.item.metadataDiff": "Herramienta diff de metadatos",
|
||||
"menu.item.generateSitemap": "Renderizar sitio",
|
||||
"menu.item.about": "Acerca de Blogging Desktop Server",
|
||||
"menu.item.openDocumentation": "Abrir documentación",
|
||||
"menu.item.viewOnGitHub": "Ver en GitHub",
|
||||
"menu.item.reportIssue": "Reportar problema",
|
||||
"render.archive": "Archivo",
|
||||
"render.pagination.label": "Paginación",
|
||||
"render.pagination.newer": "más reciente",
|
||||
"render.pagination.older": "más antiguo",
|
||||
"render.notFound.message": "No se pudo encontrar la página de vista previa solicitada.",
|
||||
"render.notFound.back": "Volver al inicio de vista previa",
|
||||
"render.photoArchive.empty": "No se encontraron fotos para este archivo.",
|
||||
"render.gallery.empty": "No se encontraron imágenes vinculadas.",
|
||||
"render.tagCloud.empty": "No se encontraron etiquetas.",
|
||||
"render.tagCloud.ariaLabel": "Nube de etiquetas",
|
||||
"render.video.youtubeTitle": "Vídeo de YouTube",
|
||||
"render.video.vimeoTitle": "Vídeo de Vimeo",
|
||||
"render.month.1": "enero",
|
||||
"render.month.2": "febrero",
|
||||
"render.month.3": "marzo",
|
||||
"render.month.4": "abril",
|
||||
"render.month.5": "mayo",
|
||||
"render.month.6": "junio",
|
||||
"render.month.7": "julio",
|
||||
"render.month.8": "agosto",
|
||||
"render.month.9": "septiembre",
|
||||
"render.month.10": "octubre",
|
||||
"render.month.11": "noviembre",
|
||||
"render.month.12": "diciembre"
|
||||
}
|
||||
67
src/main/shared/i18n/locales/fr.json
Normal file
67
src/main/shared/i18n/locales/fr.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"menu.group.file": "Fichier",
|
||||
"menu.group.edit": "Édition",
|
||||
"menu.group.view": "Affichage",
|
||||
"menu.group.blog": "Espace blog",
|
||||
"menu.group.help": "Aide",
|
||||
"menu.item.newPost": "Nouvel article",
|
||||
"menu.item.importMedia": "Importer des médias...",
|
||||
"menu.item.save": "Enregistrer",
|
||||
"menu.item.openInBrowser": "Ouvrir dans le navigateur",
|
||||
"menu.item.openDataFolder": "Ouvrir le dossier de données",
|
||||
"menu.item.quit": "Quitter",
|
||||
"menu.item.undo": "Annuler",
|
||||
"menu.item.redo": "Rétablir",
|
||||
"menu.item.cut": "Couper",
|
||||
"menu.item.copy": "Copier",
|
||||
"menu.item.paste": "Coller",
|
||||
"menu.item.delete": "Supprimer",
|
||||
"menu.item.selectAll": "Tout sélectionner",
|
||||
"menu.item.find": "Rechercher",
|
||||
"menu.item.replace": "Remplacer",
|
||||
"menu.item.viewPosts": "Articles",
|
||||
"menu.item.viewMedia": "Médias",
|
||||
"menu.item.toggleSidebar": "Basculer la barre latérale",
|
||||
"menu.item.togglePanel": "Basculer le panneau",
|
||||
"menu.item.toggleDevTools": "Basculer les outils de développement",
|
||||
"menu.item.reload": "Recharger",
|
||||
"menu.item.forceReload": "Forcer le rechargement",
|
||||
"menu.item.resetZoom": "Taille réelle",
|
||||
"menu.item.zoomIn": "Zoom avant",
|
||||
"menu.item.zoomOut": "Zoom arrière",
|
||||
"menu.item.toggleFullScreen": "Basculer en plein écran",
|
||||
"menu.item.publishSelected": "Publier la sélection",
|
||||
"menu.item.previewPost": "Aperçu de l’article",
|
||||
"menu.item.rebuildDatabase": "Reconstruire Database from Files",
|
||||
"menu.item.reindexText": "Reindex Recherche Text",
|
||||
"menu.item.metadataDiff": "Outil de diff des métadonnées",
|
||||
"menu.item.generateSitemap": "Rendre le site",
|
||||
"menu.item.about": "À propos de Blogging Desktop Server",
|
||||
"menu.item.openDocumentation": "Ouvrir la documentation",
|
||||
"menu.item.viewOnGitHub": "Voir sur GitHub",
|
||||
"menu.item.reportIssue": "Signaler un problème",
|
||||
"render.archive": "Archives",
|
||||
"render.pagination.label": "Navigation paginée",
|
||||
"render.pagination.newer": "plus récent",
|
||||
"render.pagination.older": "plus ancien",
|
||||
"render.notFound.message": "La page d’aperçu demandée est introuvable.",
|
||||
"render.notFound.back": "Retour à l’accueil de l’aperçu",
|
||||
"render.photoArchive.empty": "Aucune photo trouvée pour cette archive.",
|
||||
"render.gallery.empty": "Aucune image liée trouvée.",
|
||||
"render.tagCloud.empty": "Aucun tag trouvé.",
|
||||
"render.tagCloud.ariaLabel": "Nuage de tags",
|
||||
"render.video.youtubeTitle": "Vidéo YouTube",
|
||||
"render.video.vimeoTitle": "Vidéo Vimeo",
|
||||
"render.month.1": "janvier",
|
||||
"render.month.2": "février",
|
||||
"render.month.3": "mars",
|
||||
"render.month.4": "avril",
|
||||
"render.month.5": "mai",
|
||||
"render.month.6": "juin",
|
||||
"render.month.7": "juillet",
|
||||
"render.month.8": "août",
|
||||
"render.month.9": "septembre",
|
||||
"render.month.10": "octobre",
|
||||
"render.month.11": "novembre",
|
||||
"render.month.12": "décembre"
|
||||
}
|
||||
67
src/main/shared/i18n/locales/it.json
Normal file
67
src/main/shared/i18n/locales/it.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"menu.group.file": "Archivio",
|
||||
"menu.group.edit": "Modifica",
|
||||
"menu.group.view": "Vista",
|
||||
"menu.group.blog": "Sezione blog",
|
||||
"menu.group.help": "Aiuto",
|
||||
"menu.item.newPost": "Nuovo post",
|
||||
"menu.item.importMedia": "Importa media...",
|
||||
"menu.item.save": "Salva",
|
||||
"menu.item.openInBrowser": "Apri nel browser",
|
||||
"menu.item.openDataFolder": "Apri cartella dati",
|
||||
"menu.item.quit": "Esci",
|
||||
"menu.item.undo": "Annulla",
|
||||
"menu.item.redo": "Ripeti",
|
||||
"menu.item.cut": "Taglia",
|
||||
"menu.item.copy": "Copia",
|
||||
"menu.item.paste": "Incolla",
|
||||
"menu.item.delete": "Elimina",
|
||||
"menu.item.selectAll": "Seleziona tutto",
|
||||
"menu.item.find": "Trova",
|
||||
"menu.item.replace": "Sostituisci",
|
||||
"menu.item.viewPosts": "Post",
|
||||
"menu.item.viewMedia": "Contenuti media",
|
||||
"menu.item.toggleSidebar": "Attiva/disattiva barra laterale",
|
||||
"menu.item.togglePanel": "Attiva/disattiva pannello",
|
||||
"menu.item.toggleDevTools": "Attiva/disattiva strumenti sviluppatore",
|
||||
"menu.item.reload": "Ricarica",
|
||||
"menu.item.forceReload": "Forza ricaricamento",
|
||||
"menu.item.resetZoom": "Dimensione reale",
|
||||
"menu.item.zoomIn": "Ingrandisci",
|
||||
"menu.item.zoomOut": "Riduci",
|
||||
"menu.item.toggleFullScreen": "Attiva/disattiva schermo intero",
|
||||
"menu.item.publishSelected": "Pubblica selezionato",
|
||||
"menu.item.previewPost": "Anteprima post",
|
||||
"menu.item.rebuildDatabase": "Ricostruisci Database from Files",
|
||||
"menu.item.reindexText": "Reindex Ricerca Text",
|
||||
"menu.item.metadataDiff": "Strumento diff metadati",
|
||||
"menu.item.generateSitemap": "Renderizza sito",
|
||||
"menu.item.about": "Informazioni su Blogging Desktop Server",
|
||||
"menu.item.openDocumentation": "Apri documentazione",
|
||||
"menu.item.viewOnGitHub": "Visualizza su GitHub",
|
||||
"menu.item.reportIssue": "Segnala problema",
|
||||
"render.archive": "Archivio",
|
||||
"render.pagination.label": "Paginazione",
|
||||
"render.pagination.newer": "più recente",
|
||||
"render.pagination.older": "più vecchio",
|
||||
"render.notFound.message": "La pagina di anteprima richiesta non è stata trovata.",
|
||||
"render.notFound.back": "Torna alla home di anteprima",
|
||||
"render.photoArchive.empty": "Nessuna foto trovata per questo archivio.",
|
||||
"render.gallery.empty": "Nessuna immagine collegata trovata.",
|
||||
"render.tagCloud.empty": "Nessun tag trovato.",
|
||||
"render.tagCloud.ariaLabel": "Nuvola di tag",
|
||||
"render.video.youtubeTitle": "Video YouTube",
|
||||
"render.video.vimeoTitle": "Video Vimeo",
|
||||
"render.month.1": "gennaio",
|
||||
"render.month.2": "febbraio",
|
||||
"render.month.3": "marzo",
|
||||
"render.month.4": "aprile",
|
||||
"render.month.5": "maggio",
|
||||
"render.month.6": "giugno",
|
||||
"render.month.7": "luglio",
|
||||
"render.month.8": "agosto",
|
||||
"render.month.9": "settembre",
|
||||
"render.month.10": "ottobre",
|
||||
"render.month.11": "novembre",
|
||||
"render.month.12": "dicembre"
|
||||
}
|
||||
@@ -61,76 +61,76 @@ export const APP_MENU_GROUPS: AppMenuGroupDefinition[] = [
|
||||
{
|
||||
label: 'File',
|
||||
items: [
|
||||
{ label: 'New Post', action: 'newPost', accelerator: 'CmdOrCtrl+N' },
|
||||
{ label: 'Import Media...', action: 'importMedia', accelerator: 'CmdOrCtrl+I' },
|
||||
{ label: 'menu.item.newPost', action: 'newPost', accelerator: 'CmdOrCtrl+N' },
|
||||
{ label: 'menu.item.importMedia', action: 'importMedia', accelerator: 'CmdOrCtrl+I' },
|
||||
{ label: '', action: 'file-separator-0', separator: true },
|
||||
{ label: 'Save', action: 'save', accelerator: 'CmdOrCtrl+S' },
|
||||
{ label: 'menu.item.save', action: 'save', accelerator: 'CmdOrCtrl+S' },
|
||||
{ label: '', action: 'file-separator-1', separator: true },
|
||||
{ label: 'Open in Browser', action: 'openInBrowser' },
|
||||
{ label: 'menu.item.openInBrowser', action: 'openInBrowser' },
|
||||
{ label: '', action: 'file-separator-2', separator: true },
|
||||
{ label: 'Open Data Folder', action: 'openDataFolder' },
|
||||
{ label: 'menu.item.openDataFolder', action: 'openDataFolder' },
|
||||
{ label: '', action: 'file-separator-3', separator: true },
|
||||
{ label: 'Quit', action: 'quit', accelerator: 'CmdOrCtrl+Q' },
|
||||
{ label: 'menu.item.quit', action: 'quit', accelerator: 'CmdOrCtrl+Q' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
items: [
|
||||
{ label: 'Undo', action: 'undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
|
||||
{ label: 'Redo', action: 'redo', accelerator: 'CmdOrCtrl+Y', role: 'redo' },
|
||||
{ label: 'menu.item.undo', action: 'undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
|
||||
{ label: 'menu.item.redo', action: 'redo', accelerator: 'CmdOrCtrl+Y', role: 'redo' },
|
||||
{ label: '', action: 'edit-separator-1', separator: true },
|
||||
{ label: 'Cut', action: 'cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
||||
{ label: 'Copy', action: 'copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
||||
{ label: 'Paste', action: 'paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
|
||||
{ label: 'Delete', action: 'delete', role: 'delete' },
|
||||
{ label: 'menu.item.cut', action: 'cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
||||
{ label: 'menu.item.copy', action: 'copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
||||
{ label: 'menu.item.paste', action: 'paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
|
||||
{ label: 'menu.item.delete', action: 'delete', role: 'delete' },
|
||||
{ label: '', action: 'edit-separator-2', separator: true },
|
||||
{ label: 'Select All', action: 'selectAll', accelerator: 'CmdOrCtrl+A', role: 'selectAll' },
|
||||
{ label: 'menu.item.selectAll', action: 'selectAll', accelerator: 'CmdOrCtrl+A', role: 'selectAll' },
|
||||
{ label: '', action: 'edit-separator-3', separator: true },
|
||||
{ label: 'Find', action: 'find', accelerator: 'CmdOrCtrl+F' },
|
||||
{ label: 'Replace', action: 'replace', accelerator: 'CmdOrCtrl+H' },
|
||||
{ label: 'menu.item.find', action: 'find', accelerator: 'CmdOrCtrl+F' },
|
||||
{ label: 'menu.item.replace', action: 'replace', accelerator: 'CmdOrCtrl+H' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
items: [
|
||||
{ label: 'Posts', action: 'viewPosts', accelerator: 'CmdOrCtrl+1' },
|
||||
{ label: 'Media', action: 'viewMedia', accelerator: 'CmdOrCtrl+2' },
|
||||
{ label: 'Toggle Sidebar', action: 'toggleSidebar', accelerator: 'CmdOrCtrl+B' },
|
||||
{ label: 'Toggle Panel', action: 'togglePanel', accelerator: 'CmdOrCtrl+J' },
|
||||
{ label: 'Toggle Developer Tools', action: 'toggleDevTools', accelerator: 'CmdOrCtrl+Shift+I' },
|
||||
{ label: 'menu.item.viewPosts', action: 'viewPosts', accelerator: 'CmdOrCtrl+1' },
|
||||
{ label: 'menu.item.viewMedia', action: 'viewMedia', accelerator: 'CmdOrCtrl+2' },
|
||||
{ label: 'menu.item.toggleSidebar', action: 'toggleSidebar', accelerator: 'CmdOrCtrl+B' },
|
||||
{ label: 'menu.item.togglePanel', action: 'togglePanel', accelerator: 'CmdOrCtrl+J' },
|
||||
{ label: 'menu.item.toggleDevTools', action: 'toggleDevTools', accelerator: 'CmdOrCtrl+Shift+I' },
|
||||
{ label: '', action: 'view-separator-1', separator: true },
|
||||
{ label: 'Reload', action: 'reload' },
|
||||
{ label: 'Force Reload', action: 'forceReload' },
|
||||
{ label: 'menu.item.reload', action: 'reload' },
|
||||
{ label: 'menu.item.forceReload', action: 'forceReload' },
|
||||
{ label: '', action: 'view-separator-2', separator: true },
|
||||
{ label: 'Actual Size', action: 'resetZoom' },
|
||||
{ label: 'Zoom In', action: 'zoomIn' },
|
||||
{ label: 'Zoom Out', action: 'zoomOut' },
|
||||
{ label: 'menu.item.resetZoom', action: 'resetZoom' },
|
||||
{ label: 'menu.item.zoomIn', action: 'zoomIn' },
|
||||
{ label: 'menu.item.zoomOut', action: 'zoomOut' },
|
||||
{ label: '', action: 'view-separator-3', separator: true },
|
||||
{ label: 'Toggle Full Screen', action: 'toggleFullScreen' },
|
||||
{ label: 'menu.item.toggleFullScreen', action: 'toggleFullScreen' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Blog',
|
||||
items: [
|
||||
{ label: 'Publish Selected', action: 'publishSelected', accelerator: 'CmdOrCtrl+Shift+P' },
|
||||
{ label: 'menu.item.publishSelected', action: 'publishSelected', accelerator: 'CmdOrCtrl+Shift+P' },
|
||||
{ label: '', action: 'blog-separator-1', separator: true },
|
||||
{ label: 'Preview Post', action: 'previewPost', id: APP_MENU_ITEM_IDS.previewPost, enabled: false, accelerator: 'CmdOrCtrl+Shift+V' },
|
||||
{ label: 'menu.item.previewPost', action: 'previewPost', id: APP_MENU_ITEM_IDS.previewPost, enabled: false, accelerator: 'CmdOrCtrl+Shift+V' },
|
||||
{ label: '', action: 'blog-separator-2', separator: true },
|
||||
{ label: 'Rebuild Database from Files', action: 'rebuildDatabase' },
|
||||
{ label: 'Reindex Search Text', action: 'reindexText' },
|
||||
{ label: 'menu.item.rebuildDatabase', action: 'rebuildDatabase' },
|
||||
{ label: 'menu.item.reindexText', action: 'reindexText' },
|
||||
{ label: '', action: 'blog-separator-3', separator: true },
|
||||
{ label: 'Metadata Diff Tool', action: 'metadataDiff' },
|
||||
{ label: 'Render Site', action: 'generateSitemap', accelerator: 'CmdOrCtrl+R' },
|
||||
{ label: 'menu.item.metadataDiff', action: 'metadataDiff' },
|
||||
{ label: 'menu.item.generateSitemap', action: 'generateSitemap', accelerator: 'CmdOrCtrl+R' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
items: [
|
||||
{ label: 'About Blogging Desktop Server', action: 'about' },
|
||||
{ label: 'Open Documentation', action: 'openDocumentation' },
|
||||
{ label: 'menu.item.about', action: 'about' },
|
||||
{ label: 'menu.item.openDocumentation', action: 'openDocumentation' },
|
||||
{ label: '', action: 'help-separator-1', separator: true },
|
||||
{ label: 'View on GitHub', action: 'viewOnGitHub' },
|
||||
{ label: 'Report Issue', action: 'reportIssue' },
|
||||
{ label: 'menu.item.viewOnGitHub', action: 'viewOnGitHub' },
|
||||
{ label: 'menu.item.reportIssue', action: 'reportIssue' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user