Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
da0d3d0
[gui] GUI localizations infrastructure
sharder996 Apr 6, 2026
b5d5e03
[gui] Extract strings out of BeforeQuitDialog
sharder996 Apr 7, 2026
e836c59
[gui] Ignore generated localization files
sharder996 Apr 7, 2026
5453776
[format] Add arb files to editor config
sharder996 Apr 7, 2026
2cf9f12
[gui] Localize vm action labels
sharder996 Apr 7, 2026
64de2ac
[gui] Apply dart fixes
sharder996 Apr 7, 2026
e57e636
[gui] Extract strings from bulk actions
sharder996 Apr 7, 2026
2435a0d
[gui] Extract strings from `NoVms`
sharder996 Apr 7, 2026
69109b4
[gui] Extract strings from `SearchBox`
sharder996 Apr 7, 2026
cffb0b6
[gui] Move string resolution to resource file
sharder996 Apr 7, 2026
04f27ff
[gui] Extract strings from `HelpScreen`
sharder996 Apr 7, 2026
5c56b6b
[gui] Extract strings from sidebar
sharder996 Apr 7, 2026
bcdf550
[gui] Extract strings from `DeleteInstanceDialog`
sharder996 Apr 7, 2026
4cbad63
[gui] Edit `DeleteInstanceDialog` usage
sharder996 Apr 7, 2026
d32aa12
[gui] Extract strings from `CloseTerminalDialog`
sharder996 Apr 7, 2026
693044c
[gui] Remove strings from `Notifier` classes
sharder996 Apr 7, 2026
a8729f4
[gui] Extract strings from GUI settings classes
sharder996 Apr 7, 2026
4e6659f
[gui] Extrace strings from vm_details classes
sharder996 Apr 7, 2026
bb92b4d
[gui] Extract strings from `vm_table` directory
sharder996 Apr 7, 2026
3344c69
[gui] Extract some more localization strings
sharder996 Apr 8, 2026
7e79fec
[gui] Extract strings from catalogue classes
sharder996 Apr 8, 2026
8af3cda
[gui] Consolidate identical strings
sharder996 Apr 8, 2026
9b47259
[gui] Extract strings from `tray_menu`
sharder996 Apr 16, 2026
bb7b18f
[gui] Extract strings from `vm_details`
sharder996 Apr 16, 2026
40c77fa
[gui] Localize system update notification strings
sharder996 Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ insert_final_newline = true
max_line_length = 80
trim_trailing_whitespace = true

[*.{json,json5,proto}]
[*.{json,json5,proto,arb}]
indent_size = 4
indent_style = space
insert_final_newline = true
Expand Down
3 changes: 3 additions & 0 deletions src/client/gui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ app.*.symbols
.gclient_previous_custom_vars
.gclient_previous_sync_commits

# Generated localization files
**/l10n/app_localizations*.dart

#### MULTIPASS SPECIFIC IGNORES ###
lib/generated
flutter*.log
Expand Down
3 changes: 3 additions & 0 deletions src/client/gui/l10n.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
20 changes: 7 additions & 13 deletions src/client/gui/lib/before_quit_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';

import 'confirmation_dialog.dart';
import 'l10n/app_localizations.dart';

class BeforeQuitDialog extends StatefulWidget {
final int runningCount;
Expand All @@ -23,22 +24,15 @@ class _BeforeQuitDialogState extends State<BeforeQuitDialog> {

@override
Widget build(BuildContext context) {
String getMessage() {
if (widget.runningCount == 1) {
return 'There is 1 running instance. Do you want to stop it?';
} else {
return 'There are ${widget.runningCount} running instances. Do you want to stop them?';
}
}

final l10n = AppLocalizations.of(context)!;
return ConfirmationDialog(
title: 'Stop running instances?',
title: l10n.beforeQuitTitle,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8),
child: Text(getMessage()),
child: Text(l10n.beforeQuitMessage(widget.runningCount)),
),
const SizedBox(height: 24),
Row(
Expand All @@ -48,14 +42,14 @@ class _BeforeQuitDialogState extends State<BeforeQuitDialog> {
onChanged: (value) => setState(() => remember = value!),
),
const SizedBox(width: 8),
const Text('Do not ask me again'),
Text(l10n.dialogDoNotAskAgain),
],
),
],
),
actionText: 'Stop instances',
actionText: l10n.beforeQuitStopAction,
onAction: () => widget.onStop(remember),
inactionText: 'Leave instances running',
inactionText: l10n.beforeQuitKeepAction,
onInaction: () => widget.onKeep(remember),
);
}
Expand Down
21 changes: 13 additions & 8 deletions src/client/gui/lib/catalogue/catalogue.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:grpc/grpc.dart';
import 'package:intersperse/intersperse.dart';

import '../l10n/app_localizations.dart';
import '../providers.dart';
import 'image_card.dart';
import 'launch_form.dart';
Expand Down Expand Up @@ -154,23 +155,26 @@ class CatalogueScreen extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final content = ref.watch(imagesProvider).when(
skipLoadingOnRefresh: false,
data: _buildCatalogue,
error: (error, _) {
final errorMessage = error is GrpcError ? error.message : error;
final errorMessage = error is GrpcError
? (error.message ?? error.toString())
: error.toString();
return Center(
child: Column(
children: [
const SizedBox(height: 32),
Text(
'Failed to retrieve images: $errorMessage',
l10n.catalogueLoadError(errorMessage),
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 16),
TextButton(
onPressed: () => ref.invalidate(imagesProvider),
child: const Text('Refresh'),
child: Text(l10n.catalogueRefresh),
),
],
),
Expand All @@ -181,15 +185,16 @@ class CatalogueScreen extends ConsumerWidget {

final welcomeText = Container(
constraints: const BoxConstraints(maxWidth: 500),
child: const Column(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Welcome to Multipass', style: TextStyle(fontSize: 37)),
Text(l10n.catalogueWelcomeTitle,
style: const TextStyle(fontSize: 37)),
Padding(
padding: EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(
'Get an instant VM in seconds. Multipass can launch and run virtual machines and configure them like a public cloud.',
style: TextStyle(fontSize: 16),
l10n.catalogueWelcomeBody,
style: const TextStyle(fontSize: 16),
),
),
],
Expand Down
33 changes: 17 additions & 16 deletions src/client/gui/lib/catalogue/image_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart' hide ImageInfo;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';

import '../l10n/app_localizations.dart';
import '../providers.dart';
import 'catalogue.dart';
import 'launch_form.dart';
Expand All @@ -28,25 +29,24 @@ class ImageCard extends ConsumerWidget {
};
}

String _getDisplayTitle(ImageInfo parentImage) {
String _getDisplayTitle(ImageInfo parentImage, AppLocalizations l10n) {
return switch (parentImage.os.toLowerCase()) {
'ubuntu' when parentImage.aliases.any((a) => a.contains('core')) =>
'Ubuntu Core',
'ubuntu' => 'Ubuntu Server',
'debian' => 'Debian',
'fedora' => 'Fedora',
l10n.imageCardTitleUbuntuCore,
'ubuntu' => l10n.imageCardTitleUbuntuServer,
'debian' => l10n.imageCardTitleDebian,
'fedora' => l10n.imageCardTitleFedora,
_ => parentImage.os, // Default case: return the OS name as-is
};
}

String _getDescription(ImageInfo parentImage) {
String _getDescription(ImageInfo parentImage, AppLocalizations l10n) {
return switch (parentImage.os.toLowerCase()) {
'ubuntu' when parentImage.aliases.any((a) => a.contains('core')) =>
'Ubuntu operating system optimised for IoT and Edge',
'ubuntu' =>
'Ubuntu operating system designed as a backbone for the internet',
'debian' => 'Debian official cloud image',
'fedora' => 'Fedora Cloud Edition',
l10n.imageCardDescUbuntuCore,
'ubuntu' => l10n.imageCardDescUbuntuServer,
'debian' => l10n.imageCardDescDebian,
'fedora' => l10n.imageCardDescFedora,
_ => '',
};
}
Expand All @@ -59,6 +59,7 @@ class ImageCard extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final selectedImage =
ref.watch(selectedImageProvider(imageKey)) ?? parentImage;

Expand Down Expand Up @@ -90,11 +91,11 @@ class ImageCard extends ConsumerWidget {
_getParentImageLogo(parentImage.os),
height: 24,
fit: BoxFit.contain,
semanticsLabel: '${parentImage.os} logo',
semanticsLabel: l10n.imageCardLogoSemantics(parentImage.os),
),
const SizedBox(width: 8),
Text(
_getDisplayTitle(parentImage),
_getDisplayTitle(parentImage, l10n),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
Expand All @@ -103,7 +104,7 @@ class ImageCard extends ConsumerWidget {
],
),
const SizedBox(height: 12),
Text(_getDescription(parentImage),
Text(_getDescription(parentImage, l10n),
style: const TextStyle(fontWeight: FontWeight.w300)),
const SizedBox(height: 16),
const Spacer(),
Expand Down Expand Up @@ -166,7 +167,7 @@ class ImageCard extends ConsumerWidget {

initiateLaunchFlow(ref, launchRequest);
},
child: const Text('Launch'),
child: Text(l10n.vmTableLaunch),
),
const SizedBox(width: 8),
OutlinedButton(
Expand All @@ -175,7 +176,7 @@ class ImageCard extends ConsumerWidget {
selectedImage;
Scaffold.of(context).openEndDrawer();
},
child: const Text('Configure'),
child: Text(l10n.dialogConfigure),
),
]),
],
Expand Down
Loading
Loading