Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(i18n) Provide translation strings for Registry #604

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
6 changes: 6 additions & 0 deletions registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ name = "registry"
version = "1.0.0"
edition = "2021"

[package.metadata.i18n]
available-locales = ["en"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use en-us for the locale

default-locale = "en"
load-path = "locales"

[profile.release]
strip = true
# optimize for size
Expand All @@ -14,6 +19,7 @@ lto = true
clap = { version = "4.5", features = ["derive"] }
crossterm = "0.28"
registry = "1.2"
rust-i18n = "3"
schemars = "0.8"
serde = "1.0"
serde_json = "1.0"
Expand Down
87 changes: 87 additions & 0 deletions registry/locales/cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
_version: 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe Windows resources are typically one folder/file per locale, so we may want to stick with version 1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not strictly opposed to that convention, especially if it's more familiar to contributors—I went with v2 because of how I was organizing the translations and believe that they'll be easier to maintain compared to replicating the structure across each file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other thing to consider is that I've been using toml and organizing it by source files and keeping it in order with the source file to make it a bit easier to map later

cli:
about:
en: Manage state of Windows registry
# config command help
config:
about:
en: Manage registry configuration.
args:
input:
help:
en: The registry JSON input.
what_if:
help:
en: Run as a what-if operation instead of applying the registry configuration.
get:
about:
en: Retrieve registry configuration.
set:
about:
en: Apply registry configuration.
delete:
about:
en: Delete registry configuration.
# query command help
query:
about:
en: Query a registry key or value.
args:
key_path:
help:
en: The registry key path to query.
value_name:
help:
en: The name of the value to query.
recurse:
help:
en: Recursively query subkeys.
# set command help
set:
about:
en: Set a registry key or value.
args:
key_path:
help:
en: The registry key path to set.
value:
help:
en: The value to set.
# remove command help
remove:
about:
en: Remove a registry key or value.
args:
key_path:
help:
en: The registry key path to remove.
value_name:
help:
en: The name of the value to remove.
recurse:
help:
en: Recursively remove subkeys.
# find command help
find:
about:
en: Find a registry key or value.
args:
key_path:
help:
en: The registry key path to start find.
find:
help:
en: The string to find.
recurse:
help:
en: Recursively find.
keys_only:
help:
en: Only find keys.
values_only:
help:
en: Only find values.
# schema command help
schema:
about:
en: Retrieve JSON schema.
35 changes: 35 additions & 0 deletions registry/locales/errors.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
_version: 2
errors:
InvalidHive:
en: Invalid hive
Json:
en: JSON
Registry:
en: Registry
RegistryKey:
en: Registry key
RegistryKeyNotFound:
en: Registry key not found
RegistryValue:
en: Registry value
Utf16Conversion:
en: UTF-16 conversion failed due to interior NULL values for
UnsupportedValueDataType:
en: Unsupported registry value data type
# internal, not an enum
eprint:
tracing_init:
en: Unable to set global default tracing subscriber. Tracing is diabled.
debug:
attach:
en: attach debugger to pid %{pid} and press any key to continue
event:
read:
en: "Error: Failed to read event: %{err}"
unexpexted:
en: "Unexpected event: %{e:?}"
remove:
key_not_exist:
en: Key already does not exist
deleting_subkey:
en: Deleting subkey '%{name}' using %{parent}
5 changes: 5 additions & 0 deletions registry/locales/resource.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
_version: 2
resource:
what_if:
create_key:
en: "key: %{subkey} not found, would create it"
55 changes: 28 additions & 27 deletions registry/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
// Licensed under the MIT License.

use clap::{Parser, Subcommand};
use rust_i18n::t;

#[derive(Parser)]
#[clap(name = "registry", version = "0.0.1", about = "Manage state of Windows registry", long_about = None)]
#[clap(name = "registry", version = "0.0.1", about = t!("cli.about").to_string(), long_about = None)]
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
pub struct Arguments {

#[clap(subcommand)]
Expand All @@ -13,70 +14,70 @@ pub struct Arguments {

#[derive(Debug, PartialEq, Eq, Subcommand)]
pub enum ConfigSubCommand {
#[clap(name = "get", about = "Retrieve registry configuration.")]
#[clap(name = "get", about = t!("cli.config.get.about").to_string())]
Get {
#[clap(short, long, required = true, help = "The registry JSON input.")]
#[clap(short, long, required = true, help = t!("cli.config.args.input.help").to_string())]
input: String,
},
#[clap(name = "set", about = "Apply registry configuration.")]
#[clap(name = "set", about = t!("cli.config.set.about").to_string())]
Set {
#[clap(short, long, required = true, help = "The registry JSON input.")]
#[clap(short, long, required = true, help = t!("cli.config.args.input.help").to_string())]
input: String,
#[clap(short = 'w', long, help = "Run as a what-if operation instead of applying the registry configuration")]
#[clap(short = 'w', long, help = t!("cli.config.args.what_if.help").to_string())]
what_if: bool,
},
#[clap(name = "delete", about = "Delete registry configuration.")]
#[clap(name = "delete", about = t!("cli.config.delete.about").to_string())]
Delete {
#[clap(short, long, required = true, help = "The registry JSON input.")]
#[clap(short, long, required = true, help = t!("cli.config.args.input.help").to_string())]
input: String,
},
}

#[derive(Debug, PartialEq, Eq, Subcommand)]
pub enum SubCommand {
#[clap(name = "query", about = "Query a registry key or value.", arg_required_else_help = true)]
#[clap(name = "query", about = t!("cli.query.about").to_string(), arg_required_else_help = true)]
Query {
#[clap(short, long, required = true, help = "The registry key path to query.")]
#[clap(short, long, required = true, help = t!("cli.query.args.key_path.help").to_string())]
key_path: String,
#[clap(short, long, help = "The name of the value to query.")]
#[clap(short, long, help = t!("cli.query.args.value_name.help").to_string())]
value_name: Option<String>,
#[clap(short, long, help = "Recursively query subkeys.")]
#[clap(short, long, help = t!("cli.query.args.recurse.help").to_string())]
recurse: bool,
},
#[clap(name = "set", about = "Set a registry key or value.")]
#[clap(name = "set", about = t!("cli.set.about").to_string())]
Set {
#[clap(short, long, required = true, help = "The registry key path to set.")]
#[clap(short, long, required = true, help = t!("cli.set.args.key_path.help").to_string())]
key_path: String,
#[clap(short, long, help = "The value to set.")]
#[clap(short, long, help = t!("cli.set.args.value.help").to_string())]
value: String,
},
#[clap(name = "remove", about = "Remove a registry key or value.", arg_required_else_help = true)]
#[clap(name = "remove", about = t!("cli.remove.about").to_string(), arg_required_else_help = true)]
Remove {
#[clap(short, long, required = true, help = "The registry key path to remove.")]
#[clap(short, long, required = true, help = t!("cli.remove.args.key_path.help").to_string())]
key_path: String,
#[clap(short, long, help = "The name of the value to remove.")]
#[clap(short, long, help = t!("cli.remove.args.value_name.help").to_string())]
value_name: Option<String>,
#[clap(short, long, help = "Recursively remove subkeys.")]
#[clap(short, long, help = t!("cli.remove.args.recurse.help").to_string())]
recurse: bool,
},
#[clap(name = "find", about = "Find a registry key or value.", arg_required_else_help = true)]
#[clap(name = "find", about = t!("cli.find.about").to_string(), arg_required_else_help = true)]
Find {
#[clap(short, long, required = true, help = "The registry key path to start find.")]
#[clap(short, long, required = true, help = t!("cli.find.args.key_path.help").to_string())]
key_path: String,
#[clap(short, long, required = true, help = "The string to find.")]
#[clap(short, long, required = true, help = t!("cli.find.args.find.help").to_string())]
find: String,
#[clap(short, long, help = "Recursively find.")]
#[clap(short, long, help = t!("cli.find.args.recurse.help").to_string())]
recurse: bool,
#[clap(long, help = "Only find keys.")]
#[clap(long, help = t!("cli.find.args.keys_only.help").to_string())]
keys_only: bool,
#[clap(long, help = "Only find values.")]
#[clap(long, help = t!("cli.find.args.values_only.help").to_string())]
values_only: bool,
},
#[clap(name = "config", about = "Manage registry configuration.", arg_required_else_help = true)]
#[clap(name = "config", about = t!("cli.config.about").to_string(), arg_required_else_help = true)]
Config {
#[clap(subcommand)]
subcommand: ConfigSubCommand,
},
#[clap(name = "schema", about = "Retrieve JSON schema.")]
#[clap(name = "schema", about = t!("cli.schema.about").to_string())]
Schema,
}
17 changes: 9 additions & 8 deletions registry/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use rust_i18n::t;
use thiserror::Error;

#[derive(Error, Debug)]
#[allow(clippy::module_name_repetitions)]
pub enum RegistryError {
#[error("Invalid hive: {0}.")]
#[error("{t}: {0}", t = t!("errors.InvalidHive"))]
InvalidHive(String),

#[error("JSON: {0}")]
#[error("{t}: {0}", t = t!("errors.Json"))]
Json(#[from] serde_json::Error),

#[error("Registry: {0}")]
#[error("{t}: {0}", t = t!("errors.Registry"))]
Registry(#[from] registry::Error),

#[error("Registry key: {0}")]
#[error("{t}: {0}", t = t!("errors.RegistryKey"))]
RegistryKey(#[from] registry::key::Error),

#[error("Registry key not found: {0}")]
#[error("{t}: {0}", t = t!("errors.RegistryKeyNotFound"))]
RegistryKeyNotFound(String),

#[error("Registry value: {0}")]
#[error("{t}: {0}", t = t!("errors.RegistryValue"))]
RegistryValue(#[from] registry::value::Error),

#[error("UTF-16 conversion of {0} failed due to interior NULL values")]
#[error("{t}: {0}", t = t!("errors.Utf16Conversion"))]
Utf16Conversion(String),

#[error("Unsupported registry value data type")]
#[error("{t}", t = t!("errors.UnsupportedValueDataType"))]
UnsupportedValueDataType,
}
22 changes: 18 additions & 4 deletions registry/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ use crossterm::event;
#[cfg(debug_assertions)]
use std::env;

// #[macro_use]
// extern crate rust_i18n;

// Init translations using the `[package.metadata.i18n]` section in `Cargo.toml`
use rust_i18n::t;
rust_i18n::i18n!("locales");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should specify fallback = "en-us"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that gets pulled from he package metadata, but can explicitly set it here.


use args::Arguments;
use clap::Parser;
use registry_helper::RegistryHelper;
Expand Down Expand Up @@ -135,19 +142,24 @@ pub fn enable_tracing() {
let subscriber = tracing_subscriber::Registry::default().with(fmt).with(filter);

if tracing::subscriber::set_global_default(subscriber).is_err() {
eprintln!("Unable to set global default tracing subscriber. Tracing is diabled.");
let msg = t!("error.eprint.tracing_init");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can just be:

eprintln!("{}", t!("error.eprint.tracing_init");

eprintln!("{msg}");
}
}

#[cfg(debug_assertions)]
fn check_debug() {
if env::var("DEBUG_REGISTRY").is_ok() {
eprintln!("attach debugger to pid {} and press any key to continue", std::process::id());
let msg = t!("error.eprint.debug.attach", pid = std::process::id());
eprintln!("{msg}");
// eprintln!("attach debugger to pid {} and press any key to continue", std::process::id());
loop {
let event = match event::read() {
Ok(event) => event,
Err(err) => {
eprintln!("Error: Failed to read event: {err}");
let msg = t!("error.eprint.debug.event.read", "err" => err);
eprintln!("{msg}");
// eprintln!("Error: Failed to read event: {err}");
break;
}
};
Expand All @@ -157,7 +169,9 @@ fn check_debug() {
break;
}
} else {
eprintln!("Unexpected event: {event:?}");
let msg = t!("error.eprint.debug.event.unexpected", e = event : {:?});
eprintln!("{msg}");
// eprintln!("Unexpected event: {event:?}");
continue;
}
}
Expand Down
13 changes: 10 additions & 3 deletions registry/src/registry_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

use registry::{Data, Hive, RegKey, Security, key, value};
use rust_i18n::t;
use utfx::{U16CString, UCString};
use crate::config::{Metadata, Registry, RegistryValueData};
use crate::error::RegistryError;
Expand Down Expand Up @@ -99,7 +100,8 @@ impl RegistryHelper {
};

if self.what_if {
what_if_metadata.push(format!("key: {subkey} not found, would create it"));
what_if_metadata.push(t!("resource.what_if.create_key", "subkey" => subkey).to_string());
// what_if_metadata.push(format!("key: {subkey} not found, would create it"));
}
else {
reg_key = reg_key.create(path, Security::CreateSubKey)?;
Expand Down Expand Up @@ -185,7 +187,9 @@ impl RegistryHelper {
Ok(reg_key) => reg_key,
// handle NotFound error
Err(RegistryError::RegistryKeyNotFound(_)) => {
eprintln!("Key already does not exist");
let msg = t!("error.eprint.remove.key_not_exist");
eprintln!("{msg}");
// eprintln!("Key already does not exist");
return Ok(());
},
Err(e) => return Err(e),
Expand All @@ -200,7 +204,10 @@ impl RegistryHelper {

// get the subkey name
let subkey_name = &self.config.key_path[parent_path.len() + 1..];
eprintln!("Deleting subkey '{subkey_name}' using {parent_reg_key}");

let msg = t!("error.eprint.remove.deleting_subkey", name = subkey_name, parent = parent_reg_key);
eprintln!("{msg}");
// eprintln!("Deleting subkey '{subkey_name}' using {parent_reg_key}");
let Ok(subkey_name) = UCString::<u16>::from_str(subkey_name) else {
return Err(RegistryError::Utf16Conversion("subkey_name".to_string()));
};
Expand Down
Loading