Skip to content

Commit b456319

Browse files
Read the user's preferred language from the OS (#382)
1 parent ceefc01 commit b456319

File tree

10 files changed

+115
-1
lines changed

10 files changed

+115
-1
lines changed

app/MindWork AI Studio/Layout/MainLayout.razor.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ protected override async Task OnInitializedAsync()
7979
SettingsManager.DataDirectory = dataDir;
8080
Directory.CreateDirectory(SettingsManager.DataDirectory);
8181

82+
//
83+
// Read the user language from Rust:
84+
//
85+
var userLanguage = await this.RustService.ReadUserLanguage();
86+
this.Logger.LogInformation($"The user language is: '{userLanguage}'");
87+
8288
// Ensure that all settings are loaded:
8389
await this.SettingsManager.LoadSettings();
8490

app/MindWork AI Studio/Pages/About.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
<ThirdPartyComponent Name="file-format" Developer="Mickaël Malécot & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mmalecot/file-format/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/mmalecot/file-format" UseCase="This library is used to determine the file type of a file. This is necessary, e.g., when we want to stream a file."/>
114114
<ThirdPartyComponent Name="calamine" Developer="Johann Tuffe, Joel Natividad, Eric Jolibois, Dmitriy & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tafia/calamine/blob/master/LICENSE-MIT.md" RepositoryUrl="https://github.com/tafia/calamine" UseCase="This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat."/>
115115
<ThirdPartyComponent Name="pdfium-render" Developer="Alastair Carey, Dorian Rudolph & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/ajrcarey/pdfium-render/blob/master/LICENSE.md" RepositoryUrl="https://github.com/ajrcarey/pdfium-render" UseCase="This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat."/>
116+
<ThirdPartyComponent Name="sys-locale" Developer="1Password Team, ComplexSpaces & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/1Password/sys-locale/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/1Password/sys-locale" UseCase="This library is used to determine the language of the operating system. This is necessary to set the language of the user interface."/>
116117
<ThirdPartyComponent Name="Lua-CSharp" Developer="Yusuke Nakada & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/nuskey8/Lua-CSharp/blob/main/LICENSE" RepositoryUrl="https://github.com/nuskey8/Lua-CSharp" UseCase="We use Lua as the language for plugins. Lua-CSharp lets Lua scripts communicate with AI Studio and vice versa. Thank you, Yusuke Nakada, for this great library." />
117118
<ThirdPartyComponent Name="HtmlAgilityPack" Developer="ZZZ Projects & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE" RepositoryUrl="https://github.com/zzzprojects/html-agility-pack" UseCase="We use the HtmlAgilityPack to extract content from the web. This is necessary, e.g., when you provide a URL as input for an assistant."/>
118119
<ThirdPartyComponent Name="ReverseMarkdown" Developer="Babu Annamalai & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mysticmind/reversemarkdown-net/blob/master/LICENSE" RepositoryUrl="https://github.com/mysticmind/reversemarkdown-net" UseCase="This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant."/>

app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ IS_MAINTAINED = true
4040
-- When the plugin is deprecated, this message will be shown to users:
4141
DEPRECATION_MESSAGE = ""
4242

43+
-- The IETF BCP 47 tag for the language. It's the ISO 639 language
44+
-- code followed by the ISO 3166-1 country code:
45+
IETF_TAG = "de-DE"
46+
4347
UI_TEXT_CONTENT = {
4448
HOME = CONTENT_HOME,
4549
}

app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ IS_MAINTAINED = true
4040
-- When the plugin is deprecated, this message will be shown to users:
4141
DEPRECATION_MESSAGE = ""
4242

43+
-- The IETF BCP 47 tag for the language. It's the ISO 639 language
44+
-- code followed by the ISO 3166-1 country code:
45+
IETF_TAG = "en-US"
46+
4347
UI_TEXT_CONTENT = {
4448
HOME = CONTENT_HOME,
4549
}

app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
66
{
77
private readonly Dictionary<string, string> content = [];
88
private readonly List<ILanguagePlugin> otherLanguagePlugins = [];
9+
private readonly string langCultureTag;
910

1011
private ILanguagePlugin? baseLanguage;
1112

1213
public PluginLanguage(bool isInternal, LuaState state, PluginType type) : base(isInternal, state, type)
1314
{
14-
if (this.TryInitUITextContent(out var issue, out var readContent))
15+
if(!this.TryInitIETFTag(out var issue, out this.langCultureTag))
16+
this.pluginIssues.Add(issue);
17+
18+
if (this.TryInitUITextContent(out issue, out var readContent))
1519
this.content = readContent;
1620
else
1721
this.pluginIssues.Add(issue);
@@ -65,4 +69,62 @@ public bool TryGetText(string key, out string value)
6569
value = string.Empty;
6670
return false;
6771
}
72+
73+
/// <summary>
74+
/// Tries to initialize the IETF tag.
75+
/// </summary>
76+
/// <param name="message">The error message, when the IETF tag could not be read.</param>
77+
/// <param name="readLangCultureTag">The read IETF tag.</param>
78+
/// <returns>True, when the IETF tag could be read, false otherwise.</returns>
79+
private bool TryInitIETFTag(out string message, out string readLangCultureTag)
80+
{
81+
if (!this.state.Environment["IETF_TAG"].TryRead(out readLangCultureTag))
82+
{
83+
message = "The field IETF_TAG does not exist or is not a valid string.";
84+
readLangCultureTag = string.Empty;
85+
return false;
86+
}
87+
88+
if (string.IsNullOrWhiteSpace(readLangCultureTag))
89+
{
90+
message = "The field IETF_TAG is empty. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.";
91+
readLangCultureTag = string.Empty;
92+
return false;
93+
}
94+
95+
if (readLangCultureTag.Length != 5)
96+
{
97+
message = "The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.";
98+
readLangCultureTag = string.Empty;
99+
return false;
100+
}
101+
102+
if (readLangCultureTag[2] != '-')
103+
{
104+
message = "The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.";
105+
readLangCultureTag = string.Empty;
106+
return false;
107+
}
108+
109+
// Check the first part consists of only lower case letters:
110+
for (var i = 0; i < 2; i++)
111+
if (!char.IsLower(readLangCultureTag[i]))
112+
{
113+
message = "The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.";
114+
readLangCultureTag = string.Empty;
115+
return false;
116+
}
117+
118+
// Check the second part consists of only upper case letters:
119+
for (var i = 3; i < 5; i++)
120+
if (!char.IsUpper(readLangCultureTag[i]))
121+
{
122+
message = "The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.";
123+
readLangCultureTag = string.Empty;
124+
return false;
125+
}
126+
127+
message = string.Empty;
128+
return true;
129+
}
68130
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace AIStudio.Tools.Services;
2+
3+
public sealed partial class RustService
4+
{
5+
public async Task<string> ReadUserLanguage()
6+
{
7+
var response = await this.http.GetAsync("/system/language");
8+
if (!response.IsSuccessStatusCode)
9+
{
10+
this.logger!.LogError($"Failed to read the user language from Rust: '{response.StatusCode}'");
11+
return string.Empty;
12+
}
13+
14+
return await response.Content.ReadAsStringAsync();
15+
}
16+
}

runtime/Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

runtime/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ rcgen = { version = "0.13.2", features = ["pem"] }
3636
file-format = "0.26.0"
3737
calamine = "0.26.1"
3838
pdfium-render = "0.8.29"
39+
sys-locale = "0.3.2"
3940

4041
# Fixes security vulnerability downstream, where the upstream is not fixed yet:
4142
url = "2.5"

runtime/src/environment.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::sync::OnceLock;
22
use rocket::get;
3+
use sys_locale::get_locale;
34
use crate::api_token::APIToken;
45

56
/// The data directory where the application stores its data.
@@ -34,4 +35,12 @@ pub fn is_dev() -> bool {
3435
/// Returns true if the application is running in production mode.
3536
pub fn is_prod() -> bool {
3637
!is_dev()
38+
}
39+
40+
#[get("/system/language")]
41+
pub fn read_user_language(_token: APIToken) -> String {
42+
get_locale().unwrap_or_else(|| {
43+
log::warn!("Could not determine the system language. Use default 'en-US'.");
44+
String::from("en-US")
45+
})
3746
}

runtime/src/runtime_api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ pub fn start_runtime_api() {
7777
crate::secret::delete_secret,
7878
crate::environment::get_data_directory,
7979
crate::environment::get_config_directory,
80+
crate::environment::read_user_language,
8081
crate::file_data::extract_data,
8182
crate::log::get_log_paths,
8283
])

0 commit comments

Comments
 (0)