working setup flow + manifest localization

This commit is contained in:
Aiden McClelland
2026-01-20 18:28:28 -07:00
parent 6a1c1fde06
commit 3828b03790
51 changed files with 799 additions and 324 deletions

View File

@@ -44,28 +44,44 @@ impl DeviceInfo {
impl DeviceInfo {
pub fn to_header_value(&self) -> HeaderValue {
let mut url: Url = "http://localhost".parse().unwrap();
url.query_pairs_mut()
.append_pair("os.version", &self.os.version.to_string())
let mut qp = url.query_pairs_mut();
qp.append_pair("os.version", &self.os.version.to_string())
.append_pair("os.compat", &self.os.compat.to_string())
.append_pair("os.platform", &*self.os.platform);
if let Some(lang) = self.os.language.as_deref() {
qp.append_pair("os.language", lang);
}
drop(qp);
HeaderValue::from_str(url.query().unwrap_or_default()).unwrap()
}
pub fn from_header_value(header: &HeaderValue) -> Result<Self, Error> {
let query: BTreeMap<_, _> = form_urlencoded::parse(header.as_bytes()).collect();
let has_hw_info = query.keys().any(|k| k.starts_with("hardware."));
let version = query
.get("os.version")
.or_not_found("os.version")?
.parse()?;
Ok(Self {
os: OsInfo {
version: query
.get("os.version")
.or_not_found("os.version")?
.parse()?,
compat: query.get("os.compat").or_not_found("os.compat")?.parse()?,
platform: query
.get("os.platform")
.or_not_found("os.platform")?
.deref()
.into(),
language: query
.get("os.language")
.map(|v| v.deref())
.map(InternedString::intern)
.or_else(|| {
if version < "0.4.0-alpha.18".parse().ok()? {
Some(rust_i18n::locale().deref().into())
} else {
None
}
}),
version,
},
hardware: has_hw_info
.then(|| {
@@ -190,8 +206,8 @@ pub struct OsInfo {
pub version: Version,
#[ts(type = "string")]
pub compat: VersionRange,
#[ts(type = "string")]
pub platform: InternedString,
pub language: Option<InternedString>,
}
impl From<&RpcContext> for OsInfo {
fn from(_: &RpcContext) -> Self {
@@ -199,6 +215,7 @@ impl From<&RpcContext> for OsInfo {
version: crate::version::Current::default().semver(),
compat: crate::version::Current::default().compat().clone(),
platform: InternedString::intern(&*crate::PLATFORM),
language: Some(InternedString::intern(&*rust_i18n::locale())),
}
}
}

View File

@@ -11,6 +11,7 @@ use crate::context::CliContext;
use crate::prelude::*;
use crate::registry::context::RegistryContext;
use crate::registry::package::index::Category;
use crate::s9pk::manifest::LocaleString;
use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable};
pub fn category_api<C: Context>() -> ParentHandler<C> {
@@ -66,7 +67,7 @@ pub fn category_api<C: Context>() -> ParentHandler<C> {
pub struct AddCategoryParams {
#[ts(type = "string")]
pub id: InternedString,
pub name: String,
pub name: LocaleString,
}
pub async fn add_category(
@@ -196,7 +197,7 @@ pub fn display_categories<T>(
"NAME",
]);
for (id, info) in categories {
table.add_row(row![&*id, &info.name]);
table.add_row(row![&*id, &info.name.localized()]);
}
table.print_tty(false)?;
Ok(())

View File

@@ -79,20 +79,20 @@ pub struct GetPackageResponse {
pub other_versions: Option<BTreeMap<VersionString, PackageInfoShort>>,
}
impl GetPackageResponse {
pub fn tables(&self) -> Vec<prettytable::Table> {
pub fn tables(self) -> Vec<prettytable::Table> {
use prettytable::*;
let mut res = Vec::with_capacity(self.best.len());
for (version, info) in &self.best {
let mut table = info.table(version);
for (version, info) in self.best {
let mut table = info.table(&version);
let lesser_versions: BTreeMap<_, _> = self
.other_versions
.as_ref()
.into_iter()
.flatten()
.filter(|(v, _)| ***v < **version)
.filter(|(v, _)| ***v < *version)
.collect();
if !lesser_versions.is_empty() {
@@ -121,13 +121,17 @@ pub struct GetPackageResponseFull {
pub other_versions: BTreeMap<VersionString, PackageVersionInfo>,
}
impl GetPackageResponseFull {
pub fn tables(&self) -> Vec<prettytable::Table> {
pub fn tables(self) -> Vec<prettytable::Table> {
let mut res = Vec::with_capacity(self.best.len());
let all: BTreeMap<_, _> = self.best.iter().chain(self.other_versions.iter()).collect();
let all: BTreeMap<_, _> = self
.best
.into_iter()
.chain(self.other_versions.into_iter())
.collect();
for (version, info) in all {
res.push(info.table(version));
res.push(info.table(&version));
}
res
@@ -444,7 +448,11 @@ pub async fn cli_download(
return Err(Error::new(
eyre!(
"{}",
t!("registry.package.get.version-not-found", id = id, version = target_version.unwrap_or(VersionRange::Any))
t!(
"registry.package.get.version-not-found",
id = id,
version = target_version.unwrap_or(VersionRange::Any)
)
),
ErrorKind::NotFound,
));
@@ -465,7 +473,11 @@ pub async fn cli_download(
return Err(Error::new(
eyre!(
"{}",
t!("registry.package.get.version-not-found", id = id, version = target_version.unwrap_or(VersionRange::Any))
t!(
"registry.package.get.version-not-found",
id = id,
version = target_version.unwrap_or(VersionRange::Any)
)
),
ErrorKind::NotFound,
));

View File

@@ -17,7 +17,7 @@ use crate::registry::device_info::DeviceInfo;
use crate::rpc_continuations::Guid;
use crate::s9pk::S9pk;
use crate::s9pk::git_hash::GitHash;
use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements};
use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements, LocaleString};
use crate::s9pk::merkle_archive::source::FileSource;
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
use crate::sign::{AnySignature, AnyVerifyingKey};
@@ -49,22 +49,27 @@ pub struct PackageInfo {
#[model = "Model<Self>"]
#[ts(export)]
pub struct Category {
pub name: String,
pub name: LocaleString,
}
#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq, Eq)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct DependencyMetadata {
#[ts(type = "string | null")]
pub title: Option<InternedString>,
pub title: Option<LocaleString>,
pub icon: Option<DataUrl<'static>>,
pub description: Option<String>,
pub description: Option<LocaleString>,
pub optional: bool,
}
impl DependencyMetadata {
pub fn localize_for(&mut self, locale: &str) {
self.title.as_mut().map(|t| t.localize_for(locale));
self.description.as_mut().map(|d| d.localize_for(locale));
}
}
#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq, Eq)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
pub struct PackageMetadata {
@@ -72,7 +77,7 @@ pub struct PackageMetadata {
pub title: InternedString,
pub icon: DataUrl<'static>,
pub description: Description,
pub release_notes: String,
pub release_notes: LocaleString,
pub git_hash: Option<GitHash>,
#[ts(type = "string")]
pub license: InternedString,
@@ -199,20 +204,20 @@ impl PackageVersionInfo {
self.s9pks.sort_by_key(|(h, _)| h.specificity_desc());
Ok(())
}
pub fn table(&self, version: &VersionString) -> prettytable::Table {
pub fn table(self, version: &VersionString) -> prettytable::Table {
use prettytable::*;
let mut table = Table::new();
table.add_row(row![bc => &self.metadata.title]);
table.add_row(row![br -> "VERSION", AsRef::<str>::as_ref(version)]);
table.add_row(row![br -> "RELEASE NOTES", &self.metadata.release_notes]);
table.add_row(row![br -> "RELEASE NOTES", &self.metadata.release_notes.localized()]);
table.add_row(
row![br -> "ABOUT", &textwrap::wrap(&self.metadata.description.short, 80).join("\n")],
row![br -> "ABOUT", &textwrap::wrap(&self.metadata.description.short.localized(), 80).join("\n")],
);
table.add_row(row![
br -> "DESCRIPTION",
&textwrap::wrap(&self.metadata.description.long, 80).join("\n")
&textwrap::wrap(&self.metadata.description.long.localized(), 80).join("\n")
]);
table.add_row(row![br -> "GIT HASH", self.metadata.git_hash.as_deref().unwrap_or("N/A")]);
table.add_row(row![br -> "LICENSE", &self.metadata.license]);
@@ -280,6 +285,24 @@ impl Model<PackageVersionInfo> {
{
return Ok(false);
}
if let Some(locale) = device_info.os.language.as_deref() {
let metadata = self.as_metadata_mut();
metadata
.as_alerts_mut()
.mutate(|a| Ok(a.localize_for(locale)));
metadata
.as_dependency_metadata_mut()
.as_entries_mut()?
.into_iter()
.try_for_each(|(_, d)| d.mutate(|d| Ok(d.localize_for(locale))));
metadata
.as_description_mut()
.mutate(|d| Ok(d.localize_for(locale)))?;
metadata
.as_release_notes_mut()
.mutate(|r| Ok(r.localize_for(locale)))?;
}
}
Ok(true)