) => {
+ if (!page.fmLocale) {
+ return null;
+ }
+
+ return (
+
+
+ {page.fmLocale.title || page.fmLocale.locale}
+
+ );
+};
\ No newline at end of file
diff --git a/src/dashboardWebView/components/Contents/Item.tsx b/src/dashboardWebView/components/Contents/Item.tsx
index 02ded451..5844e8ab 100644
--- a/src/dashboardWebView/components/Contents/Item.tsx
+++ b/src/dashboardWebView/components/Contents/Item.tsx
@@ -16,6 +16,7 @@ import { LocalizationKey } from '../../../localization';
import { useNavigate } from 'react-router-dom';
import { routePaths } from '../..';
import useCard from '../../hooks/useCard';
+import { I18nLabel } from './I18nLabel';
export interface IItemProps extends Page { }
@@ -132,6 +133,8 @@ export const Item: React.FunctionComponent = ({
onOpen={openFile}
/>
+
+
-
+ {
+ (escapedDescription || descriptionHtml) && (
+
+ )
+ }
{
tagsHtml ? (
diff --git a/src/dashboardWebView/models/Page.ts b/src/dashboardWebView/models/Page.ts
index 2d20f3e0..dce8467d 100644
--- a/src/dashboardWebView/models/Page.ts
+++ b/src/dashboardWebView/models/Page.ts
@@ -1,3 +1,5 @@
+import { I18nConfig } from '../../models';
+
export interface Page {
// Properties for caching
fmCachePath: string;
@@ -19,6 +21,11 @@ export interface Page {
fmContentType: string;
fmDateFormat: string | undefined;
+ // i18n fields
+ fmDefaultLocale?: boolean;
+ fmLocale?: I18nConfig;
+ fmTranslations?: { [locale: string]: string };
+
title: string;
slug: string;
date: string | Date;
diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts
index 03c8de9d..d3aa808f 100644
--- a/src/helpers/ArticleHelper.ts
+++ b/src/helpers/ArticleHelper.ts
@@ -123,8 +123,14 @@ export class ArticleHelper {
* Retrieve the file's front matter by its path
* @param filePath
*/
- public static async getFrontMatterByPath(filePath: string) {
- const file = await readFileAsync(filePath, { encoding: 'utf-8' });
+ public static async getFrontMatterByPath(
+ filePath: string
+ ): Promise {
+ const file = await ArticleHelper.getContents(filePath);
+ if (!file) {
+ return undefined;
+ }
+
const article = ArticleHelper.parseFile(file, filePath);
if (!article) {
return undefined;
@@ -136,6 +142,20 @@ export class ArticleHelper {
};
}
+ /**
+ * Reads the contents of a file asynchronously.
+ * @param filePath - The path of the file to read.
+ * @returns A promise that resolves to the contents of the file, or undefined if the file does not exist.
+ */
+ public static async getContents(filePath: string): Promise {
+ const file = await workspace.fs.readFile(Uri.file(parseWinPath(filePath)));
+ if (!file) {
+ return undefined;
+ }
+
+ return new TextDecoder().decode(file);
+ }
+
/**
* Store the new information in the file
*
@@ -370,21 +390,9 @@ export class ArticleHelper {
if (article.data.type) {
contentType = contentTypes.find((ct) => ct.name === article.data.type);
} else if (!contentType && article.path) {
- // Get the content type by the folder name
- let folders = Folders.get();
- let parsedPath = parseWinPath(article.path);
- let pageFolderMatches = folders.filter(
- (folder) => parsedPath && folder.path && parsedPath.includes(folder.path)
- );
-
- // Sort by longest path
- pageFolderMatches = pageFolderMatches.sort((a, b) => b.path.length - a.path.length);
- if (
- pageFolderMatches.length > 0 &&
- pageFolderMatches[0].contentTypes &&
- pageFolderMatches[0].contentTypes.length === 1
- ) {
- const contentTypeName = pageFolderMatches[0].contentTypes[0];
+ const pageFolder = Folders.getPageFolderByFilePath(article.path);
+ if (pageFolder && pageFolder.contentTypes?.length === 1) {
+ const contentTypeName = pageFolder.contentTypes[0];
contentType = contentTypes.find((ct) => ct.name === contentTypeName);
}
}
diff --git a/src/helpers/FrameworkDetector.ts b/src/helpers/FrameworkDetector.ts
index 0c98e7f8..dd44466e 100644
--- a/src/helpers/FrameworkDetector.ts
+++ b/src/helpers/FrameworkDetector.ts
@@ -151,6 +151,60 @@ export class FrameworkDetector {
return parseWinPath(relAssetPath);
}
+ /**
+ * Returns the absolute path by combining the relative path and the file path.
+ * If a static folder is configured, it will be taken into account.
+ * @param relAssetPath The relative path.
+ * @param filePath The file path.
+ * @returns The absolute path.
+ */
+ public static getAbsPathByFile(relAssetPath: string, filePath: string): string {
+ const staticFolderValue = Settings.get(SETTING_CONTENT_STATIC_FOLDER);
+ const staticFolder = Folders.getStaticFolderRelativePath();
+
+ if (
+ staticFolderValue &&
+ staticFolder &&
+ typeof staticFolderValue !== 'string' &&
+ staticFolderValue.relative
+ ) {
+ const fileDir = dirname(filePath);
+ return parseWinPath(join(fileDir, relAssetPath));
+ }
+
+ return relAssetPath;
+ }
+
+ /**
+ * Returns the relative path of an asset file based on the provided absolute asset path and file path.
+ * If the static folder setting is configured and the static folder is available, the relative path is calculated based on the file and asset directories.
+ * Otherwise, the absolute asset path is returned as is.
+ *
+ * @param absAssetPath The absolute path of the asset file.
+ * @param fileDir The path of the directory
+ * @returns The relative path of the asset file.
+ */
+ public static getRelPathByFileDir(absAssetPath: string, fileDir: string): string {
+ const staticFolderValue = Settings.get(SETTING_CONTENT_STATIC_FOLDER);
+ const staticFolder = Folders.getStaticFolderRelativePath();
+
+ if (
+ staticFolderValue &&
+ staticFolder &&
+ typeof staticFolderValue !== 'string' &&
+ staticFolderValue.relative
+ ) {
+ const assetDir = dirname(absAssetPath);
+ const fileName = parse(absAssetPath);
+
+ let relAssetPath = relative(fileDir, assetDir);
+ relAssetPath = join(relAssetPath, `${fileName.name}${fileName.ext}`);
+ return parseWinPath(relAssetPath);
+ }
+
+ return absAssetPath;
+ }
+
/**
* Define the default settings for Hexo
*/
diff --git a/src/models/ContentFolder.ts b/src/models/ContentFolder.ts
index 734245a8..88f00a1d 100644
--- a/src/models/ContentFolder.ts
+++ b/src/models/ContentFolder.ts
@@ -1,3 +1,5 @@
+import { I18nConfig } from './i18nConfig';
+
export interface ContentFolder {
title: string;
path: string;
@@ -10,4 +12,6 @@ export interface ContentFolder {
originalPath?: string;
$schema?: string;
extended?: boolean;
+ defaultLocale?: string;
+ locales: I18nConfig[];
}
diff --git a/src/models/i18nConfig.ts b/src/models/i18nConfig.ts
index 033b9325..1e472c1f 100644
--- a/src/models/i18nConfig.ts
+++ b/src/models/i18nConfig.ts
@@ -1,5 +1,5 @@
export interface I18nConfig {
- title?: string;
locale: string;
- path: string;
+ title?: string;
+ path?: string;
}
diff --git a/src/services/PagesParser.ts b/src/services/PagesParser.ts
index 06e95227..f573ebff 100644
--- a/src/services/PagesParser.ts
+++ b/src/services/PagesParser.ts
@@ -1,3 +1,4 @@
+import { i18n } from './../commands/i18n';
import { STATIC_FOLDER_PLACEHOLDER } from './../constants/StaticFolderPlaceholder';
import { parseWinPath } from './../helpers/parseWinPath';
import { dirname, extname, join } from 'path';
@@ -209,6 +210,10 @@ export class PagesParser {
escapedDescription = '';
}
+ const isDefaultLanguage = await i18n.isDefaultLanguage(filePath);
+ const locale = await i18n.getLocale(filePath);
+ const translations = await i18n.getTranslations(filePath);
+
const page: Page = {
...article.data,
// Cache properties
@@ -230,6 +235,10 @@ export class PagesParser {
fmContentType: contentType.name || DEFAULT_CONTENT_TYPE_NAME,
fmBody: article?.content || '',
fmDateFormat: dateFormat,
+ // i18n properties
+ fmDefaultLocale: isDefaultLanguage,
+ fmLocale: locale,
+ fmTranslations: translations,
// Make sure these are always set
title: escapedTitle,
description: escapedDescription,