Repository: SakiRinn/LiveCaptions-Translator
Pull request #109: Support baidu translation api
Author: login: Xzonn
Created: 2025-04-17T11:46:30Z | Merged: 2025-04-18T18:48:59Z

Added support for [Baidu Translate](https://fanyi-api.baidu.com/product/11). For verified individual accounts, Baidu Translate provides a monthly free quota of 1 million characters. The implementation is based on the existing code for Youdao Translate, with necessary adjustments made to adapt it for Baidu's API.


Diff:
diff --git a/src/models/TranslateAPIConfig.cs b/src/models/TranslateAPIConfig.cs
index 1d4e13b..0202c4c 100644
--- a/src/models/TranslateAPIConfig.cs
+++ b/src/models/TranslateAPIConfig.cs
@@ -306,4 +306,67 @@ public class Response
             public string result { get; set; }
         }
     }
+
+    public class BaiduConfig : TranslateAPIConfig
+    {
+        public class TransResult
+        {
+            public string src {  get; set; }
+            public string dst { get; set; }
+        }
+
+        public class TranslationResult
+        {
+            public string error_code { get; set; }
+            public string from { get; set; }
+            public string to { get; set; }
+            public List<TransResult> trans_result { get; set; }
+        }
+
+        [JsonIgnore]
+        public override Dictionary<string, string> SupportedLanguages { get; } = new()
+        {
+            { "zh-CN", "zh" }, 
+            { "zh-TW", "cht" }, 
+            { "en-US", "en" },      
+            { "ja-JP", "jp" },      
+            { "ko-KR", "kor" },     
+            { "fr-FR", "fra" },      
+            { "th-TH", "th" },
+        };
+
+        private string appId = "";
+        private string appSecret = "";
+        private string apiUrl = "https://fanyi-api.baidu.com/api/trans/vip/translate";
+
+        public string AppId
+        {
+            get => appId;
+            set
+            {
+                appId = value;
+                OnPropertyChanged("AppId");
+            }
+        }
+
+        public string AppSecret
+        {
+            get => appSecret;
+            set
+            {
+                appSecret = value;
+                OnPropertyChanged("AppSecret");
+            }
+        }
+
+        public string ApiUrl
+        {
+            get => apiUrl;
+            set
+            {
+                apiUrl = value;
+                OnPropertyChanged("ApiUrl");
+            }
+        }
+    }
 }
diff --git a/src/utils/TranslateAPI.cs b/src/utils/TranslateAPI.cs
index b267172..fa54422 100644
--- a/src/utils/TranslateAPI.cs
+++ b/src/utils/TranslateAPI.cs
@@ -25,6 +25,7 @@ public static readonly Dictionary<string, Func<string, CancellationToken, Task<s
             { "OpenRouter", OpenRouter },
             { "Youdao", Youdao },
             { "MTranServer", MTranServer },
+            { "Baidu", Baidu },
         };
 
         public static Func<string, CancellationToken, Task<string>> TranslateFunction
@@ -446,6 +447,62 @@ public static async Task<string> MTranServer(string text, CancellationToken toke
             else
                 return $"[Translation Failed] HTTP Error - {response.StatusCode}";
         }
+
+        public static async Task<string> Baidu(string text, CancellationToken token = default)
+        {
+            var config = Translator.Setting.CurrentAPIConfig as BaiduConfig;
+            string language = config.SupportedLanguages.TryGetValue(Translator.Setting.TargetLanguage, out var langValue)
+                ? langValue : Translator.Setting.TargetLanguage;
+
+            string salt = DateTime.Now.Millisecond.ToString();
+            string sign = BitConverter.ToString(
+                MD5.Create().ComputeHash(
+                    Encoding.UTF8.GetBytes($"{config.AppId}{text}{salt}{config.AppSecret}"))).Replace("-", "").ToLower();
+
+            var parameters = new Dictionary<string, string>
+            {
+                ["q"] = text,
+                ["from"] = "auto",
+                ["to"] = language,
+                ["appid"] = config.AppId,
+                ["salt"] = salt,
+                ["sign"] = sign
+            };
+
+            var content = new FormUrlEncodedContent(parameters);
+            client.DefaultRequestHeaders.Clear();
+
+            HttpResponseMessage response;
+            try
+            {
+                response = await client.PostAsync(config.ApiUrl, content, token);
+            }
+            catch (OperationCanceledException ex)
+            {
+                if (ex.Message.StartsWith("The request"))
+                    return $"[Translation Failed] {ex.Message}";
+                return string.Empty;
+            }
+            catch (Exception ex)
+            {
+                return $"[Translation Failed] {ex.Message}";
+            }
+
+            if (response.IsSuccessStatusCode)
+            {
+                string responseString = await response.Content.ReadAsStringAsync();
+                var responseObj = JsonSerializer.Deserialize<BaiduConfig.TranslationResult>(responseString);
+
+                if (responseObj.error_code is not null && responseObj.error_code != "0")
+                    return $"[Translation Failed] Baidu Error {responseObj.error_code}";
+
+                return responseObj.trans_result?.FirstOrDefault()?.dst ?? "[Translation Failed] No content";
+            }
+            else
+            {
+                return $"[Translation Failed] HTTP Error - {response.StatusCode}";
+            }
+        }
     }
 
     public class ConfigDictConverter : JsonConverter<Dictionary<string, TranslateAPIConfig>>
diff --git a/src/windows/SettingWindow.xaml b/src/windows/SettingWindow.xaml
index ed0ec8d..92e0b62 100644
--- a/src/windows/SettingWindow.xaml
+++ b/src/windows/SettingWindow.xaml
@@ -143,6 +143,19 @@
                             <TextBlock VerticalAlignment="Center" Text="MTranServer" />
                         </StackPanel>
                     </ui:Button>
+
+					<ui:Button
+                        x:Name="BaiduButton"
+                        Margin="10,5,10,0"
+                        Padding="10"
+                        HorizontalAlignment="Stretch"
+                        HorizontalContentAlignment="Left"
+                        Click="NavigationButton_Click"
+                        Tag="Baidu">
+						<StackPanel Orientation="Horizontal">
+							<TextBlock VerticalAlignment="Center" Text="Baidu" />
+						</StackPanel>
+					</ui:Button>
                 </StackPanel>
             </Border>
             <ScrollViewer
@@ -557,6 +570,66 @@
                                 </Grid>
                             </ui:Card>
                         </StackPanel>
+
+						<StackPanel x:Name="BaiduSection" Margin="20">
+							<TextBlock
+                                Margin="10"
+                                FontSize="20"
+                                FontWeight="Medium"
+                                Text="Baidu" />
+							<ui:Card Padding="15">
+								<Grid x:Name="BaiduGrid">
+									<Grid.RowDefinitions>
+										<RowDefinition Height="Auto" />
+										<RowDefinition Height="Auto" />
+									</Grid.RowDefinitions>
+									<Grid.ColumnDefinitions>
+										<ColumnDefinition Width="Auto" />
+										<ColumnDefinition Width="Auto" />
+									</Grid.ColumnDefinitions>
+
+									<StackPanel
+                                        Grid.Row="0"
+                                        Grid.Column="0"
+                                        Margin="15,0,0,0"
+                                        Orientation="Vertical">
+										<ui:TextBlock Margin="2.5,0,0,5" Text="API Url" />
+										<ui:TextBox
+                                            Width="130"
+                                            Height="30"
+                                            Padding="10,4,10,7"
+                                            FontSize="13.3"
+                                            Text="{Binding Configs[Baidu].ApiUrl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
+									</StackPanel>
+									<StackPanel
+                                        Grid.Row="1"
+                                        Grid.Column="0"
+                                        Margin="15,10,0,0"
+                                        Orientation="Vertical">
+										<ui:TextBlock Margin="2.5,0,0,5" Text="APP ID" />
+										<ui:TextBox
+                                            Width="130"
+                                            Height="30"
+                                            Padding="10,4,10,7"
+                                            FontSize="13.3"
+                                            Text="{Binding Configs[Baidu].AppId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
+									</StackPanel>
+									<StackPanel
+                                        Grid.Row="1"
+                                        Grid.Column="1"
+                                        Margin="15,10,0,0"
+                                        Orientation="Vertical">
+										<ui:TextBlock Margin="2.5,0,0,5" Text="APP Secret" />
+										<ui:TextBox
+                                            Width="200"
+                                            Height="30"
+                                            Padding="10,4,10,7"
+                                            FontSize="13.3"
+                                            Text="{Binding Configs[Baidu].AppSecret, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
+									</StackPanel>
+								</Grid>
+							</ui:Card>
+						</StackPanel>
                     </StackPanel>
                 </StackPanel>
             </ScrollViewer>
diff --git a/src/windows/SettingWindow.xaml.cs b/src/windows/SettingWindow.xaml.cs
index 50e98b6..4ce7c9a 100644
--- a/src/windows/SettingWindow.xaml.cs
+++ b/src/windows/SettingWindow.xaml.cs
@@ -23,6 +23,7 @@ public SettingWindow()
             DeepLButton.Background = new SolidColorBrush(Colors.Transparent);
             YoudaoButton.Background = new SolidColorBrush(Colors.Transparent);
             MTranServerButton.Background = new SolidColorBrush(Colors.Transparent);
+            BaiduButton.Background = new SolidColorBrush(Colors.Transparent);
 
             Loaded += (sender, args) =>
             {
@@ -45,7 +46,8 @@ private void InitializeSectionReferences()
                 { "OpenRouter", OpenRouterSection },
                 { "DeepL", DeepLSection },
                 { "Youdao", YoudaoSection },
-                { "MTranServer", MTranServerSection }
+                { "MTranServer", MTranServerSection },
+                { "Baidu", BaiduSection },
             };
         }
 


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds support for Baidu Translate, enabling users to leverage Baidu’s translation API with a monthly free quota.  
- Updated the settings UI to include a Baidu translation option.  
- Added Baidu translation handling in the API layer with appropriate MD5 signing and error handling.  
- Introduced a new BaiduConfig class for configuration management.

### Reviewed Changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

| File                                      | Description                                                    |
| ----------------------------------------- | -------------------------------------------------------------- |
| src/windows/SettingWindow.xaml.cs         | Added UI support for Baidu translation setting and button.     |
| src/utils/TranslateAPI.cs                 | Integrated Baidu API call with MD5 signing and error handling.   |
| src/models/TranslateAPIConfig.cs          | Defined BaiduConfig with required properties and supported languages.  |

<details>
<summary>Files not reviewed (1)</summary>

* **src/windows/SettingWindow.xaml**: Language not supported
</details>



state: COMMENTED
author_association: NONE
submitted_at: 2025-04-18T08:34:21Z

Review comments:
diff_hunk: @@ -446,6 +447,62 @@ public static async Task<string> MTranServer(string text, CancellationToken toke
             else
                 return $"[Translation Failed] HTTP Error - {response.StatusCode}";
         }
+
+        public static async Task<string> Baidu(string text, CancellationToken token = default)
+        {
+            var config = Translator.Setting.CurrentAPIConfig as BaiduConfig;
+            string language = config.SupportedLanguages.TryGetValue(Translator.Setting.TargetLanguage, out var langValue)
+                ? langValue : Translator.Setting.TargetLanguage;
+
+            string salt = DateTime.Now.Millisecond.ToString();
path: src/utils/TranslateAPI.cs
user: login: Copilot
body: Using DateTime.Now.Millisecond for salt generation may lead to collisions and insufficient randomness; consider using a more robust random generator for salt.
```suggestion
            byte[] saltBytes = new byte[16];
            RandomNumberGenerator.Fill(saltBytes);
            string salt = BitConverter.ToString(saltBytes).Replace("-", "").ToLower();
```
created_at: 2025-04-18T08:34:20Z
author_association: NONE
diff_hunk: @@ -446,6 +447,62 @@ public static async Task<string> MTranServer(string text, CancellationToken toke
             else
                 return $"[Translation Failed] HTTP Error - {response.StatusCode}";
         }
+
+        public static async Task<string> Baidu(string text, CancellationToken token = default)
+        {
+            var config = Translator.Setting.CurrentAPIConfig as BaiduConfig;
+            string language = config.SupportedLanguages.TryGetValue(Translator.Setting.TargetLanguage, out var langValue)
+                ? langValue : Translator.Setting.TargetLanguage;
+
+            string salt = DateTime.Now.Millisecond.ToString();
+            string sign = BitConverter.ToString(
+                MD5.Create().ComputeHash(
+                    Encoding.UTF8.GetBytes($"{config.AppId}{text}{salt}{config.AppSecret}"))).Replace("-", "").ToLower();
+
+            var parameters = new Dictionary<string, string>
+            {
+                ["q"] = text,
+                ["from"] = "auto",
+                ["to"] = language,
+                ["appid"] = config.AppId,
+                ["salt"] = salt,
+                ["sign"] = sign
+            };
+
+            var content = new FormUrlEncodedContent(parameters);
+            client.DefaultRequestHeaders.Clear();
+
+            HttpResponseMessage response;
+            try
+            {
+                response = await client.PostAsync(config.ApiUrl, content, token);
+            }
+            catch (OperationCanceledException ex)
+            {
+                if (ex.Message.StartsWith("The request"))
+                    return $"[Translation Failed] {ex.Message}";
+                return string.Empty;
path: src/utils/TranslateAPI.cs
user: login: Copilot
body: Returning an empty string on OperationCanceledException may hide the cancellation status; consider propagating the cancellation exception or returning a more informative error message.
```suggestion
                return "[Translation Canceled] The operation was canceled.";
```
created_at: 2025-04-18T08:34:21Z
author_association: NONE

PR comments:
user: login: A-nony-mous
created_at: 2025-04-18T08:39:16Z
body: 晦气
![image](https://github.com/user-attachments/assets/c69ef21f-d75f-4d94-ab0a-b4bdd442ca98)

author_association: COLLABORATOR
user: login: A-nony-mous
created_at: 2025-04-18T08:40:58Z
body: 建议：
1，格式化你提交的代码
![image](https://github.com/user-attachments/assets/f1b212fb-4aef-4c82-aaab-8279b2d1e2ca)
2，commit message用[约定式提交](https://www.conventionalcommits.org/zh-hans/v1.0.0/#%e7%ba%a6%e5%ae%9a%e5%bc%8f%e6%8f%90%e4%ba%a4%e8%a7%84%e8%8c%83)
3，看一下copilot的输出，虽然是不痛不痒的

cc @SakiRinn 
author_association: COLLABORATOR
user: login: SakiRinn
created_at: 2025-04-18T18:44:29Z
body: > 建议：
> 1，格式化你提交的代码
> ![image](https://github.com/user-attachments/assets/f1b212fb-4aef-4c82-aaab-8279b2d1e2ca)
> 2，commit message用[约定式提交](https://www.conventionalcommits.org/zh-hans/v1.0.0/#%e7%ba%a6%e5%ae%9a%e5%bc%8f%e6%8f%90%e4%ba%a4%e8%a7%84%e8%8c%83)
> 3，看一下copilot的输出，虽然是不痛不痒的
> 
> cc @SakiRinn 

没啥大问题
author_association: OWNER