Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"author": "trganda",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",
"@types/node": "^16.18.126",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"builtin-modules": "3.3.0",
Expand Down
18 changes: 17 additions & 1 deletion src/arrange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getMetadata } from "./settings/metadata";
import { getActiveFile } from "./commons";
import { isExcluded } from "./exclude";
import { containOriginalNameVariable, loadOriginalName } from "./lib/originalStorage";
import { LinkUpdater } from "./lib/linkUpdater";

const bannerRegex = /!\[\[(.*?)\]\]/i;

Expand All @@ -24,11 +25,13 @@ export class ArrangeHandler {
settings: AttachmentManagementPluginSettings;
app: App;
plugin: Plugin;
linkUpdater: LinkUpdater;

constructor(settings: AttachmentManagementPluginSettings, app: App, plugin: Plugin) {
this.settings = settings;
this.app = app;
this.plugin = plugin;
this.linkUpdater = new LinkUpdater(app);
}

/**
Expand Down Expand Up @@ -125,7 +128,20 @@ export class ArrangeHandler {
const { name } = await deduplicateNewName(attachName + "." + path.extname(link), attachPathFile);
debugLog("rearrangeAttachment - deduplicated name:", name);

await this.app.fileManager.renameFile(linkFile, path.join(attachPath, name));
const oldPath = linkFile.path;
const newPath = path.join(attachPath, name);

// 重命名文件
await this.app.fileManager.renameFile(linkFile, newPath);

// 手动更新链接引用,因为fileManager.renameFile在某些情况下不会自动更新链接
// 参考: https://github.com/trganda/obsidian-attachment-management/issues/46
try {
await this.linkUpdater.updateLinksForRenamedFile(oldPath, newPath);
debugLog(`Successfully updated links for renamed file: ${oldPath} -> ${newPath}`);
} catch (error) {
console.error(`Failed to update links for renamed file: ${oldPath} -> ${newPath}`, error);
}
}
}
}
Expand Down
16 changes: 14 additions & 2 deletions src/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ import { isExcluded } from "./exclude";
import { getExtensionOverrideSetting } from "./model/extensionOverride";
import { md5sum, isImage, isPastedImage } from "./utils";
import { saveOriginalName } from "./lib/originalStorage";
import { LinkUpdater } from "./lib/linkUpdater";

export class CreateHandler {
readonly plugin: Plugin;
readonly app: App;
readonly settings: AttachmentManagementPluginSettings;
readonly linkUpdater: LinkUpdater;

constructor(plugin: Plugin, settings: AttachmentManagementPluginSettings) {
this.plugin = plugin;
this.app = this.plugin.app;
this.app = plugin.app;
this.settings = settings;
this.linkUpdater = new LinkUpdater(plugin.app);
}

/**
Expand Down Expand Up @@ -85,13 +88,22 @@ export class CreateHandler {

const original = attach.basename;
const name = attach.name;
const oldPath = attach.path;

// this api will not update the link in markdown file automatically on `create` event
// forgive using to rename, refer: https://github.com/trganda/obsidian-attachment-management/issues/46
this.app.fileManager
.renameFile(attach, dst)
.then(() => {
.then(async () => {
new Notice(`Renamed ${name} to ${attachName}.`);

// 手动更新链接引用,因为fileManager.renameFile在create事件中不会自动更新链接
try {
await this.linkUpdater.updateLinksForRenamedFile(oldPath, dst);
debugLog(`Successfully updated links for created file: ${oldPath} -> ${dst}`);
} catch (error) {
console.error(`Failed to update links for created file: ${oldPath} -> ${dst}`, error);
}
})
.finally(() => {
// save origianl name in setting
Expand Down
115 changes: 115 additions & 0 deletions src/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { moment } from 'obsidian';

// 支持的语言类型
export type SupportedLanguage = 'en' | 'zh-cn';

// 翻译键值对接口
export interface TranslationMap {
[key: string]: string | TranslationMap;
}

// 当前语言设置
let currentLanguage: SupportedLanguage = 'en';

// 语言包存储
const translations: Record<SupportedLanguage, TranslationMap> = {
'en': {},
'zh-cn': {}
};

/**
* 设置当前语言
* @param language 语言代码
*/
export function setLanguage(language: SupportedLanguage): void {
currentLanguage = language;
}

/**
* 获取当前语言
* @returns 当前语言代码
*/
export function getCurrentLanguage(): SupportedLanguage {
return currentLanguage;
}

/**
* 注册语言包
* @param language 语言代码
* @param translationMap 翻译映射
*/
export function registerTranslations(language: SupportedLanguage, translationMap: TranslationMap): void {
translations[language] = { ...translations[language], ...translationMap };
}

/**
* 获取翻译文本
* @param key 翻译键,支持点分隔的嵌套键
* @param params 可选的参数对象,用于字符串插值
* @returns 翻译后的文本
*/
export function t(key: string, params?: Record<string, string | number>): string {
const keys = key.split('.');
let value: any = translations[currentLanguage];

// 遍历嵌套键
for (const k of keys) {
if (value && typeof value === 'object' && k in value) {
value = value[k];
} else {
// 如果当前语言没有找到,尝试使用英文作为后备
if (currentLanguage !== 'en') {
let fallbackValue: any = translations['en'];
for (const fk of keys) {
if (fallbackValue && typeof fallbackValue === 'object' && fk in fallbackValue) {
fallbackValue = fallbackValue[fk];
} else {
fallbackValue = key; // 最终后备:返回键本身
break;
}
}
value = fallbackValue;
} else {
value = key; // 返回键本身作为后备
}
break;
}
}

// 确保返回字符串
let result = typeof value === 'string' ? value : key;

// 处理参数插值
if (params) {
Object.entries(params).forEach(([paramKey, paramValue]) => {
result = result.replace(new RegExp(`\\{${paramKey}\\}`, 'g'), String(paramValue));
});
}

return result;
}

/**
* 根据系统语言自动检测语言设置
* @returns 检测到的语言代码
*/
export function detectLanguage(): SupportedLanguage {
const locale = moment.locale();

// 检测中文
if (locale.startsWith('zh')) {
return 'zh-cn';
}

// 默认返回英文
return 'en';
}

/**
* 初始化i18n系统
* @param language 可选的初始语言,如果不提供则自动检测
*/
export function initI18n(language?: SupportedLanguage): void {
const initialLanguage = language || detectLanguage();
setLanguage(initialLanguage);
}
44 changes: 44 additions & 0 deletions src/i18n/loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { registerTranslations, SupportedLanguage } from './index';
import { en } from './locales/en';
import { zhCn } from './locales/zh-cn';

/**
* 加载所有语言包
*/
export function loadAllTranslations(): void {
// 注册英文语言包
registerTranslations('en', en);

// 注册中文语言包
registerTranslations('zh-cn', zhCn);
}

/**
* 获取支持的语言列表
* @returns 支持的语言列表,包含代码和显示名称
*/
export function getSupportedLanguages(): Array<{ code: SupportedLanguage; name: string; nativeName: string }> {
return [
{
code: 'en',
name: 'English',
nativeName: 'English'
},
{
code: 'zh-cn',
name: 'Chinese (Simplified)',
nativeName: '简体中文'
}
];
}

/**
* 根据语言代码获取语言显示名称
* @param code 语言代码
* @returns 语言显示名称
*/
export function getLanguageName(code: SupportedLanguage): string {
const languages = getSupportedLanguages();
const language = languages.find(lang => lang.code === code);
return language ? language.nativeName : code;
}
Loading