diff --git a/core/src/db/model/package.rs b/core/src/db/model/package.rs
index 76869511a..70c33a360 100644
--- a/core/src/db/model/package.rs
+++ b/core/src/db/model/package.rs
@@ -14,7 +14,7 @@ use crate::net::host::Hosts;
use crate::net::service_interface::ServiceInterface;
use crate::prelude::*;
use crate::progress::FullProgress;
-use crate::s9pk::manifest::Manifest;
+use crate::s9pk::manifest::{LocaleString, Manifest};
use crate::status::StatusInfo;
use crate::util::DataUrl;
use crate::util::serde::{Pem, is_partial_of};
@@ -417,7 +417,7 @@ impl Map for CurrentDependencies {
#[serde(rename_all = "camelCase")]
#[model = "Model"]
pub struct CurrentDependencyInfo {
- pub title: Option,
+ pub title: Option,
pub icon: Option>,
#[serde(flatten)]
pub kind: CurrentDependencyKind,
diff --git a/core/src/registry/package/index.rs b/core/src/registry/package/index.rs
index 4a53c1c13..64b83d5e4 100644
--- a/core/src/registry/package/index.rs
+++ b/core/src/registry/package/index.rs
@@ -290,12 +290,12 @@ impl Model {
let metadata = self.as_metadata_mut();
metadata
.as_alerts_mut()
- .mutate(|a| Ok(a.localize_for(locale)));
+ .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))));
+ .try_for_each(|(_, d)| d.mutate(|d| Ok(d.localize_for(locale))))?;
metadata
.as_description_mut()
.mutate(|d| Ok(d.localize_for(locale)))?;
diff --git a/core/src/service/effects/dependency.rs b/core/src/service/effects/dependency.rs
index ffd38ddac..43f4297b9 100644
--- a/core/src/service/effects/dependency.rs
+++ b/core/src/service/effects/dependency.rs
@@ -228,7 +228,7 @@ pub async fn set_dependencies(
.s9pk
.dependency_metadata(&dep_id)
.await?
- .map(|m| m.title.localized()),
+ .map(|m| m.title),
icon: context
.seed
.persistent_container
@@ -345,7 +345,7 @@ pub async fn check_dependencies(
.collect();
results.push(CheckDependenciesResult {
package_id,
- title,
+ title: title.map(|t| t.localized()),
installed_version: None,
satisfies: BTreeSet::new(),
is_running: false,
@@ -371,7 +371,7 @@ pub async fn check_dependencies(
.collect();
results.push(CheckDependenciesResult {
package_id,
- title,
+ title: title.map(|t| t.localized()),
installed_version,
satisfies,
is_running,
diff --git a/sdk/base/lib/osBindings/CurrentDependencyInfo.ts b/sdk/base/lib/osBindings/CurrentDependencyInfo.ts
index e56e2be7a..aaabea857 100644
--- a/sdk/base/lib/osBindings/CurrentDependencyInfo.ts
+++ b/sdk/base/lib/osBindings/CurrentDependencyInfo.ts
@@ -1,8 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DataUrl } from "./DataUrl"
+import type { LocaleString } from "./LocaleString"
export type CurrentDependencyInfo = {
- title: string | null
+ title: LocaleString | null
icon: DataUrl | null
versionRange: string
} & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] })
diff --git a/sdk/package/lib/version/VersionInfo.ts b/sdk/package/lib/version/VersionInfo.ts
index 0d6bd1c62..35d6c0544 100644
--- a/sdk/package/lib/version/VersionInfo.ts
+++ b/sdk/package/lib/version/VersionInfo.ts
@@ -7,7 +7,7 @@ export type VersionOptions = {
/** The exver-compliant version number */
version: Version & ValidateExVer
/** The release notes for this version */
- releaseNotes: string
+ releaseNotes: T.LocaleString
/** Data migrations for this version */
migrations: {
/**
diff --git a/web/projects/marketplace/src/pages/list/categories/categories.component.html b/web/projects/marketplace/src/pages/list/categories/categories.component.html
index cdb2b2657..2bb5017cd 100644
--- a/web/projects/marketplace/src/pages/list/categories/categories.component.html
+++ b/web/projects/marketplace/src/pages/list/categories/categories.component.html
@@ -8,7 +8,7 @@
{{
- cat.key === 'ai' ? (cat.key | uppercase) : (cat.value.name | titlecase)
+ cat.key === 'ai' ? (cat.key | uppercase) : (cat.value.name | localize | titlecase)
}}
diff --git a/web/projects/marketplace/src/pages/list/categories/categories.module.ts b/web/projects/marketplace/src/pages/list/categories/categories.module.ts
index 15a779c8a..2d67d8aca 100644
--- a/web/projects/marketplace/src/pages/list/categories/categories.module.ts
+++ b/web/projects/marketplace/src/pages/list/categories/categories.module.ts
@@ -2,12 +2,13 @@ import { TuiIcon, TuiAppearance } from '@taiga-ui/core'
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { TuiSkeleton } from '@taiga-ui/kit'
+import { LocalizePipe } from '@start9labs/shared'
import { CategoriesComponent } from './categories.component'
import { RouterModule } from '@angular/router'
@NgModule({
- imports: [RouterModule, CommonModule, TuiAppearance, TuiIcon, TuiSkeleton],
+ imports: [RouterModule, CommonModule, TuiAppearance, TuiIcon, TuiSkeleton, LocalizePipe],
declarations: [CategoriesComponent],
exports: [CategoriesComponent],
})
diff --git a/web/projects/marketplace/src/pages/show/dependencies/dependency-item.component.ts b/web/projects/marketplace/src/pages/show/dependencies/dependency-item.component.ts
index 60e4302d7..216dc8d7c 100644
--- a/web/projects/marketplace/src/pages/show/dependencies/dependency-item.component.ts
+++ b/web/projects/marketplace/src/pages/show/dependencies/dependency-item.component.ts
@@ -1,7 +1,7 @@
import { KeyValue } from '@angular/common'
-import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
+import { ChangeDetectionStrategy, Component, inject, Input } from '@angular/core'
import { RouterModule } from '@angular/router'
-import { ExverPipesModule, i18nPipe } from '@start9labs/shared'
+import { ExverPipesModule, i18nPipe, i18nService } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiAvatar, TuiLineClamp } from '@taiga-ui/kit'
import { MarketplacePkgBase } from '../../../types'
@@ -97,6 +97,8 @@ import { MarketplacePkgBase } from '../../../types'
imports: [RouterModule, TuiAvatar, ExverPipesModule, TuiLineClamp, i18nPipe],
})
export class MarketplaceDepItemComponent {
+ private readonly i18nService = inject(i18nService)
+
@Input({ required: true })
pkg!: MarketplacePkgBase
@@ -109,6 +111,7 @@ export class MarketplaceDepItemComponent {
}
getTitle(key: string): string {
- return this.pkg.dependencyMetadata[key]?.title || key
+ const title = this.pkg.dependencyMetadata[key]?.title
+ return title ? this.i18nService.localize(title) : key
}
}
diff --git a/web/projects/marketplace/src/pages/show/hero.component.ts b/web/projects/marketplace/src/pages/show/hero.component.ts
index 488773d0f..8579ddaa2 100644
--- a/web/projects/marketplace/src/pages/show/hero.component.ts
+++ b/web/projects/marketplace/src/pages/show/hero.component.ts
@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { SharedPipesModule, TickerComponent } from '@start9labs/shared'
+import { T } from '@start9labs/start-sdk'
@Component({
selector: 'marketplace-package-hero',
@@ -125,7 +126,7 @@ export class MarketplacePackageHeroComponent {
id: string
title: string
version: string
- description: { short: string }
+ description: { short: T.LocaleString }
icon: string
}
diff --git a/web/projects/marketplace/src/pages/show/release-notes.component.ts b/web/projects/marketplace/src/pages/show/release-notes.component.ts
index a8d69552d..15a8a7a61 100644
--- a/web/projects/marketplace/src/pages/show/release-notes.component.ts
+++ b/web/projects/marketplace/src/pages/show/release-notes.component.ts
@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
-import { MarkdownPipe } from '@start9labs/shared'
+import { LocalizePipe, MarkdownPipe } from '@start9labs/shared'
import { NgDompurifyPipe } from '@taiga-ui/dompurify'
import { MarketplacePkgBase } from '../../types'
@@ -9,7 +9,7 @@ import { MarketplacePkgBase } from '../../types'
New in {{ pkg().version }}
-
+
`,
@@ -21,7 +21,7 @@ import { MarketplacePkgBase } from '../../types'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
- imports: [NgDompurifyPipe, MarkdownPipe],
+ imports: [NgDompurifyPipe, MarkdownPipe, LocalizePipe],
})
export class MarketplaceReleaseNotesComponent {
readonly pkg = input.required()
diff --git a/web/projects/shared/src/i18n/i18n.service.ts b/web/projects/shared/src/i18n/i18n.service.ts
index 28f1bc86a..375e941bb 100644
--- a/web/projects/shared/src/i18n/i18n.service.ts
+++ b/web/projects/shared/src/i18n/i18n.service.ts
@@ -1,6 +1,7 @@
import { inject, Injectable, signal } from '@angular/core'
import { TuiLanguageName, TuiLanguageSwitcherService } from '@taiga-ui/i18n'
import { I18N, I18N_LOADER, I18N_STORAGE } from './i18n.providers'
+import { T } from '@start9labs/start-sdk'
export const languages = ['en_US', 'es_ES', 'de_DE', 'fr_FR', 'pl_PL'] as const
export type Languages = (typeof languages)[number]
@@ -37,6 +38,14 @@ export class i18nService extends TuiLanguageSwitcherService {
)
}
+ localize(string: T.LocaleString): string {
+ if (typeof string === 'string') return string
+
+ return (
+ string[this.lang] ?? string['en_US'] ?? Object.values(string)[0] ?? ''
+ )
+ }
+
setLang(language: Languages = 'en_US'): void {
const tuiLang = LANGUAGE_TO_TUI[language]
const current = this.language
diff --git a/web/projects/shared/src/i18n/localize.pipe.ts b/web/projects/shared/src/i18n/localize.pipe.ts
new file mode 100644
index 000000000..e9f439ec0
--- /dev/null
+++ b/web/projects/shared/src/i18n/localize.pipe.ts
@@ -0,0 +1,16 @@
+import { inject, Injectable, Pipe, PipeTransform } from '@angular/core'
+import { i18nService } from './i18n.service'
+import { T } from '@start9labs/start-sdk'
+
+@Pipe({
+ name: 'localize',
+ pure: false,
+})
+@Injectable({ providedIn: 'root' })
+export class LocalizePipe implements PipeTransform {
+ private readonly i18nService = inject(i18nService)
+
+ transform(string: T.LocaleString): string {
+ return this.i18nService.localize(string)
+ }
+}
diff --git a/web/projects/shared/src/public-api.ts b/web/projects/shared/src/public-api.ts
index 3269be9aa..c3c683984 100644
--- a/web/projects/shared/src/public-api.ts
+++ b/web/projects/shared/src/public-api.ts
@@ -18,6 +18,7 @@ export * from './directives/safe-links.directive'
export * from './i18n/i18n.pipe'
export * from './i18n/i18n.providers'
export * from './i18n/i18n.service'
+export * from './i18n/localize.pipe'
export * from './pipes/exver/exver.module'
export * from './pipes/exver/exver.pipe'
diff --git a/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts b/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts
index f8732b32b..142ef126d 100644
--- a/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts
@@ -14,6 +14,7 @@ import {
Exver,
ExverPipesModule,
i18nPipe,
+ i18nService,
LoadingService,
sameUrl,
} from '@start9labs/shared'
@@ -122,6 +123,7 @@ export class MarketplaceControlsComponent {
private readonly router = inject(Router)
private readonly marketplace = inject(MarketplaceService)
private readonly api = inject(ApiService)
+ private readonly i18n = inject(i18nService)
readonly pkg = input.required>()
@@ -148,7 +150,7 @@ export class MarketplaceControlsComponent {
const originalUrl = localPkg?.registry || null
if (!localPkg) {
- if (await this.alerts.alertInstall(this.pkg().alerts.install || '')) {
+ if (await this.alerts.alertInstall(this.i18n.localize(this.pkg().alerts.install || ''))) {
this.installOrUpload(currentUrl)
}
return
diff --git a/web/projects/ui/src/app/routes/portal/routes/updates/item.component.ts b/web/projects/ui/src/app/routes/portal/routes/updates/item.component.ts
index c8348945a..0a87d2faf 100644
--- a/web/projects/ui/src/app/routes/portal/routes/updates/item.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/updates/item.component.ts
@@ -12,6 +12,7 @@ import {
DialogService,
i18nKey,
i18nPipe,
+ LocalizePipe,
MarkdownPipe,
SafeLinksDirective,
} from '@start9labs/shared'
@@ -138,7 +139,7 @@ import UpdatesComponent from './updates.component'
@@ -237,6 +238,7 @@ import UpdatesComponent from './updates.component'
TuiProgressCircle,
TuiTitle,
TuiFade,
+ LocalizePipe,
MarkdownPipe,
NgDompurifyPipe,
SafeLinksDirective,