diff --git a/.editorconfig b/.editorconfig
index dedc5722..3ad9d05b 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -292,3 +292,8 @@ indent_size = 2
end_of_line = lf
[*.{cmd,bat}]
end_of_line = crlf
+
+# YAML files
+[*.{yml,yaml}]
+indent_size = 2
+end_of_line = lf
diff --git a/.github/scripts/localization-check.js b/.github/scripts/localization-check.js
new file mode 100644
index 00000000..4841e3d6
--- /dev/null
+++ b/.github/scripts/localization-check.js
@@ -0,0 +1,59 @@
+const fs = require('fs-extra');
+const path = require('path');
+const xml2js = require('xml2js');
+
+const repoRoot = path.join(__dirname, '../../');
+const localesDir = path.join(repoRoot, 'src/Resources/Locales');
+const enUSFile = path.join(localesDir, 'en_US.axaml');
+const outputFile = path.join(repoRoot, 'TRANSLATION.md');
+const readmeFile = path.join(repoRoot, 'README.md');
+
+const parser = new xml2js.Parser();
+
+async function parseXml(filePath) {
+ const data = await fs.readFile(filePath);
+ return parser.parseStringPromise(data);
+}
+
+async function calculateTranslationRate() {
+ const enUSData = await parseXml(enUSFile);
+ const enUSKeys = new Set(enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
+
+ const translationRates = [];
+ const badges = [];
+
+ const files = (await fs.readdir(localesDir)).filter(file => file !== 'en_US.axaml' && file.endsWith('.axaml'));
+
+ // Add en_US badge first
+ badges.push(``);
+
+ for (const file of files) {
+ const filePath = path.join(localesDir, file);
+ const localeData = await parseXml(filePath);
+ const localeKeys = new Set(localeData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
+
+ const missingKeys = [...enUSKeys].filter(key => !localeKeys.has(key));
+ const translationRate = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100;
+
+ translationRates.push(`### ${file}: ${translationRate.toFixed(2)}%\n`);
+ translationRates.push(`\nMissing Keys
\n\n${missingKeys.map(key => `- ${key}`).join('\n')}\n\n `);
+
+ // Add badges
+ const locale = file.replace('.axaml', '').replace('_', '__');
+ const badgeColor = translationRate === 100 ? 'brightgreen' : translationRate >= 75 ? 'yellow' : 'red';
+ badges.push(`}%25-${badgeColor})`);
+ }
+
+ console.log(translationRates.join('\n\n'));
+
+ await fs.writeFile(outputFile, translationRates.join('\n\n'), 'utf8');
+
+ // Update README.md
+ let readmeContent = await fs.readFile(readmeFile, 'utf8');
+ const badgeSection = `## Translation Status\n\n${badges.join(' ')}`;
+ console.log(badgeSection);
+ readmeContent = readmeContent.replace(/## Translation Status\n\n.*\n\n/, badgeSection + '\n\n');
+ await fs.writeFile(readmeFile, readmeContent, 'utf8');
+}
+
+calculateTranslationRate().catch(err => console.error(err));
diff --git a/.github/workflows/localization-check.yml b/.github/workflows/localization-check.yml
new file mode 100644
index 00000000..d5ebe8c3
--- /dev/null
+++ b/.github/workflows/localization-check.yml
@@ -0,0 +1,42 @@
+name: Localization Check
+on:
+ push:
+ branches: [ develop ]
+ paths:
+ - 'src/Resources/Locales/**'
+ - 'README.md'
+ workflow_dispatch:
+ workflow_call:
+
+jobs:
+ localization-check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20.x'
+
+ - name: Install dependencies
+ run: npm install fs-extra@11.2.0 path@0.12.7 xml2js@0.6.2
+
+ - name: Run localization check
+ run: node .github/scripts/localization-check.js
+
+ - name: Commit changes
+ run: |
+ git config --global user.name 'github-actions[bot]'
+ git config --global user.email 'github-actions[bot]@users.noreply.github.com'
+ if [ -n "$(git status --porcelain)" ]; then
+ git add README.md TRANSLATION.md
+ git commit -m 'doc: Update translation status and missing keys'
+ git push
+ else
+ echo "No changes to commit"
+ fi
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 95bde92d..0c66b11e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,10 @@ ehthumbs_vista.db
bin/
obj/
+# ignore ci node files
+node_modules/
+package.json
+package-lock.json
build/resources/
build/SourceGit/
@@ -32,4 +36,4 @@ build/*.tar.gz
build/*.deb
build/*.rpm
build/*.AppImage
-SourceGit.app/
\ No newline at end of file
+SourceGit.app/
diff --git a/README.md b/README.md
index 62427db4..31532e01 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,10 @@
> [!WARNING]
> **Linux** only tested on **Debian 12** on both **X11** & **Wayland**.
+## Translation Status
+
+      
+
## How to Use
**To use this tool, you need to install Git(>=2.23.0) first.**
diff --git a/SourceGit.sln b/SourceGit.sln
index 10c94e36..abd42aee 100644
--- a/SourceGit.sln
+++ b/SourceGit.sln
@@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\ci.yml = .github\workflows\ci.yml
.github\workflows\package.yml = .github\workflows\package.yml
.github\workflows\release.yml = .github\workflows\release.yml
+ .github\workflows\localization-check.yml = .github\workflows\localization-check.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}"
diff --git a/TRANSLATION.md b/TRANSLATION.md
new file mode 100644
index 00000000..2cac2dec
--- /dev/null
+++ b/TRANSLATION.md
@@ -0,0 +1,184 @@
+### de_DE.axaml: 98.95%
+
+
+
+Missing Keys
+
+- Text.Configure.Git.EnableSignOff
+- Text.Configure.IssueTracker.AddSampleGitLabIssue
+- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
+- Text.Preference.Advanced
+- Text.Preference.AI.AnalyzeDiffPrompt
+- Text.Preference.AI.GenerateSubjectPrompt
+- Text.WorkingCopy.ConfirmCommitWithoutFiles
+
+
+
+### fr_FR.axaml: 90.38%
+
+
+
+Missing Keys
+
+- Text.About.Chart
+- Text.AIAssistant
+- Text.AIAssistant.Tip
+- Text.CherryPick.AppendSourceToMessage
+- Text.CherryPick.Mainline
+- Text.CherryPick.Mainline.Tips
+- Text.CommitCM.CherryPickMultiple
+- Text.CommitCM.SquashCommitsSinceThis
+- Text.CommitDetail.Info.WebLinks
+- Text.Configure.Git.DefaultRemote
+- Text.Configure.Git.EnableSignOff
+- Text.Configure.IssueTracker.AddSampleGitLabIssue
+- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
+- Text.ConfigureWorkspace
+- Text.ConfigureWorkspace.Color
+- Text.ConfigureWorkspace.Restore
+- Text.ConventionalCommit
+- Text.ConventionalCommit.BreakingChanges
+- Text.ConventionalCommit.ClosedIssue
+- Text.ConventionalCommit.Detail
+- Text.ConventionalCommit.Scope
+- Text.ConventionalCommit.ShortDescription
+- Text.ConventionalCommit.Type
+- Text.Diff.IgnoreWhitespace
+- Text.Discard.IncludeIgnored
+- Text.FileHistory.FileChange
+- Text.GitLFS.Locks.OnlyMine
+- Text.Histories.Header.AuthorTime
+- Text.Histories.Tips
+- Text.Histories.Tips.MacOS
+- Text.Histories.Tips.Prefix
+- Text.Hotkeys.Repo.CommitWithAutoStage
+- Text.Hotkeys.Repo.DiscardSelected
+- Text.MoveRepositoryNode
+- Text.MoveRepositoryNode.Target
+- Text.Preference.Advanced
+- Text.Preference.AI
+- Text.Preference.AI.AnalyzeDiffPrompt
+- Text.Preference.AI.ApiKey
+- Text.Preference.AI.GenerateSubjectPrompt
+- Text.Preference.AI.Model
+- Text.Preference.AI.Server
+- Text.Preference.General.ShowAuthorTime
+- Text.Preference.Integration
+- Text.Preference.Shell
+- Text.Preference.Shell.Type
+- Text.Preference.Shell.Path
+- Text.Repository.AutoFetching
+- Text.Repository.EnableReflog
+- Text.Repository.Search.InCurrentBranch
+- Text.ScanRepositories
+- Text.ScanRepositories.RootDir
+- Text.Squash.Into
+- Text.Stash.OnlyStagedChanges
+- Text.Stash.TipForSelectedFiles
+- Text.Statistics.Overview
+- Text.TagCM.CopyMessage
+- Text.Welcome.Move
+- Text.Welcome.ScanDefaultCloneDir
+- Text.WorkingCopy.CommitTip
+- Text.WorkingCopy.CommitWithAutoStage
+- Text.WorkingCopy.ConfirmCommitWithoutFiles
+- Text.Workspace
+- Text.Workspace.Configure
+
+
+
+### pt_BR.axaml: 93.53%
+
+
+
+Missing Keys
+
+- Text.About.Chart
+- Text.AIAssistant
+- Text.AIAssistant.Tip
+- Text.CherryPick.AppendSourceToMessage
+- Text.CherryPick.Mainline
+- Text.CherryPick.Mainline.Tips
+- Text.CommitCM.CherryPickMultiple
+- Text.CommitCM.SquashCommitsSinceThis
+- Text.CommitDetail.Info.ContainsIn
+- Text.CommitDetail.Info.ContainsIn.Title
+- Text.CommitDetail.Info.WebLinks
+- Text.Configure.Git.DefaultRemote
+- Text.Configure.Git.EnableSignOff
+- Text.Configure.IssueTracker.AddSampleGitLabIssue
+- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
+- Text.ConfigureWorkspace
+- Text.ConfigureWorkspace.Color
+- Text.ConfigureWorkspace.Restore
+- Text.ConventionalCommit
+- Text.ConventionalCommit.BreakingChanges
+- Text.ConventionalCommit.ClosedIssue
+- Text.ConventionalCommit.Detail
+- Text.ConventionalCommit.Scope
+- Text.ConventionalCommit.ShortDescription
+- Text.ConventionalCommit.Type
+- Text.CopyAllText
+- Text.Discard.IncludeIgnored
+- Text.FileHistory.FileContent
+- Text.FileHistory.FileChange
+- Text.GitLFS.Locks.OnlyMine
+- Text.MoveRepositoryNode
+- Text.MoveRepositoryNode.Target
+- Text.Preference.Advanced
+- Text.Push.CheckSubmodules
+- Text.Squash.Into
+- Text.Stash.OnlyStagedChanges
+- Text.Stash.TipForSelectedFiles
+- Text.Statistics.Overview
+- Text.TagCM.CopyMessage
+- Text.WorkingCopy.Staged.UnstageAll
+- Text.WorkingCopy.Unstaged
+- Text.WorkingCopy.Unstaged.Stage
+- Text.WorkingCopy.Unstaged.StageAll
+
+
+
+### ru_RU.axaml: 98.80%
+
+
+
+Missing Keys
+
+- Text.Configure.Git.EnableSignOff
+- Text.Configure.IssueTracker.AddSampleGitLabIssue
+- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
+- Text.Preference.Advanced
+- Text.Preference.AI.AnalyzeDiffPrompt
+- Text.Preference.AI.GenerateSubjectPrompt
+- Text.Repository.EnableReflog
+- Text.WorkingCopy.ConfirmCommitWithoutFiles
+
+
+
+### zh_CN.axaml: 98.95%
+
+
+
+Missing Keys
+
+- Text.Preference.AI
+- Text.Preference.AI.AnalyzeDiffPrompt
+- Text.Preference.AI.ApiKey
+- Text.Preference.AI.GenerateSubjectPrompt
+- Text.Preference.AI.Model
+- Text.Preference.AI.Server
+- Text.RemoteCM.Prune.Target
+
+
+
+### zh_TW.axaml: 99.70%
+
+
+
+Missing Keys
+
+- Text.Preference.AI.AnalyzeDiffPrompt
+- Text.Preference.AI.GenerateSubjectPrompt
+
+