feat: in-app updates (GitHub desktop, Play Android) + i18n fixes (#241)

* feat(desktop): share dialog on Linux/macOS via cloudreve://share deeplink

The Explorer context menu is Windows-only; the share dialog itself is
cross-platform but had no trigger elsewhere. cloudreve://share/<encoded
local path> now routes through the same ManagerCommand::ShareLink
pipeline, and desktop/linux/ ships a cloudreve-share helper plus
Nautilus script and Dolphin service-menu glue so file managers can
reach it. OAuth callback deeplinks are unaffected.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(desktop): don't drop share deep links at startup

Queue cloudreve://share/ links arriving before APP_STATE init and drain
after the sync service is up; also scan argv for a cloudreve:// URL so a
cold start launched by the file manager still opens the share dialog.
Nautilus script now reads NAUTILUS_SCRIPT_SELECTED_FILE_PATHS per line so
paths with spaces survive.

* feat(desktop): macOS share helper + file-manager install docs

cloudreve-share for macOS (open cloudreve://share/...) plus Shortcuts
Quick Action recipe for Finder. READMEs document Nautilus, Dolphin, and
Finder installation.

* feat: in-app updates (GitHub desktop, Play Android) + i18n fixes

Desktop (GitHub builds):
- tauri-plugin-updater wired to releases/latest/latest.json; minisign
  keypair generated, private key in TAURI_SIGNING_PRIVATE_KEY secrets
- Startup check prompts once per version (config.prompted_update_version);
  About page gains a manual 'Check for updates' entry
- New /update window: version + notes -> in-app download overlay with
  progress events -> install -> restart via app.restart()
- Release workflow signs updater artifacts, uploads .sig/.app.tar.gz/
  .AppImage.tar.gz, and a new updater-manifest job emits latest.json
  (desktop/scripts/gen_latest_json.py); tauri-action path signs natively
- Desktop version bumped 0.2.0 -> 0.3.0 so released updates register

Android (Play builds):
- Play in-app update check on resume -> in-app dialog -> official
  IMMEDIATE update sheet (app-update-ktx); once per version via prefs
- targetSdk/compileSdk 36 + AGP 8.9.3 (Play requires >= 36); bumped to
  versionCode 2 / 1.1.0 — uploaded and published on internal track

i18n:
- Desktop: translated share.* + settings.notificationSettings into all
  10 non-English locales; added update.* everywhere; popup time strings
  (justNow/minutesAgo/...) localized via i18n.t in utils
- Frontend: added 4 keys missing from en-US (raw-key rendering):
  modals.anonymous, modals.shareTargetsCount, common.save,
  file.selectUser (+ event.allEventTypes for zh-CN); fixed two
  mistyped keys (uplaoder->uploader, uploader:->application:uploader)

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pull/3593/head
Tomáš Dvořák 2 weeks ago committed by GitHub
parent bb215ffd8b
commit 8c6cbe09a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -51,6 +51,10 @@ jobs:
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# With the signing key set, tauri-action signs updater artifacts and
# publishes latest.json on the release — the in-app updater feed.
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
projectPath: desktop
tagName: ${{ github.ref_name }}

@ -77,6 +77,9 @@ jobs:
- name: Build desktop bundles
working-directory: desktop
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: cargo tauri build
- name: Publish bundles to GitHub release
@ -92,8 +95,9 @@ jobs:
done
gh release view "$TAG" >/dev/null
FILES=$(find desktop/target/release/bundle -type f \
\( -name '*.deb' -o -name '*.AppImage' -o -name '*.msi' \
-o -name '*.exe' -o -name '*.dmg' -o -name '*.app.tar.gz' \))
\( -name '*.deb' -o -name '*.AppImage' -o -name '*.AppImage.tar.gz' \
-o -name '*.msi' -o -name '*.exe' -o -name '*.dmg' \
-o -name '*.app.tar.gz' -o -name '*.sig' \))
[ -n "$FILES" ]
echo "$FILES" | while read -r f; do
name="cloudreve-desktop-$(basename "$f")"
@ -101,6 +105,23 @@ jobs:
gh release upload "$TAG" "$name" --clobber
done
updater-manifest:
needs: desktop
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and publish latest.json
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
set -e
mkdir -p sigs
gh release download "$TAG" -p '*.sig' -D sigs --clobber
python3 desktop/scripts/gen_latest_json.py "$TAG" sigs latest.json
gh release upload "$TAG" latest.json --clobber
android:
runs-on: ubuntu-latest
defaults:

@ -7,14 +7,14 @@ plugins {
android {
namespace = "org.cloudreve.android"
compileSdk = 35
compileSdk = 36
defaultConfig {
applicationId = "com.tdvorak.cloudreve"
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "1.0.0"
targetSdk = 36
versionCode = 2
versionName = "1.1.0"
}
signingConfigs {
@ -94,4 +94,9 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
implementation("io.coil-kt:coil-compose:2.7.0")
// Play in-app updates — no-op on sideloaded/non-Play installs
implementation("com.google.android.play:app-update-ktx:2.1.0")
// ActivityResult API (update flow launcher) needs fragment >= 1.3.0
implementation("androidx.fragment:fragment-ktx:1.8.5")
}

@ -4,7 +4,7 @@
<uses-sdk
android:minSdkVersion="28"
android:targetSdkVersion="35" />
android:targetSdkVersion="36" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

@ -1,6 +1,7 @@
package org.cloudreve.android
import android.app.Application
import com.google.android.play.core.appupdate.AppUpdateInfo
import kotlinx.coroutines.flow.MutableStateFlow
import org.cloudreve.android.api.ApiClient
import org.cloudreve.android.data.CameraUploadSettings
@ -16,6 +17,9 @@ class CloudreveApp : Application() {
data class OAuthCallback(val code: String, val state: String)
val oauthCallback = MutableStateFlow<OAuthCallback?>(null)
/** Pending Play in-app update, set by MainActivity's update check. */
val playUpdateAvailable = MutableStateFlow<AppUpdateInfo?>(null)
lateinit var sessionManager: SessionManager
private set
lateinit var apiClient: ApiClient

@ -6,6 +6,11 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.appupdate.AppUpdateOptions
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.UpdateAvailability
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@ -18,6 +23,9 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import kotlinx.coroutines.launch
import org.cloudreve.android.api.RefreshTokenRequest
import org.cloudreve.android.ui.files.FilesScreen
@ -30,6 +38,9 @@ import org.cloudreve.android.work.UploadWorker
class MainActivity : ComponentActivity() {
private val updateLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@ -37,6 +48,39 @@ class MainActivity : ComponentActivity() {
setContent { App() }
}
override fun onResume() {
super.onResume()
checkForPlayUpdate()
}
// Ask Play for an update; if one is available and this install came from
// Play, surface the in-app prompt once per version, then hand off to the
// official IMMEDIATE update sheet.
private fun checkForPlayUpdate() {
val manager = AppUpdateManagerFactory.create(this)
manager.appUpdateInfo.addOnSuccessListener { info ->
if (info.updateAvailability() != UpdateAvailability.UPDATE_AVAILABLE ||
!info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
) return@addOnSuccessListener
val version = info.availableVersionCode()
val prefs = getSharedPreferences("updates", MODE_PRIVATE)
if (prefs.getInt("prompted_version", -1) == version) return@addOnSuccessListener
prefs.edit().putInt("prompted_version", version).apply()
(application as CloudreveApp).playUpdateAvailable.value = info
}
}
fun launchPlayUpdate() {
val info = (application as CloudreveApp).playUpdateAvailable.value ?: return
runCatching {
AppUpdateManagerFactory.create(this).startUpdateFlowForResult(
info,
updateLauncher,
AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build(),
)
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleShareIntent(intent)
@ -103,6 +147,25 @@ fun App() {
}
CloudreveTheme {
val updateInfo by app.playUpdateAvailable.collectAsState()
if (updateInfo != null) {
AlertDialog(
onDismissRequest = { app.playUpdateAvailable.value = null },
title = { Text("Update available") },
text = { Text("A new version of Cloudreve Mobile is available on Google Play.") },
confirmButton = {
TextButton(onClick = {
app.playUpdateAvailable.value = null
(context as? MainActivity)?.launchPlayUpdate()
}) { Text("Update") }
},
dismissButton = {
TextButton(onClick = { app.playUpdateAvailable.value = null }) {
Text("Later")
}
},
)
}
when (loggedIn) {
null -> Unit // session store still loading; keep frame empty
false -> NavHost(navController = nav, startDestination = "login") {

@ -1,5 +1,5 @@
plugins {
id("com.android.application") version "8.7.3" apply false
id("com.android.application") version "8.9.3" apply false
id("org.jetbrains.kotlin.android") version "2.0.21" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.21" apply false

@ -66,6 +66,9 @@ pub struct AppConfig {
pub hide_tray_icon: bool,
/// Language/locale setting (e.g., "en-US", "zh-CN"). None means use system default.
pub language: Option<String>,
/// Last app-update version the user was already prompted about. A new
/// version still prompts once; dismissing snoozes until the next release.
pub prompted_update_version: Option<String>,
}
impl Default for AppConfig {
@ -81,6 +84,7 @@ impl Default for AppConfig {
sync_delay_seconds: 0,
hide_tray_icon: false,
language: None,
prompted_update_version: None,
}
}
}

@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Generate the Tauri updater manifest (latest.json) from release .sig assets.
Usage: gen_latest_json.py <tag> <sig_dir> <output.json>
Scans <sig_dir> for `*.sig` files downloaded from the GitHub release, maps each
to its updater platform, and emits the manifest the tauri-plugin-updater
expects. Asset URLs point at the release's renamed `cloudreve-desktop-*`
assets. The version is read from desktop/src-tauri/tauri.conf.json — desktop
has its own semver, independent of the repo's server release tags.
"""
import json
import os
import sys
PLATFORM_MAP = [
("_x64-setup.exe", "windows-x86_64"),
("_arm64-setup.exe", "windows-aarch64"),
("_aarch64.app.tar.gz", "darwin-aarch64"),
("_x64.app.tar.gz", "darwin-x86_64"),
("amd64.AppImage.tar.gz", "linux-x86_64"),
("aarch64.AppImage.tar.gz", "linux-aarch64"),
]
REPO = "Dvorinka/cloudreve"
def main() -> int:
tag, sig_dir, out_path = sys.argv[1], sys.argv[2], sys.argv[3]
conf = json.load(open("desktop/src-tauri/tauri.conf.json"))
version = conf["version"]
platforms = {}
for fname in sorted(os.listdir(sig_dir)):
if not fname.endswith(".sig"):
continue
# Release assets are renamed to `cloudreve-desktop-<basename>` at
# upload time, so the downloaded .sig name already is the asset name.
asset = fname[: -len(".sig")]
for marker, platform in PLATFORM_MAP:
if marker in asset:
signature = open(os.path.join(sig_dir, fname)).read().strip()
if not signature:
print(f"warning: empty signature in {fname}, skipping")
break
platforms[platform] = {
"signature": signature,
"url": (
f"https://github.com/{REPO}/releases/download/{tag}/"
f"{asset}"
),
}
break
if not platforms:
print("error: no updater artifacts found", file=sys.stderr)
return 1
manifest = {
"version": version,
"notes": f"Cloudreve Desktop {version}",
"pub_date": "",
"platforms": platforms,
}
with open(out_path, "w") as f:
json.dump(manifest, f, indent=2)
print(f"latest.json: version={version}, platforms={sorted(platforms)}")
return 0
if __name__ == "__main__":
sys.exit(main())

@ -1,6 +1,6 @@
[package]
name = "cloudreve-desktop"
version = "0.2.0"
version = "0.3.0"
description = "Cloudreve Desktop Sync Client"
authors = ["cloudreve"]
license = ""
@ -44,6 +44,7 @@ tauri-plugin-deep-link = "2.4.9"
tauri-plugin-dialog = "2.7.1"
tauri-plugin-prevent-default = "4"
tauri-plugin-os = "2"
tauri-plugin-updater = "2"
tauri-plugin-frame = "1.1.6"
[target.'cfg(windows)'.dependencies.windows]

@ -1471,3 +1471,161 @@ pub async fn open_log_folder() -> CommandResult<()> {
showfile::show_path_in_file_manager(format!("{}\\", log_dir.display()));
Ok(())
}
/// Metadata about an available app update, returned to the frontend.
#[derive(serde::Serialize)]
pub struct UpdateInfo {
pub version: String,
pub current_version: String,
pub notes: Option<String>,
pub date: Option<String>,
}
/// Progress payload emitted on `update-progress` while an update downloads.
#[derive(serde::Serialize, Clone)]
struct UpdateProgress {
downloaded: u64,
total: Option<u64>,
}
/// Check the configured update endpoint (GitHub releases `latest.json`) for a
/// newer desktop build. Returns `None` when already up to date or when no
/// signed updater artifact exists for this platform.
#[tauri::command]
pub async fn check_update(app: AppHandle) -> CommandResult<Option<UpdateInfo>> {
use tauri_plugin_updater::UpdaterExt;
let update = app
.updater()
.map_err(|e| e.to_string())?
.check()
.await
.map_err(|e| e.to_string())?;
Ok(update.map(|u| UpdateInfo {
version: u.version.clone(),
current_version: u.current_version.clone(),
notes: u.body.clone(),
date: u.date.map(|d| d.to_string()),
}))
}
/// Download the pending update, emit `update-progress` events, and install it.
/// The frontend should call `restart_app` afterwards.
#[tauri::command]
pub async fn install_update(app: AppHandle) -> CommandResult<()> {
use tauri::Emitter;
use tauri_plugin_updater::UpdaterExt;
let update = app
.updater()
.map_err(|e| e.to_string())?
.check()
.await
.map_err(|e| e.to_string())?
.ok_or_else(|| "No update available".to_string())?;
let app2 = app.clone();
let app3 = app.clone();
update
.download_and_install(
move |chunk_len, content_len| {
let _ = app2.emit(
"update-progress",
UpdateProgress {
downloaded: chunk_len as u64,
total: content_len,
},
);
},
move || {
let _ = app3.emit("update-finished", ());
},
)
.await
.map_err(|e| e.to_string())?;
Ok(())
}
/// Restart the application (used after a successful update install).
#[tauri::command]
pub fn restart_app(app: AppHandle) {
app.restart();
}
/// Show or create the in-app update window. The window itself runs the update
/// check so it works both for automatic prompts and manual "Check for
/// updates" from the About page.
pub fn show_update_window_impl(app: &AppHandle) {
// One update window at a time — refocus instead of stacking dialogs.
if let Some(window) = app.get_webview_window("update") {
let _ = window.unminimize();
let _ = window.show();
let _ = window.set_focus();
return;
}
let url_path = get_url_with_lang("index.html#/update");
#[cfg(windows)]
let effects = WindowEffectsConfig {
effects: vec![WindowEffect::Mica, WindowEffect::Acrylic],
state: None,
radius: None,
color: None,
};
let builder = WebviewWindowBuilder::new(app, "update", WebviewUrl::App(url_path.into()))
.title("Software Update")
.inner_size(460.0, 380.0)
.resizable(false)
.visible(false)
.decorations(false)
.minimizable(false);
#[cfg(not(windows))]
let builder = builder.background_color(Color(255, 255, 255, 255));
#[cfg(windows)]
let builder = builder.transparent(true);
#[cfg(target_os = "macos")]
let builder = builder
.title_bar_style(TitleBarStyle::Overlay)
.hidden_title(true);
let Some(builder) = apply_default_window_icon(builder, app, "update") else {
return;
};
match builder.build() {
Ok(window) => {
#[cfg(target_os = "macos")]
update_dock_on_window_close(&window);
#[cfg(windows)]
{
let _ = window.set_effects(effects);
}
move_window_safely(&window, Position::Center, "update");
#[cfg(windows)]
let _ = window.create_overlay_titlebar();
let _ = window.show();
let _ = window.set_focus();
#[cfg(target_os = "macos")]
crate::update_dock_visibility(app);
}
Err(e) => {
tracing::error!(target: "main", error = %e, "Failed to create update window");
}
}
}
/// Open the in-app update window (Settings → About → "Check for updates").
#[tauri::command]
pub async fn show_update_window(app: AppHandle) -> CommandResult<()> {
show_update_window_impl(&app);
Ok(())
}

@ -190,6 +190,29 @@ async fn init_sync_service(app: AppHandle) -> anyhow::Result<()> {
event_broadcaster.no_drive();
}
// Check GitHub releases for a newer desktop build in the background. The
// prompt is shown once per version; dismissing it snoozes until the next
// release. Failures (offline, no updater artifact for this platform) are
// silent — the About page still offers a manual check.
{
let app = app.clone();
tauri::async_runtime::spawn(async move {
use tauri_plugin_updater::UpdaterExt;
tokio::time::sleep(std::time::Duration::from_secs(8)).await;
let Ok(updater) = app.updater() else { return };
let Ok(Some(update)) = updater.check().await else { return };
let config = ConfigManager::get().get_config();
if config.prompted_update_version.as_deref() == Some(update.version.as_str()) {
return;
}
let _ = ConfigManager::get()
.update(|c| c.prompted_update_version = Some(update.version.clone()));
commands::show_update_window_impl(&app);
});
}
tracing::info!(target: "main", "Tauri application setup complete");
Ok(())
@ -472,6 +495,7 @@ pub fn run() {
builder
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_prevent_default::debug())
@ -532,6 +556,10 @@ pub fn run() {
commands::get_file_icon,
commands::show_file_in_explorer,
commands::create_share,
commands::check_update,
commands::install_update,
commands::restart_app,
commands::show_update_window,
commands::show_add_drive_window,
commands::show_reauthorize_window,
commands::show_settings_window,

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Cloudreve",
"version": "0.2.0",
"version": "0.3.0",
"identifier": "cloudreve.desktop",
"build": {
"frontendDist": "../ui/dist",
@ -41,7 +41,9 @@
"targets": "all",
"linux": {
"deb": {
"depends": ["fuse3"]
"depends": [
"fuse3"
]
}
},
"icon": [
@ -50,12 +52,24 @@
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
],
"createUpdaterArtifacts": true
},
"plugins": {
"deep-link": {
"desktop": {
"schemes": ["cloudreve"]
"schemes": [
"cloudreve"
]
}
},
"updater": {
"endpoints": [
"https://github.com/Dvorinka/cloudreve/releases/latest/download/latest.json"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIzRUE2QkM3NzJDQ0QxRjAKUldUdzBjeHl4MnZxSXloWjRaRmtqMXZkcXN1UlRYdk1PanliYis0WDlqWEdWczQ1RHZBcHMvV2cK",
"windows": {
"installMode": "passive"
}
}
}

@ -54,14 +54,18 @@
"processing": "Verarbeitung...",
"conflicts": "Konflikte",
"conflictStatus": "{{count}} Konflikt(e) erfordern Aufmerksamkeit",
"conflictDescription": "Die lokale Datei steht im Konflikt mit der Remote-Version",
"conflictDescription": "Lokale Datei steht im Konflikt mit der Remote-Version",
"resolveConflict": "Konflikt lösen",
"resolving": "Wird gelöst...",
"keepRemote": "Remote-Version behalten",
"overwriteRemote": "Remote-Version überschreiben",
"saveAsNew": "Als neue Datei speichern",
"keepAllRemote": "Alle Remote-Versionen behalten",
"overwriteAllRemote": "Alle Remote-Versionen überschreiben"
"overwriteAllRemote": "Alle Remote-Versionen überschreiben",
"justNow": "Gerade eben",
"minutesAgo": "vor {{count}} Min.",
"hoursAgo": "vor {{count}} Std.",
"daysAgo": "vor {{count}} Tagen"
},
"settings": {
"title": "Einstellungen",
@ -138,26 +142,40 @@
"yearsAgo": "Vor {{value}} Jahren"
},
"share": {
"title": "Share link for {{name}}",
"linkReady": "Your share link is ready:",
"permission": "Sharing permissions",
"permViewDownload": "View + Download",
"permPreviewOnly": "Preview only (no download)",
"permAllowEdit": "View + Download + Edit",
"permUploadOnly": "Upload only (drop box)",
"expires": "Expires",
"expire_never": "Never expires",
"expire_oneHour": "1 hour",
"expire_oneDay": "1 day",
"expire_sevenDays": "7 days",
"expire_thirtyDays": "30 days",
"passwordProtect": "Protect with password",
"password": "Password",
"downloadLimit": "Download limit",
"unlimited": "Unlimited",
"create": "Create share",
"cancel": "Cancel",
"done": "Done",
"copied": "Link copied to clipboard"
"title": "Freigabelink für {{name}}",
"linkReady": "Ihr Freigabelink ist bereit:",
"permission": "Freigabeberechtigungen",
"permViewDownload": "Ansehen + Herunterladen",
"permPreviewOnly": "Nur Vorschau (kein Download)",
"permAllowEdit": "Ansehen + Herunterladen + Bearbeiten",
"permUploadOnly": "Nur hochladen (Abgabebox)",
"expires": "Läuft ab",
"expire_never": "Läuft nie ab",
"expire_oneHour": "1 Stunde",
"expire_oneDay": "1 Tag",
"expire_sevenDays": "7 Tage",
"expire_thirtyDays": "30 Tage",
"passwordProtect": "Mit Passwort schützen",
"password": "Passwort",
"downloadLimit": "Download-Limit",
"unlimited": "Unbegrenzt",
"create": "Freigabe erstellen",
"cancel": "Abbrechen",
"done": "Fertig",
"copied": "Link in die Zwischenablage kopiert"
},
"update": {
"title": "Software-Update",
"available": "Version {{version}} ist verfügbar (aktuell {{current}}).",
"downloading": "Update wird heruntergeladen…",
"ready": "Update installiert — zum Anwenden neu starten.",
"upToDate": "Sie sind auf dem neuesten Stand.",
"failed": "Update-Prüfung fehlgeschlagen.",
"updateNow": "Jetzt aktualisieren",
"later": "Später",
"restart": "Jetzt neu starten",
"close": "Schließen",
"checkNow": "Nach Updates suchen",
"downloadManually": "Manuell herunterladen"
}
}

@ -61,7 +61,11 @@
"overwriteRemote": "Overwrite remote",
"saveAsNew": "Save as new",
"keepAllRemote": "Keep all remote",
"overwriteAllRemote": "Overwrite all remote"
"overwriteAllRemote": "Overwrite all remote",
"justNow": "Just now",
"minutesAgo": "{{count}}m ago",
"hoursAgo": "{{count}}h ago",
"daysAgo": "{{count}}d ago"
},
"settings": {
"title": "Settings",
@ -159,5 +163,19 @@
"cancel": "Cancel",
"done": "Done",
"copied": "Link copied to clipboard"
},
"update": {
"title": "Software Update",
"available": "Version {{version}} is available (you have {{current}}).",
"downloading": "Downloading update…",
"ready": "Update installed — restart to apply.",
"upToDate": "You're up to date.",
"failed": "Update check failed.",
"updateNow": "Update now",
"later": "Later",
"restart": "Restart now",
"close": "Close",
"checkNow": "Check for updates",
"downloadManually": "Download manually"
}
}

@ -61,7 +61,11 @@
"overwriteRemote": "Sobrescribir remoto",
"saveAsNew": "Guardar como nuevo",
"keepAllRemote": "Conservar todas las remotas",
"overwriteAllRemote": "Sobrescribir todas las remotas"
"overwriteAllRemote": "Sobrescribir todas las remotas",
"justNow": "Ahora mismo",
"minutesAgo": "hace {{count}} min",
"hoursAgo": "hace {{count}} h",
"daysAgo": "hace {{count}} d"
},
"settings": {
"title": "Configuración",
@ -138,26 +142,40 @@
"yearsAgo": "Hace {{value}} años"
},
"share": {
"title": "Share link for {{name}}",
"linkReady": "Your share link is ready:",
"permission": "Sharing permissions",
"permViewDownload": "View + Download",
"permPreviewOnly": "Preview only (no download)",
"permAllowEdit": "View + Download + Edit",
"permUploadOnly": "Upload only (drop box)",
"expires": "Expires",
"expire_never": "Never expires",
"expire_oneHour": "1 hour",
"expire_oneDay": "1 day",
"expire_sevenDays": "7 days",
"expire_thirtyDays": "30 days",
"passwordProtect": "Protect with password",
"password": "Password",
"downloadLimit": "Download limit",
"unlimited": "Unlimited",
"create": "Create share",
"cancel": "Cancel",
"done": "Done",
"copied": "Link copied to clipboard"
"title": "Enlace para compartir de {{name}}",
"linkReady": "Tu enlace para compartir está listo:",
"permission": "Permisos de uso compartido",
"permViewDownload": "Ver + Descargar",
"permPreviewOnly": "Solo vista previa (sin descarga)",
"permAllowEdit": "Ver + Descargar + Editar",
"permUploadOnly": "Solo subir (buzón)",
"expires": "Caduca",
"expire_never": "Nunca caduca",
"expire_oneHour": "1 hora",
"expire_oneDay": "1 día",
"expire_sevenDays": "7 días",
"expire_thirtyDays": "30 días",
"passwordProtect": "Proteger con contraseña",
"password": "Contraseña",
"downloadLimit": "Límite de descargas",
"unlimited": "Ilimitado",
"create": "Crear enlace",
"cancel": "Cancelar",
"done": "Hecho",
"copied": "Enlace copiado al portapapeles"
},
"update": {
"title": "Actualización de software",
"available": "La versión {{version}} está disponible (tienes {{current}}).",
"downloading": "Descargando actualización…",
"ready": "Actualización instalada — reinicia para aplicar.",
"upToDate": "Estás al día.",
"failed": "Error al comprobar actualizaciones.",
"updateNow": "Actualizar ahora",
"later": "Más tarde",
"restart": "Reiniciar ahora",
"close": "Cerrar",
"checkNow": "Buscar actualizaciones",
"downloadManually": "Descargar manualmente"
}
}

@ -61,7 +61,11 @@
"overwriteRemote": "Écraser la version distante",
"saveAsNew": "Enregistrer comme nouveau fichier",
"keepAllRemote": "Conserver toutes les versions distantes",
"overwriteAllRemote": "Écraser toutes les versions distantes"
"overwriteAllRemote": "Écraser toutes les versions distantes",
"justNow": "À l'instant",
"minutesAgo": "il y a {{count}} min",
"hoursAgo": "il y a {{count}} h",
"daysAgo": "il y a {{count}} j"
},
"settings": {
"title": "Paramètres",
@ -138,26 +142,40 @@
"yearsAgo": "Il y a {{value}} ans"
},
"share": {
"title": "Share link for {{name}}",
"linkReady": "Your share link is ready:",
"permission": "Sharing permissions",
"permViewDownload": "View + Download",
"permPreviewOnly": "Preview only (no download)",
"permAllowEdit": "View + Download + Edit",
"permUploadOnly": "Upload only (drop box)",
"expires": "Expires",
"expire_never": "Never expires",
"expire_oneHour": "1 hour",
"expire_oneDay": "1 day",
"expire_sevenDays": "7 days",
"expire_thirtyDays": "30 days",
"passwordProtect": "Protect with password",
"password": "Password",
"downloadLimit": "Download limit",
"unlimited": "Unlimited",
"create": "Create share",
"cancel": "Cancel",
"done": "Done",
"copied": "Link copied to clipboard"
"title": "Lien de partage pour {{name}}",
"linkReady": "Votre lien de partage est prêt :",
"permission": "Permissions de partage",
"permViewDownload": "Voir + Télécharger",
"permPreviewOnly": "Aperçu uniquement (sans téléchargement)",
"permAllowEdit": "Voir + Télécharger + Modifier",
"permUploadOnly": "Téléversement uniquement (boîte de dépôt)",
"expires": "Expire",
"expire_never": "N'expire jamais",
"expire_oneHour": "1 heure",
"expire_oneDay": "1 jour",
"expire_sevenDays": "7 jours",
"expire_thirtyDays": "30 jours",
"passwordProtect": "Protéger par mot de passe",
"password": "Mot de passe",
"downloadLimit": "Limite de téléchargements",
"unlimited": "Illimité",
"create": "Créer le partage",
"cancel": "Annuler",
"done": "Terminé",
"copied": "Lien copié dans le presse-papiers"
},
"update": {
"title": "Mise à jour logicielle",
"available": "La version {{version}} est disponible (vous avez {{current}}).",
"downloading": "Téléchargement de la mise à jour…",
"ready": "Mise à jour installée — redémarrez pour l'appliquer.",
"upToDate": "Vous êtes à jour.",
"failed": "Échec de la vérification des mises à jour.",
"updateNow": "Mettre à jour",
"later": "Plus tard",
"restart": "Redémarrer",
"close": "Fermer",
"checkNow": "Rechercher des mises à jour",
"downloadManually": "Télécharger manuellement"
}
}

@ -61,7 +61,11 @@
"overwriteRemote": "Sovrascrivi remoto",
"saveAsNew": "Salva come nuovo",
"keepAllRemote": "Mantieni tutte le versioni remote",
"overwriteAllRemote": "Sovrascrivi tutte le versioni remote"
"overwriteAllRemote": "Sovrascrivi tutte le versioni remote",
"justNow": "Proprio ora",
"minutesAgo": "{{count}} min fa",
"hoursAgo": "{{count}} h fa",
"daysAgo": "{{count}} g fa"
},
"settings": {
"title": "Impostazioni",
@ -138,26 +142,40 @@
"yearsAgo": "{{value}} anni fa"
},
"share": {
"title": "Share link for {{name}}",
"linkReady": "Your share link is ready:",
"permission": "Sharing permissions",
"permViewDownload": "View + Download",
"permPreviewOnly": "Preview only (no download)",
"permAllowEdit": "View + Download + Edit",
"permUploadOnly": "Upload only (drop box)",
"expires": "Expires",
"expire_never": "Never expires",
"expire_oneHour": "1 hour",
"expire_oneDay": "1 day",
"expire_sevenDays": "7 days",
"expire_thirtyDays": "30 days",
"passwordProtect": "Protect with password",
"title": "Link di condivisione per {{name}}",
"linkReady": "Il tuo link di condivisione è pronto:",
"permission": "Permessi di condivisione",
"permViewDownload": "Visualizza + Scarica",
"permPreviewOnly": "Solo anteprima (senza download)",
"permAllowEdit": "Visualizza + Scarica + Modifica",
"permUploadOnly": "Solo caricamento (casella di deposito)",
"expires": "Scade",
"expire_never": "Non scade mai",
"expire_oneHour": "1 ora",
"expire_oneDay": "1 giorno",
"expire_sevenDays": "7 giorni",
"expire_thirtyDays": "30 giorni",
"passwordProtect": "Proteggi con password",
"password": "Password",
"downloadLimit": "Download limit",
"unlimited": "Unlimited",
"create": "Create share",
"cancel": "Cancel",
"done": "Done",
"copied": "Link copied to clipboard"
"downloadLimit": "Limite di download",
"unlimited": "Illimitato",
"create": "Crea condivisione",
"cancel": "Annulla",
"done": "Fatto",
"copied": "Link copiato negli appunti"
},
"update": {
"title": "Aggiornamento software",
"available": "La versione {{version}} è disponibile (hai la {{current}}).",
"downloading": "Download dell'aggiornamento…",
"ready": "Aggiornamento installato — riavvia per applicare.",
"upToDate": "Sei aggiornato.",
"failed": "Controllo aggiornamenti fallito.",
"updateNow": "Aggiorna ora",
"later": "Più tardi",
"restart": "Riavvia ora",
"close": "Chiudi",
"checkNow": "Controlla aggiornamenti",
"downloadManually": "Scarica manualmente"
}
}

@ -54,14 +54,18 @@
"processing": "処理中...",
"conflicts": "競合",
"conflictStatus": "{{count}} 件の競合に対応が必要です",
"conflictDescription": "ローカルファイルがリモート版と競合しています",
"conflictDescription": "ローカルファイルがリモートバージョンと競合しています",
"resolveConflict": "競合を解決",
"resolving": "解決中...",
"keepRemote": "リモート版を保持",
"overwriteRemote": "リモート版を上書き",
"saveAsNew": "新しいファイルとして保存",
"keepAllRemote": "すべてリモートを保持",
"overwriteAllRemote": "すべてリモートで上書き"
"overwriteAllRemote": "すべてリモートで上書き",
"justNow": "たった今",
"minutesAgo": "{{count}}分前",
"hoursAgo": "{{count}}時間前",
"daysAgo": "{{count}}日前"
},
"settings": {
"title": "設定",
@ -138,26 +142,40 @@
"yearsAgo": "{{value}} 年前"
},
"share": {
"title": "Share link for {{name}}",
"linkReady": "Your share link is ready:",
"permission": "Sharing permissions",
"permViewDownload": "View + Download",
"permPreviewOnly": "Preview only (no download)",
"permAllowEdit": "View + Download + Edit",
"permUploadOnly": "Upload only (drop box)",
"expires": "Expires",
"expire_never": "Never expires",
"expire_oneHour": "1 hour",
"expire_oneDay": "1 day",
"expire_sevenDays": "7 days",
"expire_thirtyDays": "30 days",
"passwordProtect": "Protect with password",
"password": "Password",
"downloadLimit": "Download limit",
"unlimited": "Unlimited",
"create": "Create share",
"cancel": "Cancel",
"done": "Done",
"copied": "Link copied to clipboard"
"title": "{{name}} の共有リンク",
"linkReady": "共有リンクの準備ができました:",
"permission": "共有権限",
"permViewDownload": "表示 + ダウンロード",
"permPreviewOnly": "プレビューのみ (ダウンロード不可)",
"permAllowEdit": "表示 + ダウンロード + 編集",
"permUploadOnly": "アップロードのみ (ドロップボックス)",
"expires": "有効期限",
"expire_never": "無期限",
"expire_oneHour": "1 時間",
"expire_oneDay": "1 日",
"expire_sevenDays": "7 日",
"expire_thirtyDays": "30 日",
"passwordProtect": "パスワードで保護",
"password": "パスワード",
"downloadLimit": "ダウンロード上限",
"unlimited": "無制限",
"create": "共有を作成",
"cancel": "キャンセル",
"done": "完了",
"copied": "リンクをクリップボードにコピーしました"
},
"update": {
"title": "ソフトウェア更新",
"available": "バージョン {{version}} が利用可能です(現在 {{current}})。",
"downloading": "更新をダウンロード中…",
"ready": "更新をインストールしました — 再起動して適用します。",
"upToDate": "最新の状態です。",
"failed": "更新の確認に失敗しました。",
"updateNow": "今すぐ更新",
"later": "後で",
"restart": "今すぐ再起動",
"close": "閉じる",
"checkNow": "更新を確認",
"downloadManually": "手動でダウンロード"
}
}

@ -61,7 +61,11 @@
"overwriteRemote": "원격 버전 덮어쓰기",
"saveAsNew": "새 파일로 저장",
"keepAllRemote": "모두 원격 버전 유지",
"overwriteAllRemote": "모두 원격 버전으로 덮어쓰기"
"overwriteAllRemote": "모두 원격 버전으로 덮어쓰기",
"justNow": "방금",
"minutesAgo": "{{count}}분 전",
"hoursAgo": "{{count}}시간 전",
"daysAgo": "{{count}}일 전"
},
"settings": {
"title": "설정",
@ -138,26 +142,40 @@
"yearsAgo": "{{value}}년 전"
},
"share": {
"title": "Share link for {{name}}",
"linkReady": "Your share link is ready:",
"permission": "Sharing permissions",
"permViewDownload": "View + Download",
"permPreviewOnly": "Preview only (no download)",
"permAllowEdit": "View + Download + Edit",
"permUploadOnly": "Upload only (drop box)",
"expires": "Expires",
"expire_never": "Never expires",
"expire_oneHour": "1 hour",
"expire_oneDay": "1 day",
"expire_sevenDays": "7 days",
"expire_thirtyDays": "30 days",
"passwordProtect": "Protect with password",
"password": "Password",
"downloadLimit": "Download limit",
"unlimited": "Unlimited",
"create": "Create share",
"cancel": "Cancel",
"done": "Done",
"copied": "Link copied to clipboard"
"title": "{{name}} 공유 링크",
"linkReady": "공유 링크가 준비되었습니다:",
"permission": "공유 권한",
"permViewDownload": "보기 + 다운로드",
"permPreviewOnly": "미리보기 전용 (다운로드 불가)",
"permAllowEdit": "보기 + 다운로드 + 편집",
"permUploadOnly": "업로드 전용 (드롭 박스)",
"expires": "만료",
"expire_never": "만료 없음",
"expire_oneHour": "1시간",
"expire_oneDay": "1일",
"expire_sevenDays": "7일",
"expire_thirtyDays": "30일",
"passwordProtect": "비밀번호로 보호",
"password": "비밀번호",
"downloadLimit": "다운로드 제한",
"unlimited": "제한 없음",
"create": "공유 만들기",
"cancel": "취소",
"done": "완료",
"copied": "링크가 클립보드에 복사되었습니다"
},
"update": {
"title": "소프트웨어 업데이트",
"available": "버전 {{version}}을(를) 사용할 수 있습니다 (현재 {{current}}).",
"downloading": "업데이트 다운로드 중…",
"ready": "업데이트가 설치되었습니다 — 재시작하여 적용하세요.",
"upToDate": "최신 상태입니다.",
"failed": "업데이트 확인에 실패했습니다.",
"updateNow": "지금 업데이트",
"later": "나중에",
"restart": "지금 재시작",
"close": "닫기",
"checkNow": "업데이트 확인",
"downloadManually": "수동으로 다운로드"
}
}

@ -54,14 +54,18 @@
"processing": "Przetwarzanie...",
"conflicts": "Konflikty",
"conflictStatus": "{{count}} konflikt(y) wymagają uwagi",
"conflictDescription": "Plik lokalny jest w konflikcie z wersją zdalną",
"conflictDescription": "Lokalny plik jest w konflikcie z wersją zdalną",
"resolveConflict": "Rozwiąż konflikt",
"resolving": "Rozwiązywanie...",
"keepRemote": "Zachowaj wersję zdalną",
"overwriteRemote": "Nadpisz wersję zdalną",
"saveAsNew": "Zapisz jako nowy plik",
"keepAllRemote": "Zachowaj wszystkie zdalne",
"overwriteAllRemote": "Nadpisz wszystkie zdalne"
"overwriteAllRemote": "Nadpisz wszystkie zdalne",
"justNow": "Przed chwilą",
"minutesAgo": "{{count}} min temu",
"hoursAgo": "{{count}} godz. temu",
"daysAgo": "{{count}} dni temu"
},
"settings": {
"title": "Ustawienia",
@ -138,26 +142,40 @@
"yearsAgo": "{{value}} lat temu"
},
"share": {
"title": "Share link for {{name}}",
"linkReady": "Your share link is ready:",
"permission": "Sharing permissions",
"permViewDownload": "View + Download",
"permPreviewOnly": "Preview only (no download)",
"permAllowEdit": "View + Download + Edit",
"permUploadOnly": "Upload only (drop box)",
"expires": "Expires",
"expire_never": "Never expires",
"expire_oneHour": "1 hour",
"expire_oneDay": "1 day",
"expire_sevenDays": "7 days",
"expire_thirtyDays": "30 days",
"passwordProtect": "Protect with password",
"password": "Password",
"downloadLimit": "Download limit",
"unlimited": "Unlimited",
"create": "Create share",
"cancel": "Cancel",
"done": "Done",
"copied": "Link copied to clipboard"
"title": "Link do udostępnienia dla {{name}}",
"linkReady": "Twój link do udostępnienia jest gotowy:",
"permission": "Uprawnienia udostępniania",
"permViewDownload": "Podgląd + Pobieranie",
"permPreviewOnly": "Tylko podgląd (bez pobierania)",
"permAllowEdit": "Podgląd + Pobieranie + Edycja",
"permUploadOnly": "Tylko przesyłanie (skrzynka nadawcza)",
"expires": "Wygasa",
"expire_never": "Nigdy nie wygasa",
"expire_oneHour": "1 godzina",
"expire_oneDay": "1 dzień",
"expire_sevenDays": "7 dni",
"expire_thirtyDays": "30 dni",
"passwordProtect": "Chroń hasłem",
"password": "Hasło",
"downloadLimit": "Limit pobrań",
"unlimited": "Bez limitu",
"create": "Utwórz udostępnienie",
"cancel": "Anuluj",
"done": "Gotowe",
"copied": "Link skopiowany do schowka"
},
"update": {
"title": "Aktualizacja oprogramowania",
"available": "Dostępna jest wersja {{version}} (masz {{current}}).",
"downloading": "Pobieranie aktualizacji…",
"ready": "Aktualizacja zainstalowana — uruchom ponownie, aby zastosować.",
"upToDate": "Masz najnowszą wersję.",
"failed": "Sprawdzanie aktualizacji nie powiodło się.",
"updateNow": "Aktualizuj teraz",
"later": "Później",
"restart": "Uruchom ponownie",
"close": "Zamknij",
"checkNow": "Sprawdź aktualizacje",
"downloadManually": "Pobierz ręcznie"
}
}

@ -61,7 +61,11 @@
"overwriteRemote": "Перезаписать удалённую версию",
"saveAsNew": "Сохранить как новый файл",
"keepAllRemote": "Оставить все удалённые",
"overwriteAllRemote": "Перезаписать все удалённые"
"overwriteAllRemote": "Перезаписать все удалённые",
"justNow": "Только что",
"minutesAgo": "{{count}} мин назад",
"hoursAgo": "{{count}} ч назад",
"daysAgo": "{{count}} д назад"
},
"settings": {
"title": "Настройки",
@ -138,26 +142,40 @@
"yearsAgo": "{{value}} лет назад"
},
"share": {
"title": "Share link for {{name}}",
"linkReady": "Your share link is ready:",
"permission": "Sharing permissions",
"permViewDownload": "View + Download",
"permPreviewOnly": "Preview only (no download)",
"permAllowEdit": "View + Download + Edit",
"permUploadOnly": "Upload only (drop box)",
"expires": "Expires",
"expire_never": "Never expires",
"expire_oneHour": "1 hour",
"expire_oneDay": "1 day",
"expire_sevenDays": "7 days",
"expire_thirtyDays": "30 days",
"passwordProtect": "Protect with password",
"password": "Password",
"downloadLimit": "Download limit",
"unlimited": "Unlimited",
"create": "Create share",
"cancel": "Cancel",
"done": "Done",
"copied": "Link copied to clipboard"
"title": "Ссылка на {{name}}",
"linkReady": "Ваша ссылка готова:",
"permission": "Права доступа",
"permViewDownload": "Просмотр + Скачивание",
"permPreviewOnly": "Только просмотр (без скачивания)",
"permAllowEdit": "Просмотр + Скачивание + Редактирование",
"permUploadOnly": "Только загрузка (ящик для файлов)",
"expires": "Истекает",
"expire_never": "Без срока",
"expire_oneHour": "1 час",
"expire_oneDay": "1 день",
"expire_sevenDays": "7 дней",
"expire_thirtyDays": "30 дней",
"passwordProtect": "Защитить паролем",
"password": "Пароль",
"downloadLimit": "Лимит скачиваний",
"unlimited": "Без ограничений",
"create": "Создать ссылку",
"cancel": "Отмена",
"done": "Готово",
"copied": "Ссылка скопирована в буфер обмена"
},
"update": {
"title": "Обновление",
"available": "Доступна версия {{version}} (у вас {{current}}).",
"downloading": "Загрузка обновления…",
"ready": "Обновление установлено — перезапустите для применения.",
"upToDate": "У вас последняя версия.",
"failed": "Не удалось проверить обновления.",
"updateNow": "Обновить",
"later": "Позже",
"restart": "Перезапустить",
"close": "Закрыть",
"checkNow": "Проверить обновления",
"downloadManually": "Скачать вручную"
}
}

@ -61,7 +61,11 @@
"overwriteRemote": "覆盖远程版本",
"saveAsNew": "另存为新文件",
"keepAllRemote": "全部保留远程",
"overwriteAllRemote": "全部覆盖远程"
"overwriteAllRemote": "全部覆盖远程",
"justNow": "刚刚",
"minutesAgo": "{{count}} 分钟前",
"hoursAgo": "{{count}} 小时前",
"daysAgo": "{{count}} 天前"
},
"settings": {
"title": "设置",
@ -159,5 +163,19 @@
"cancel": "取消",
"done": "完成",
"copied": "链接已复制到剪贴板"
},
"update": {
"title": "软件更新",
"available": "新版本 {{version}} 可用(当前版本 {{current}})。",
"downloading": "正在下载更新…",
"ready": "更新已安装 — 重启以应用。",
"upToDate": "已是最新版本。",
"failed": "更新检查失败。",
"updateNow": "立即更新",
"later": "稍后",
"restart": "立即重启",
"close": "关闭",
"checkNow": "检查更新",
"downloadManually": "手动下载"
}
}

@ -61,7 +61,11 @@
"overwriteRemote": "覆蓋遠端版本",
"saveAsNew": "另存為新檔案",
"keepAllRemote": "全部保留遠端",
"overwriteAllRemote": "全部覆寫遠端"
"overwriteAllRemote": "全部覆寫遠端",
"justNow": "剛剛",
"minutesAgo": "{{count}} 分鐘前",
"hoursAgo": "{{count}} 小時前",
"daysAgo": "{{count}} 天前"
},
"settings": {
"title": "設定",
@ -138,26 +142,40 @@
"yearsAgo": "{{value}} 年前"
},
"share": {
"title": "Share link for {{name}}",
"linkReady": "Your share link is ready:",
"permission": "Sharing permissions",
"permViewDownload": "View + Download",
"permPreviewOnly": "Preview only (no download)",
"permAllowEdit": "View + Download + Edit",
"permUploadOnly": "Upload only (drop box)",
"expires": "Expires",
"expire_never": "Never expires",
"expire_oneHour": "1 hour",
"expire_oneDay": "1 day",
"expire_sevenDays": "7 days",
"expire_thirtyDays": "30 days",
"passwordProtect": "Protect with password",
"password": "Password",
"downloadLimit": "Download limit",
"unlimited": "Unlimited",
"create": "Create share",
"cancel": "Cancel",
"done": "Done",
"copied": "Link copied to clipboard"
"title": "{{name}} 的分享連結",
"linkReady": "您的分享連結已就緒:",
"permission": "分享權限",
"permViewDownload": "檢視 + 下載",
"permPreviewOnly": "僅預覽 (不可下載)",
"permAllowEdit": "檢視 + 下載 + 編輯",
"permUploadOnly": "僅上傳 (收集箱)",
"expires": "到期",
"expire_never": "永不過期",
"expire_oneHour": "1 小時",
"expire_oneDay": "1 天",
"expire_sevenDays": "7 天",
"expire_thirtyDays": "30 天",
"passwordProtect": "使用密碼保護",
"password": "密碼",
"downloadLimit": "下載次數限制",
"unlimited": "無限制",
"create": "建立分享",
"cancel": "取消",
"done": "完成",
"copied": "連結已複製到剪貼簿"
},
"update": {
"title": "軟體更新",
"available": "新版本 {{version}} 可用(目前版本 {{current}})。",
"downloading": "正在下載更新…",
"ready": "更新已安裝 — 重新啟動以套用。",
"upToDate": "已是最新版本。",
"failed": "更新檢查失敗。",
"updateNow": "立即更新",
"later": "稍後",
"restart": "立即重啟",
"close": "關閉",
"checkNow": "檢查更新",
"downloadManually": "手動下載"
}
}

@ -6,6 +6,7 @@ import {
useMediaQuery,
} from "@mui/material";
import { Routes, Route, HashRouter } from "react-router-dom";
import Update from "./pages/Update";
import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
@ -49,6 +50,7 @@ function App() {
<Route path="/popup" element={<Popup />} />
<Route path="/settings" element={<Settings />} />
<Route path="/share" element={<Share />} />
<Route path="/update" element={<Update />} />
</Routes>
</HashRouter>
</ThemeProvider>

@ -0,0 +1,227 @@
import {
Alert,
Box,
Button,
CircularProgress,
IconButton,
LinearProgress,
Typography,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { openUrl } from "@tauri-apps/plugin-opener";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { DenseFilledTextField } from "../common/StyledComponent";
type UpdateInfo = {
version: string;
current_version: string;
notes?: string | null;
date?: string | null;
};
type Phase = "checking" | "available" | "downloading" | "ready" | "uptodate" | "error";
const RELEASES_URL = "https://github.com/Dvorinka/cloudreve/releases";
export default function Update() {
const { t } = useTranslation();
const [phase, setPhase] = useState<Phase>("checking");
const [info, setInfo] = useState<UpdateInfo | null>(null);
const [progress, setProgress] = useState(0);
const [error, setError] = useState<string | null>(null);
const downloadedRef = useRef(0);
useEffect(() => {
const unProgress = listen<{ downloaded: number; total: number | null }>(
"update-progress",
(e) => {
downloadedRef.current += e.payload.downloaded;
if (e.payload.total) {
setProgress(
Math.min(100, (downloadedRef.current / e.payload.total) * 100)
);
}
}
);
const unFinished = listen("update-finished", () => setPhase("ready"));
invoke<UpdateInfo | null>("check_update")
.then((u) => {
setInfo(u);
setPhase(u ? "available" : "uptodate");
})
.catch((e) => {
setError(String(e));
setPhase("error");
});
return () => {
unProgress.then((f) => f());
unFinished.then((f) => f());
};
}, []);
const startUpdate = async () => {
setPhase("downloading");
setError(null);
downloadedRef.current = 0;
setProgress(0);
try {
await invoke("install_update");
// The ready phase is set by the update-finished listener; if the
// event raced past us, still land on ready.
setPhase((p) => (p === "downloading" ? "ready" : p));
} catch (e) {
setError(String(e));
setPhase("error");
}
};
return (
<Box
sx={{
height: "100vh",
display: "flex",
flexDirection: "column",
overflow: "hidden",
}}
>
<Box
data-tauri-drag-region
sx={{
px: 2,
pt: 1.5,
pb: 1,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
flexShrink: 0,
}}
>
<Typography variant="subtitle1" fontWeight={600} noWrap>
{t("update.title")}
</Typography>
<IconButton
size="small"
onClick={() => getCurrentWindow().close()}
sx={{ WebkitAppRegion: "no-drag", appRegion: "no-drag" }}
>
<CloseIcon fontSize="small" />
</IconButton>
</Box>
<Box
sx={{
flex: 1,
overflow: "auto",
px: 3,
pb: 2,
display: "flex",
flexDirection: "column",
justifyContent: "center",
gap: 1.5,
}}
>
{phase === "checking" && (
<Box sx={{ display: "flex", justifyContent: "center", py: 4 }}>
<CircularProgress size={28} />
</Box>
)}
{phase === "available" && info && (
<>
<Typography variant="body1" fontWeight={600}>
{t("update.available", {
version: info.version,
current: info.current_version,
})}
</Typography>
{info.notes && (
<DenseFilledTextField
fullWidth
multiline
minRows={3}
maxRows={6}
value={info.notes}
slotProps={{ input: { readOnly: true } }}
/>
)}
</>
)}
{phase === "downloading" && (
<>
<Typography variant="body2" color="text.secondary">
{t("update.downloading")}
</Typography>
<LinearProgress variant="determinate" value={progress} />
<Typography variant="caption" color="text.secondary">
{Math.round(progress)}%
</Typography>
</>
)}
{phase === "ready" && (
<Alert severity="success">{t("update.ready")}</Alert>
)}
{phase === "uptodate" && (
<Alert severity="info">{t("update.upToDate")}</Alert>
)}
{phase === "error" && (
<Alert severity="error">{error ?? t("update.failed")}</Alert>
)}
</Box>
<Box
sx={{
px: 3,
py: 2,
display: "flex",
justifyContent: "flex-end",
gap: 1,
flexShrink: 0,
}}
>
{phase === "available" && (
<>
<Button onClick={() => getCurrentWindow().close()}>
{t("update.later")}
</Button>
<Button variant="contained" onClick={startUpdate}>
{t("update.updateNow")}
</Button>
</>
)}
{phase === "ready" && (
<Button
variant="contained"
onClick={() => invoke("restart_app")}
>
{t("update.restart")}
</Button>
)}
{(phase === "uptodate" || phase === "checking") && (
<Button onClick={() => getCurrentWindow().close()}>
{t("update.close")}
</Button>
)}
{phase === "error" && (
<>
<Button onClick={() => getCurrentWindow().close()}>
{t("update.close")}
</Button>
<Button variant="outlined" onClick={() => openUrl(RELEASES_URL)}>
{t("update.downloadManually")}
</Button>
</>
)}
</Box>
</Box>
);
}

@ -1,3 +1,5 @@
import i18n from "../../i18n";
export function formatBytes(bytes: number): string {
if (bytes === 0) return "0 B";
const k = 1024;
@ -10,10 +12,12 @@ export function formatRelativeTime(timestamp: number): string {
const now = Date.now() / 1000;
const diff = now - timestamp;
if (diff < 60) return "Just now";
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
return `${Math.floor(diff / 86400)}d ago`;
if (diff < 60) return i18n.t("popup.justNow");
if (diff < 3600)
return i18n.t("popup.minutesAgo", { count: Math.floor(diff / 60) });
if (diff < 86400)
return i18n.t("popup.hoursAgo", { count: Math.floor(diff / 3600) });
return i18n.t("popup.daysAgo", { count: Math.floor(diff / 86400) });
}
export function getFileName(path: string): string {

@ -1,10 +1,12 @@
import { useEffect, useState } from "react";
import { Box, Typography, Link, Chip, Stack } from "@mui/material";
import { Box, Typography, Link, Chip, Stack, Button } from "@mui/material";
import { useTranslation } from "react-i18next";
import { getVersion } from "@tauri-apps/api/app";
import { invoke } from "@tauri-apps/api/core";
import GitHubIcon from "@mui/icons-material/GitHub";
import BugReportIcon from "@mui/icons-material/BugReportRounded";
import ForumIcon from "@mui/icons-material/ForumRounded";
import SystemUpdateAltIcon from "@mui/icons-material/SystemUpdateAlt";
import logo from "../../assets/cloudreve.svg";
import { HomeRounded } from "@mui/icons-material";
@ -33,12 +35,12 @@ export default function AboutSection() {
{
icon: <GitHubIcon fontSize="small" />,
label: "GitHub",
href: "https://github.com/cloudreve/desktop",
href: "https://github.com/Dvorinka/cloudreve",
},
{
icon: <BugReportIcon fontSize="small" />,
label: t("about.reportIssue"),
href: "https://github.com/cloudreve/desktop/issues",
href: "https://github.com/Dvorinka/cloudreve/issues",
},
{
icon: <ForumIcon fontSize="small" />,
@ -79,6 +81,16 @@ export default function AboutSection() {
</Box>
</Stack>
<Button
variant="outlined"
size="small"
startIcon={<SystemUpdateAltIcon />}
onClick={() => invoke("show_update_window")}
sx={{ width: "fit-content", mb: 2 }}
>
{t("update.checkNow")}
</Button>
<Stack spacing={1}>
{links.map((link) => (
<Link

@ -599,7 +599,9 @@
"viewSetting": "اعدادات العرض",
"saved": "تم الحفظ",
"notSet": "لم يتم تعيينه",
"deleteViewSetting": "حذف اعدادات العرض"
"deleteViewSetting": "حذف اعدادات العرض",
"anonymous": "مجهول",
"shareTargetsCount": "{{count}} من الوجهات"
},
"uploader": {
"fileCopyName": "نسخة من ",
@ -979,7 +981,12 @@
"validDurationDays": "{{num}} يوم",
"reportTarget": "الذي تريد الإبلاغ عنه",
"reportReason": "السبب",
"reportReasonOptions": ["انتهاك حقوق الطبع والنشر", "محتوى ضار", "غير المرغوب به", "أخرى"],
"reportReasonOptions": [
"انتهاك حقوق الطبع والنشر",
"محتوى ضار",
"غير المرغوب به",
"أخرى"
],
"reportDescription": "وصف إضافي",
"reportAbuseSuccess": "تم رفع التقرير."
}

@ -102,5 +102,6 @@
"50005": "خطأ داخلي.",
"50010": "العقدة المطلوبة غير متوفرة.",
"50011": "فشل في الاستعلام عن البيانات الوصفية للملف."
}
},
"save": "حفظ"
}

@ -1192,7 +1192,6 @@
"allowedHostsDes": "أسماء المضيفين أو عناوين IP المحددة (واحدة في كل سطر) التي يحق لها تجاوز التحقق من سلامة الرابط على هذه العقدة. يتم السماح دائمًا بأسماء المضيفات الخاصة بالموقع. استخدم هذه الميزة لإدراج اسم مضيف داخلي معين في القائمة البيضاء، بحيث يسمح للمستخدمين بالتنزيل منه.",
"allowedCIDRs": "نطاقات عناوين IP المسموح بها (CIDR)",
"allowedCIDRsDes": "كتل CIDR (واحدة في كل سطر، IPv4 أو IPv6) التي يتم معاملة عناوين IP الخاصة بها على أنها آمنة حتى لو كان من المفترض رفضها عادةً. استخدم هذه الميزة لعرض شبكة فرعية LAN — على سبيل المثال جهاز NAS على 192.168.10.0/24 — دون تعطيل بقية إجراءات الفحص الأمني.",
"webUIEndpoint": "نقطة نهاية Web UI",
"webUIEndpointDes": "نقطة نهاية Web UI لـ qBittorrent، مثل <0>http://127.0.0.1:8080/</0>.",
"tempPath": "مجلد التنزيل المؤقت",
"tempPathDes": "المجلد على العقدة الذي يستخدمه Aria2 كمجلد تنزيل مؤقت. تحتاج عملية Cloudreve على العقدة إلى أذونات القراءة والكتابة والتنفيذ على هذا المجلد، ويجب أن يتمكن برنامج التنزيل أيضًا من الوصول إليه. اتركه فارغًا لاستخدام مسار الملفات المؤقتة الافتراضي.",
@ -1422,7 +1421,8 @@
"otherConditions": "شروط أخرى",
"shareLinkExisted": "لديه رابط مشاركة",
"directLinkExisted": "لديه رابط مباشر",
"isUploading": "قيد التحميل"
"isUploading": "قيد التحميل",
"selectUser": "اختر مستخدمًا"
},
"entity": {
"refenenceCount": "عدد المراجع",

@ -529,7 +529,7 @@
"everyone": "Alle anderen",
"otherGroup": "Andere Benutzergruppen",
"sameGroup": "Dieselbe Benutzergruppe wie ich",
"anonymous": "Anonyme Besucher",
"anonymous": "Anonym",
"noResults": "Keine Ergebnisse",
"searchGroupUser": "Benutzer oder Benutzergruppen suchen...",
"resetToDefault": "Auf Standard zurücksetzen",
@ -664,7 +664,8 @@
"viewSetting": "Ansichtseinstellungen",
"saved": "Gespeichert",
"notSet": "Nicht gesetzt",
"deleteViewSetting": "Ansichtseinstellung löschen"
"deleteViewSetting": "Ansichtseinstellung löschen",
"shareTargetsCount": "{{count}} Ziele"
},
"uploader": {
"fileCopyName": "Kopie_",
@ -1108,7 +1109,12 @@
"report": "Missbrauch melden",
"reportTarget": "Meldeobjekt",
"reportReason": "Grund",
"reportReasonOptions": ["Urheberrechtsverletzung", "Schädlicher Inhalt", "Spam", "Andere"],
"reportReasonOptions": [
"Urheberrechtsverletzung",
"Schädlicher Inhalt",
"Spam",
"Andere"
],
"reportDescription": "Zusätzliche Beschreibung",
"reportAbuseSuccess": "Meldung eingereicht",
"migrateStoragePolicy": "Speicherrichtlinie übertragen",

@ -116,5 +116,6 @@
"40072": "Sie sind Administrator und können keine andere Gruppe erwerben.",
"40084": "Ihr Konto ist passwortlos. Sie müssen mindestens ein verknüpftes Konto behalten.",
"40085": "Der Gesamtbetrag dieser Bestellung ist zu gering für den Checkout."
}
},
"save": "Speichern"
}

@ -1428,7 +1428,8 @@
"otherConditions": "Andere Bedingungen",
"shareLinkExisted": "Hat Freigabe-Link",
"directLinkExisted": "Hat direkten Link",
"isUploading": "Wird hochgeladen"
"isUploading": "Wird hochgeladen",
"selectUser": "Benutzer auswählen"
},
"entity": {
"refenenceCount": "Referenzzähler",
@ -1474,7 +1475,8 @@
"rawContent": "Roher Inhalt",
"confirmDelete": "Sind Sie sicher, dass Sie dieses Ereignis löschen möchten?",
"deleteXEvents": "{{num}} Ereignisse löschen",
"confirmBatchDelete": "Sind Sie sicher, dass Sie {{num}} Ereignisse löschen möchten?"
"confirmBatchDelete": "Sind Sie sicher, dass Sie {{num}} Ereignisse löschen möchten?",
"allEventTypes": "Alle Ereignistypen"
},
"share": {
"confirmBatchDelete": "Sind Sie sicher, dass Sie {{num}} Freigaben löschen möchten?",

@ -698,12 +698,13 @@
"notSet": "Not set",
"deleteViewSetting": "Delete view setting",
"shareTargets": "Share targets",
"shareTargetsCount_one": "{{count}} file selected",
"shareTargetsCount_other": "{{count}} files selected",
"shareTargetsCount_one": "{{count}} target",
"shareTargetsCount_other": "{{count}} targets",
"publicListing": "List in public directory",
"publicListingDes": "Show this share on the Discover page, visible to anyone including anonymous visitors. Not available for password-protected shares.",
"hideReadme": "Hide readme file",
"hideReadmeDes": "Keep the README file itself out of the share listing. Visitors still see the rendered readme."
"hideReadmeDes": "Keep the README file itself out of the share listing. Visitors still see the rendered readme.",
"anonymous": "Anonymous"
},
"uploader": {
"fileCopyName": "Copy of ",

@ -105,5 +105,6 @@
"50010": "Desired node is unavailable.",
"50011": "Failed to query file metadata."
},
"delete": "Delete"
"delete": "Delete",
"save": "Save"
}

@ -1611,7 +1611,8 @@
"isUploading": "Is uploading",
"deletedState": "Deleted state",
"liveOnly": "Live files only",
"trashOnly": "Trash only"
"trashOnly": "Trash only",
"selectUser": "Select user"
},
"entity": {
"refenenceCount": "Reference count",

@ -529,7 +529,7 @@
"everyone": "Todos los demás",
"otherGroup": "Otros grupos",
"sameGroup": "Mismo grupo que yo",
"anonymous": "Visitantes anónimos",
"anonymous": "Anónimo",
"noResults": "Sin resultados",
"searchGroupUser": "Buscar emails o grupos...",
"resetToDefault": "Restablecer a predeterminado",
@ -660,7 +660,8 @@
"skipSoftDelete": "Eliminar permanentemente",
"skipSoftDeleteDes": "Omitir mover a papelera, eliminar permanentemente",
"unlinkOnly": "Mantener archivos físicos",
"unlinkOnlyDes": "Eliminar solo registros de archivos, los archivos físicos no serán eliminados."
"unlinkOnlyDes": "Eliminar solo registros de archivos, los archivos físicos no serán eliminados.",
"shareTargetsCount": "{{count}} destinos"
},
"uploader": {
"fileCopyName": "Copia de ",
@ -1106,7 +1107,12 @@
"report": "Reportar abuso",
"reportTarget": "Objeto del reporte",
"reportReason": "Razón",
"reportReasonOptions": ["Infracción de derechos de autor", "Contenido dañino", "Información basura", "Otro"],
"reportReasonOptions": [
"Infracción de derechos de autor",
"Contenido dañino",
"Información basura",
"Otro"
],
"reportDescription": "Descripción adicional",
"reportAbuseSuccess": "Reporte enviado",
"migrateStoragePolicy": "Transferir política de almacenamiento",

@ -116,5 +116,6 @@
"40072": "Eres administrador, no puedes comprar otro grupo.",
"40084": "Tu cuenta no tiene contraseña, debes mantener al menos una cuenta vinculada.",
"40085": "El monto total de esta orden es demasiado pequeño para el pago."
}
},
"save": "Guardar"
}

@ -1421,7 +1421,8 @@
"otherConditions": "Otras condiciones",
"shareLinkExisted": "Tiene enlace de compartir",
"directLinkExisted": "Tiene enlace directo",
"isUploading": "Está subiendo"
"isUploading": "Está subiendo",
"selectUser": "Seleccionar usuario"
},
"entity": {
"refenenceCount": "Conteo de referencia",
@ -1467,7 +1468,8 @@
"rawContent": "Contenido sin procesar",
"confirmDelete": "¿Estás seguro de que quieres eliminar este evento?",
"deleteXEvents": "Eliminar {{num}} eventos",
"confirmBatchDelete": "¿Estás seguro de que quieres eliminar {{num}} eventos?"
"confirmBatchDelete": "¿Estás seguro de que quieres eliminar {{num}} eventos?",
"allEventTypes": "Todos los tipos de eventos"
},
"share": {
"confirmBatchDelete": "¿Estás seguro de que quieres eliminar {{num}} compartidos?",

@ -529,7 +529,7 @@
"everyone": "Tous les autres",
"otherGroup": "Autres groupes",
"sameGroup": "Même groupe que moi",
"anonymous": "Visiteurs anonymes",
"anonymous": "Anonyme",
"noResults": "Aucun résultat",
"searchGroupUser": "Rechercher des e-mails ou des groupes...",
"resetToDefault": "Réinitialiser par défaut",
@ -664,7 +664,8 @@
"viewSetting": "Paramètre de vue",
"saved": "Enregistré",
"notSet": "Non défini",
"deleteViewSetting": "Supprimer le paramètre de vue"
"deleteViewSetting": "Supprimer le paramètre de vue",
"shareTargetsCount": "{{count}} cibles"
},
"uploader": {
"fileCopyName": "Copie de ",
@ -1110,7 +1111,12 @@
"report": "Signaler un abus",
"reportTarget": "Cible du signalement",
"reportReason": "Raison",
"reportReasonOptions": ["Violation de droits d'auteur", "Contenu nuisible", "Spam", "Autre"],
"reportReasonOptions": [
"Violation de droits d'auteur",
"Contenu nuisible",
"Spam",
"Autre"
],
"reportDescription": "Description supplémentaire",
"reportAbuseSuccess": "Signalement soumis.",
"migrateStoragePolicy": "Migrer la stratégie de stockage",

@ -116,5 +116,6 @@
"40072": "Vous êtes administrateur, vous ne pouvez pas acheter d'autre groupe.",
"40084": "Votre compte est sans mot de passe, vous devez conserver au moins un compte lié.",
"40085": "Le montant total de cette commande est trop petit pour le paiement."
}
},
"save": "Enregistrer"
}

@ -1421,7 +1421,8 @@
"otherConditions": "Autres conditions",
"shareLinkExisted": "A un lien de partage",
"directLinkExisted": "A un lien direct",
"isUploading": "En cours de téléchargement"
"isUploading": "En cours de téléchargement",
"selectUser": "Sélectionner un utilisateur"
},
"entity": {
"refenenceCount": "Nombre de références",
@ -1467,7 +1468,8 @@
"rawContent": "Contenu brut",
"confirmDelete": "Êtes-vous sûr de vouloir supprimer cet événement ?",
"deleteXEvents": "Supprimer {{num}} événements",
"confirmBatchDelete": "Êtes-vous sûr de vouloir supprimer {{num}} événements ?"
"confirmBatchDelete": "Êtes-vous sûr de vouloir supprimer {{num}} événements ?",
"allEventTypes": "Tous les types d'événements"
},
"share": {
"confirmBatchDelete": "Êtes-vous sûr de vouloir supprimer {{num}} partages ?",

@ -529,7 +529,7 @@
"everyone": "Tutti gli altri",
"otherGroup": "Altri gruppi",
"sameGroup": "Stesso gruppo di me",
"anonymous": "Visitatori anonimi",
"anonymous": "Anonimo",
"noResults": "Nessun risultato",
"searchGroupUser": "Cerca email o gruppi...",
"resetToDefault": "Ripristina predefinito",
@ -660,7 +660,8 @@
"skipSoftDelete": "Elimina permanentemente",
"skipSoftDeleteDes": "Salta lo spostamento nel cestino, elimina permanentemente",
"unlinkOnly": "Mantieni file fisici",
"unlinkOnlyDes": "Elimina solo i record dei file, i file fisici non saranno eliminati."
"unlinkOnlyDes": "Elimina solo i record dei file, i file fisici non saranno eliminati.",
"shareTargetsCount": "{{count}} destinazioni"
},
"uploader": {
"fileCopyName": "Copia di ",
@ -1106,7 +1107,12 @@
"report": "Segnala abuso",
"reportTarget": "Target segnalazione",
"reportReason": "Motivo",
"reportReasonOptions": ["Violazione copyright", "Contenuto dannoso", "Spam", "Altro"],
"reportReasonOptions": [
"Violazione copyright",
"Contenuto dannoso",
"Spam",
"Altro"
],
"reportDescription": "Descrizione aggiuntiva",
"reportAbuseSuccess": "Segnalazione inviata.",
"migrateStoragePolicy": "Migra policy di archiviazione",

@ -116,5 +116,6 @@
"40072": "Sei amministratore, non puoi acquistare altri gruppi.",
"40084": "Il tuo account è senza password, devi mantenere almeno un account collegato.",
"40085": "L'importo totale di questo ordine è troppo piccolo per il checkout."
}
},
"save": "Salva"
}

@ -1420,7 +1420,8 @@
"otherConditions": "Altre condizioni",
"shareLinkExisted": "Ha link di condivisione",
"directLinkExisted": "Ha link diretto",
"isUploading": "In caricamento"
"isUploading": "In caricamento",
"selectUser": "Seleziona utente"
},
"entity": {
"refenenceCount": "Conteggio riferimenti",
@ -1466,7 +1467,8 @@
"rawContent": "Contenuto raw",
"confirmDelete": "Sei sicuro di voler eliminare questo evento?",
"deleteXEvents": "Elimina {{num}} eventi",
"confirmBatchDelete": "Sei sicuro di voler eliminare {{num}} eventi?"
"confirmBatchDelete": "Sei sicuro di voler eliminare {{num}} eventi?",
"allEventTypes": "Tutti i tipi di evento"
},
"share": {
"confirmBatchDelete": "Sei sicuro di voler eliminare {{num}} condivisioni?",

@ -611,7 +611,9 @@
"viewSetting": "表示設定",
"saved": "保存されました",
"notSet": "未設定",
"deleteViewSetting": "表示設定の削除"
"deleteViewSetting": "表示設定の削除",
"anonymous": "匿名",
"shareTargetsCount": "{{count}} 件の対象"
},
"uploader": {
"fileCopyName": "コピー_",
@ -994,7 +996,12 @@
"validDurationDays": "{{num}} 日",
"reportTarget": "報告対象",
"reportReason": "理由",
"reportReasonOptions": ["著作権侵害", "有害なコンテンツ", "スパム", "その他"],
"reportReasonOptions": [
"著作権侵害",
"有害なコンテンツ",
"スパム",
"その他"
],
"reportDescription": "詳細説明",
"reportAbuseSuccess": "報告を受け付けました"
}

@ -102,5 +102,6 @@
"50005": "内部エラーが発生しました ({{message}})",
"50010": "対象ノードが利用できません",
"50011": "ファイルメタデータの取得に失敗しました"
}
},
"save": "保存"
}

@ -1422,7 +1422,8 @@
"otherConditions": "その他の条件",
"shareLinkExisted": "共有リンクが存在",
"directLinkExisted": "直接リンクが存在",
"isUploading": "アップロード中"
"isUploading": "アップロード中",
"selectUser": "ユーザーを選択"
},
"entity": {
"refenenceCount": "参照回数",
@ -1442,7 +1443,7 @@
"cleanupNotAfter": "この日付以前",
"cleanupEventTypes": "イベント種類",
"cleanupEventTypesDes": "イベント種類を選択してください。空の場合はすべての種類を削除します。",
"allEventTypes": "すべてのイベントタイプ",
"allEventTypes": "すべてのイベント種別",
"initiator": "発信者",
"event": "イベント",
"userID": "ユーザーID",

@ -529,7 +529,7 @@
"everyone": "기타 모든 사람",
"otherGroup": "다른 사용자 그룹",
"sameGroup": "나와 같은 사용자 그룹",
"anonymous": "익명 방문자",
"anonymous": "익명",
"noResults": "결과 없음",
"searchGroupUser": "사용자 또는 사용자 그룹 검색...",
"resetToDefault": "기본값으로 재설정",
@ -664,7 +664,8 @@
"viewSetting": "보기 설정",
"saved": "저장됨",
"notSet": "설정되지 않음",
"deleteViewSetting": "보기 설정 삭제"
"deleteViewSetting": "보기 설정 삭제",
"shareTargetsCount": "대상 {{count}}개"
},
"uploader": {
"fileCopyName": "사본_",
@ -1110,7 +1111,12 @@
"report": "남용 신고",
"reportTarget": "신고 대상",
"reportReason": "이유",
"reportReasonOptions": ["침해", "유해 콘텐츠", "스팸 정보", "기타"],
"reportReasonOptions": [
"침해",
"유해 콘텐츠",
"스팸 정보",
"기타"
],
"reportDescription": "추가 설명",
"reportAbuseSuccess": "신고가 제출되었습니다",
"migrateStoragePolicy": "저장 정책 전송",

@ -116,5 +116,6 @@
"40072": "관리자는 다른 사용자 그룹으로 업그레이드할 수 없습니다",
"40084": "귀하의 계정은 비밀번호가 없는 계정이므로 최소 하나의 연결된 계정을 유지해야 합니다",
"40085": "주문 금액이 너무 작아 결제를 계속할 수 없습니다"
}
},
"save": "저장"
}

@ -1421,7 +1421,8 @@
"otherConditions": "기타 조건",
"shareLinkExisted": "공유 링크 존재",
"directLinkExisted": "중계 직접 링크 존재",
"isUploading": "업로드 중"
"isUploading": "업로드 중",
"selectUser": "사용자 선택"
},
"entity": {
"refenenceCount": "참조 횟수",
@ -1467,7 +1468,8 @@
"rawContent": "원본 기록",
"confirmDelete": "이 이벤트를 삭제하시겠습니까?",
"deleteXEvents": "{{num}}개 이벤트 삭제",
"confirmBatchDelete": "{{num}}개 이벤트를 삭제하시겠습니까?"
"confirmBatchDelete": "{{num}}개 이벤트를 삭제하시겠습니까?",
"allEventTypes": "모든 이벤트 유형"
},
"share": {
"confirmBatchDelete": "{{num}}개 공유를 삭제하시겠습니까?",

@ -611,7 +611,9 @@
"viewSetting": "Ustawienia widoku",
"saved": "Zapisano",
"notSet": "Nieustawione",
"deleteViewSetting": "Usuń ustawienia widoku"
"deleteViewSetting": "Usuń ustawienia widoku",
"anonymous": "Anonimowy",
"shareTargetsCount": "{{count}} cele"
},
"uploader": {
"fileCopyName": "Kopia ",
@ -994,7 +996,12 @@
"validDurationDays": "{{num}} dni",
"reportTarget": "Cel zgłoszenia",
"reportReason": "Powód",
"reportReasonOptions": ["Naruszenie praw autorskich", "Szkodliwa treść", "Spam", "Inne"],
"reportReasonOptions": [
"Naruszenie praw autorskich",
"Szkodliwa treść",
"Spam",
"Inne"
],
"reportDescription": "Dodatkowy opis",
"reportAbuseSuccess": "Zgłoszenie przesłane."
}

@ -102,5 +102,6 @@
"50005": "Błąd wewnętrzny.",
"50010": "Żądany węzeł jest niedostępny.",
"50011": "Nie udało się pobrać metadanych pliku."
}
},
"save": "Zapisz"
}

@ -1422,7 +1422,8 @@
"otherConditions": "Inne warunki",
"shareLinkExisted": "Posiada link udostępniania",
"directLinkExisted": "Posiada link bezpośredni",
"isUploading": "Jest przesyłany"
"isUploading": "Jest przesyłany",
"selectUser": "Wybierz użytkownika"
},
"entity": {
"refenenceCount": "Liczba odniesień",

@ -529,7 +529,7 @@
"everyone": "Todos os outros",
"otherGroup": "Outros grupos",
"sameGroup": "Mesmo grupo que eu",
"anonymous": "Visitantes anônimos",
"anonymous": "Anônimo",
"noResults": "Nenhum resultado",
"searchGroupUser": "Buscar por e-mails ou grupos...",
"resetToDefault": "Redefinir para padrão",
@ -664,7 +664,8 @@
"viewSetting": "Configuração de visualização",
"saved": "Salvo",
"notSet": "Não definido",
"deleteViewSetting": "Excluir configuração de visualização"
"deleteViewSetting": "Excluir configuração de visualização",
"shareTargetsCount": "{{count}} destinos"
},
"uploader": {
"fileCopyName": "Cópia de ",
@ -1110,7 +1111,12 @@
"report": "Denunciar abuso",
"reportTarget": "Alvo da denúncia",
"reportReason": "Razão",
"reportReasonOptions": ["Violação de direitos autorais", "Conteúdo prejudicial", "Spam", "Outro"],
"reportReasonOptions": [
"Violação de direitos autorais",
"Conteúdo prejudicial",
"Spam",
"Outro"
],
"reportDescription": "Descrição adicional",
"reportAbuseSuccess": "Denúncia enviada.",
"migrateStoragePolicy": "Migrar política de armazenamento",

@ -116,5 +116,6 @@
"40072": "Você é administrador, não pode comprar outro grupo.",
"40084": "Sua conta não possui senha, você deve manter pelo menos uma conta vinculada.",
"40085": "O valor total deste pedido é muito pequeno para checkout."
}
},
"save": "Salvar"
}

@ -1421,7 +1421,8 @@
"otherConditions": "Outras condições",
"shareLinkExisted": "Tem link de compartilhamento",
"directLinkExisted": "Tem link direto",
"isUploading": "Está enviando"
"isUploading": "Está enviando",
"selectUser": "Selecionar usuário"
},
"entity": {
"refenenceCount": "Contagem de referência",
@ -1467,7 +1468,8 @@
"rawContent": "Conteúdo bruto",
"confirmDelete": "Tem certeza de que deseja excluir este evento?",
"deleteXEvents": "Excluir {{num}} eventos",
"confirmBatchDelete": "Tem certeza de que deseja excluir {{num}} eventos?"
"confirmBatchDelete": "Tem certeza de que deseja excluir {{num}} eventos?",
"allEventTypes": "Todos os tipos de evento"
},
"share": {
"confirmBatchDelete": "Tem certeza de que deseja excluir {{num}} compartilhamentos?",

@ -529,7 +529,7 @@
"everyone": "Все остальные",
"otherGroup": "Другие группы пользователей",
"sameGroup": "Та же группа пользователей, что и я",
"anonymous": "Анонимные посетители",
"anonymous": "Анонимный",
"noResults": "Нет результатов",
"searchGroupUser": "Поиск пользователей или групп пользователей...",
"resetToDefault": "Сбросить к значениям по умолчанию",
@ -664,7 +664,8 @@
"viewSetting": "Настройки просмотра",
"saved": "Сохранено",
"notSet": "Не установлено",
"deleteViewSetting": "Удалить настройки просмотра"
"deleteViewSetting": "Удалить настройки просмотра",
"shareTargetsCount": "{{count}} целей"
},
"uploader": {
"fileCopyName": "Копия_",
@ -1110,7 +1111,12 @@
"report": "Сообщить о злоупотреблении",
"reportTarget": "Объект жалобы",
"reportReason": "Причина",
"reportReasonOptions": ["Нарушение авторских прав", "Вредоносный контент", "Спам", "Другое"],
"reportReasonOptions": [
"Нарушение авторских прав",
"Вредоносный контент",
"Спам",
"Другое"
],
"reportDescription": "Дополнительное описание",
"reportAbuseSuccess": "Жалоба подана",
"migrateStoragePolicy": "Перенести политику хранения",

@ -116,5 +116,6 @@
"40072": "Администратор не может перейти в другую группу пользователей",
"40084": "Ваш аккаунт является беспарольным, вы должны оставить хотя бы один связанный аккаунт",
"40085": "Сумма заказа слишком мала для продолжения оплаты"
}
},
"save": "Сохранить"
}

@ -1422,7 +1422,8 @@
"otherConditions": "Другие условия",
"shareLinkExisted": "Существует ссылка для общего доступа",
"directLinkExisted": "Существует прямая ссылка",
"isUploading": "Загружается"
"isUploading": "Загружается",
"selectUser": "Выбрать пользователя"
},
"entity": {
"refenenceCount": "Количество ссылок",
@ -1468,7 +1469,8 @@
"rawContent": "Исходная запись",
"confirmDelete": "Подтвердите удаление этого события?",
"deleteXEvents": "Удалить {{num}} событий",
"confirmBatchDelete": "Подтвердите удаление {{num}} событий?"
"confirmBatchDelete": "Подтвердите удаление {{num}} событий?",
"allEventTypes": "Все типы событий"
},
"share": {
"confirmBatchDelete": "Подтвердите удаление {{num}} общих доступов?",

@ -703,7 +703,9 @@
"publicListing": "收录到公开目录",
"publicListingDes": "将此分享显示在“发现”页,任何人(包括未登录访客)可见。带密码的分享不可用。",
"hideReadme": "隐藏 README 文件",
"hideReadmeDes": "在分享文件列表中隐藏 README 文件本身,访客仍可看到渲染后的说明内容。"
"hideReadmeDes": "在分享文件列表中隐藏 README 文件本身,访客仍可看到渲染后的说明内容。",
"anonymous": "匿名",
"shareTargetsCount": "{{count}} 个目标"
},
"uploader": {
"fileCopyName": "副本_",

@ -106,5 +106,6 @@
"50011": "文件元信息查询失败"
},
"delete": "删除",
"40017": "该账号已被封禁。{{message}}"
"40017": "该账号已被封禁。{{message}}",
"save": "保存"
}

@ -1611,7 +1611,8 @@
"isUploading": "上传中",
"deletedState": "删除状态",
"liveOnly": "仅未删除",
"trashOnly": "仅回收站"
"trashOnly": "仅回收站",
"selectUser": "选择用户"
},
"entity": {
"refenenceCount": "引用次数",
@ -1666,7 +1667,8 @@
"noEvents": "暂无事件记录",
"confirmDelete": "确认要删除此事件?",
"deleteXEvents": "删除 {{num}} 个事件",
"confirmBatchDelete": "确认要删除 {{num}} 个事件?"
"confirmBatchDelete": "确认要删除 {{num}} 个事件?",
"allEventTypes": "全部事件类型"
},
"share": {
"confirmBatchDelete": "确认要删除 {{num}} 个分享?",

@ -611,7 +611,9 @@
"viewSetting": "視圖設定",
"saved": "已保存",
"notSet": "未設定",
"deleteViewSetting": "刪除視圖設定"
"deleteViewSetting": "刪除視圖設定",
"anonymous": "匿名",
"shareTargetsCount": "{{count}} 個目標"
},
"uploader": {
"fileCopyName": "副本_",
@ -991,7 +993,12 @@
"validDurationDays": "{{num}} 天",
"reportTarget": "舉報對象",
"reportReason": "原因",
"reportReasonOptions": ["侵權", "有害內容", "垃圾訊息", "其他"],
"reportReasonOptions": [
"侵權",
"有害內容",
"垃圾訊息",
"其他"
],
"reportDescription": "補充說明",
"reportAbuseSuccess": "舉報已提交"
}

@ -102,5 +102,6 @@
"50005": "內部錯誤 ({{message}})",
"50010": "目標節點不可用",
"50011": "檔案元資訊查詢失敗"
}
},
"save": "儲存"
}

@ -1422,7 +1422,8 @@
"otherConditions": "其他條件",
"shareLinkExisted": "有分享連結",
"directLinkExisted": "有中轉直鏈",
"isUploading": "上傳中"
"isUploading": "上傳中",
"selectUser": "選擇使用者"
},
"entity": {
"refenenceCount": "引用次數",
@ -1442,7 +1443,7 @@
"cleanupNotAfter": "在此日期之前",
"cleanupEventTypes": "事件類型",
"cleanupEventTypesDes": "選擇要清理的事件類型,留空表示清理所有類型。",
"allEventTypes": "所有事件類型",
"allEventTypes": "全部事件類型",
"initiator": "發起者",
"event": "事件",
"userID": "使用者 ID",

@ -61,7 +61,7 @@ export const getTaskStatusText = (status: TaskStatus, t: TFunction) => {
case TaskStatus.completed:
return t("application:setting.finished");
default:
return t("application:uplaoder.unknownStatus");
return t("application:uploader.unknownStatus");
}
};

@ -125,7 +125,7 @@ const Uploader = () => {
if (e instanceof UploaderError) {
enqueueSnackbar(e.Message(), { variant: "warning" });
} else {
enqueueSnackbar(t("uploader:unknownError", { msg: e.message }), {
enqueueSnackbar(t("application:uploader.unknownError", { msg: e.message }), {
variant: "error",
});
}

Loading…
Cancel
Save