diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json
index b4c6004c..b614032a 100644
--- a/assets/i18n/en-US.json
+++ b/assets/i18n/en-US.json
@@ -1343,5 +1343,133 @@
"clearCompleted": "Clear Completed",
"contentCantEmpty": "Content cannot be empty",
"features": "Features",
- "unnamed": "Unnamed"
+ "unnamed": "Unnamed",
+ "fundEnvelopeLoadFailed": "Failed to load fund envelope",
+ "fundEnvelope": "Fund Envelope",
+ "fundEnvelopeRemaining": "Remaining: {} {}",
+ "fundEnvelopeSplit": "Split: {}",
+ "fundEnvelopeSplitEvenly": "Evenly",
+ "fundEnvelopeSplitRandomly": "Randomly",
+ "fundEnvelopeClaimSuccess": "Fund claimed successfully!",
+ "fundEnvelopeStatusCreated": "Created",
+ "fundEnvelopeStatusPartial": "Partially Claimed",
+ "fundEnvelopeStatusCompleted": "Fully Claimed",
+ "fundEnvelopeStatusExpired": "Expired",
+ "fundEnvelopeStatusUnknown": "Unknown",
+ "fundEnvelopeRecipients": "Recipients ({}/{} claimed)",
+ "fundEnvelopeExpiredDaysAgo": {
+ "one": "Expired {} day ago",
+ "other": "Expired {} days ago"
+ },
+ "fundEnvelopeExpiresSoon": "Expires soon",
+ "fundEnvelopeExpiresInHours": {
+ "one": "Expires in {} hour",
+ "other": "Expires in {} hours"
+ },
+ "fundEnvelopeExpiresInDays": {
+ "one": "Expires in {} day",
+ "other": "Expires in {} days"
+ },
+ "fundEnvelopeRemainingWithSplits": "{} {} / {} splits",
+ "fundEnvelopeUnknownUser": "Unknown User",
+ "deleteSite": "Delete Site",
+ "deleteSiteConfirm": "Are you sure you want to delete this site?",
+ "siteDeletedSuccess": "Site deleted successfully",
+ "siteSlug": "Slug",
+ "siteSlugHint": "my-site",
+ "siteSlugRequired": "Please enter a slug",
+ "siteSlugInvalid": "Slug can only contain lowercase letters, numbers, and dashes",
+ "siteName": "Site Name",
+ "siteNameHint": "My Publication Site",
+ "siteNameRequired": "Please enter a site name",
+ "siteMode": "Mode",
+ "siteModeFullyManaged": "Fully Managed",
+ "siteModeSelfManaged": "Self-Managed",
+ "editPublicationSite": "Edit Publication Site",
+ "deletePublicationSite": "Delete Publication Site",
+ "publicationSiteSavedSuccess": "Publication site saved successfully",
+ "publicationSiteDeleteConfirm": "Are you sure you want to delete this publication site? This action cannot be undone.",
+ "publicationSiteDeletedSuccess": "Publication site deleted successfully",
+ "newPublicationSite": "New Publication Site",
+ "siteDetails": "Site Details",
+ "siteInformation": "Site Information",
+ "siteDomain": "Domain",
+ "siteCreated": "Created",
+ "siteUpdated": "Updated",
+ "failedToLoadSite": "Failed to load site",
+ "sitePages": "Pages",
+ "noPagesYet": "No pages yet",
+ "createFirstPage": "Create your first page to get started",
+ "failedToLoadPages": "Failed to load pages",
+ "fileManagement": "File Management",
+ "siteFiles": "Files",
+ "siteFolder": "Folder",
+ "siteRoot": "Root",
+ "noFilesUploadedYet": "No files uploaded yet",
+ "uploadFirstFile": "Upload your first file to get started",
+ "failedToLoadFiles": "Failed to load files",
+ "noFilesFoundInFolder": "No files found in the selected folder",
+ "fileActions": "File Actions",
+ "purgeFiles": "Purge Files",
+ "purgeFilesDescription": "Remove all uploaded files from the site",
+ "deploySite": "Deploy Site",
+ "deploySiteDescription": "Upload and deploy a new version from ZIP archive",
+ "confirmPurge": "Confirm Purge",
+ "purgeFilesConfirm": "This will permanently delete all files uploaded to this site. This action cannot be undone. Are you sure you want to continue?",
+ "purgeAllFiles": "Purge All Files",
+ "allFilesPurgedSuccess": "All files purged successfully",
+ "failedToPurgeFiles": "Failed to purge files: {}",
+ "siteDeployedSuccess": "Site deployed successfully",
+ "failedToDeploySite": "Failed to deploy site: {}",
+ "createPage": "Create Page",
+ "editPage": "Edit Page",
+ "pageType": "Page Type",
+ "htmlPage": "HTML Page",
+ "redirectPage": "Redirect Page",
+ "pageTypeRequired": "Please select a page type",
+ "pagePath": "Page Path",
+ "pagePathHint": "/about, /contact, etc.",
+ "pagePathRequired": "Please enter a page path",
+ "pagePathInvalid": "Page path can only contain letters, numbers, hyphens, underscores, and slashes",
+ "pagePathMustStartWithSlash": "Page path must start with /",
+ "pagePathNoConsecutiveSlashes": "Page path cannot have consecutive slashes",
+ "pageTitle": "Page Title",
+ "pageTitleHint": "About Us, Contact, etc.",
+ "pageTitleRequired": "Please enter a page title",
+ "pageContentHtml": "Page Content (HTML)",
+ "pageContentHint": "
Hello World
This is my page content...
",
+ "pageContentRequired": "Please enter HTML content for the page",
+ "redirectTarget": "Redirect Target",
+ "redirectTargetHint": "/new-page, https://example.com, etc.",
+ "redirectTargetRequired": "Please enter a redirect target",
+ "redirectTargetInvalid": "Target must be a relative path (/) or absolute URL (http/https)",
+ "deletePage": "Delete Page",
+ "deletePageConfirm": "Are you sure you want to delete this page?",
+ "savePage": "Save Page",
+ "pageCreatedSuccess": "Page created successfully",
+ "pageUpdatedSuccess": "Page updated successfully",
+ "pageDeletedSuccess": "Page deleted successfully",
+ "uploadFiles": "Upload Files",
+ "uploadPath": "Upload Path",
+ "uploadPathHint": "/ (root) or /assets/images/",
+ "uploadPathRequired": "Please enter an upload path",
+ "uploadPathMustStartWithSlash": "Path must start with /",
+ "uploadPathNoSpaces": "Path cannot contain spaces",
+ "uploadPathNoConsecutiveSlashes": "Path cannot have consecutive slashes",
+ "percentCompleted": "{}% completed",
+ "filesToUpload": "{} files to upload",
+ "fileSizeKb": "Size: {} KB",
+ "uploadingEllipsis": "Uploading...",
+ "uploadFilesCount": {
+ "one": "Upload {} File",
+ "other": "Upload {} Files"
+ },
+ "allUploadsCompleted": "All uploads completed",
+ "someUploadsFailed": "Some uploads failed",
+ "uploadingInProgress": "Uploading in progress",
+ "readyToUpload": "Ready to upload",
+ "allFilesUploadedSuccess": "All files uploaded successfully",
+ "lotteryLastNumberSpecial": "The last selected number will be your special number.",
+ "lotteryMultiplierRequired": "Please enter a multiplier",
+ "lotteryMultiplierRange": "Multiplier must be between 1 and 10"
}
\ No newline at end of file
diff --git a/lib/screens/creators/sites/site_detail.dart b/lib/screens/creators/sites/site_detail.dart
index 6b27bc2b..e7ab1e81 100644
--- a/lib/screens/creators/sites/site_detail.dart
+++ b/lib/screens/creators/sites/site_detail.dart
@@ -1,3 +1,4 @@
+import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -54,7 +55,7 @@ class PublicationSiteDetailScreen extends HookConsumerWidget {
appBar: AppBar(
title: siteAsync.maybeWhen(
data: (site) => Text(site.name),
- orElse: () => const Text('Site Details'),
+ orElse: () => Text('siteDetails'.tr()),
),
actions: [
siteAsync.maybeWhen(
@@ -105,26 +106,26 @@ class PublicationSiteDetailScreen extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- 'Site Information',
+ 'siteInformation'.tr(),
style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const Gap(16),
InfoRow(
- label: 'Name',
+ label: 'name'.tr(),
value: site.name,
icon: Symbols.title,
),
const Gap(8),
InfoRow(
- label: 'Slug',
+ label: 'slug'.tr(),
value: site.slug,
icon: Symbols.tag,
monospace: true,
),
const Gap(8),
InfoRow(
- label: 'Domain',
+ label: 'siteDomain'.tr(),
value: '${site.slug}.solian.page',
icon: Symbols.globe,
monospace: true,
@@ -136,31 +137,31 @@ class PublicationSiteDetailScreen extends HookConsumerWidget {
),
const Gap(8),
InfoRow(
- label: 'Mode',
+ label: 'siteMode'.tr(),
value:
site.mode == 0
- ? 'Fully Managed'
- : 'Self-Managed',
+ ? 'siteModeFullyManaged'.tr()
+ : 'siteModeSelfManaged'.tr(),
icon: Symbols.settings,
),
if (site.description != null &&
site.description!.isNotEmpty) ...[
const Gap(8),
InfoRow(
- label: 'Description',
+ label: 'description'.tr(),
value: site.description!,
icon: Symbols.description,
),
],
const Gap(8),
InfoRow(
- label: 'Created',
+ label: 'siteCreated'.tr(),
value: site.createdAt.formatSystem(),
icon: Symbols.calendar_add_on,
),
const Gap(8),
InfoRow(
- label: 'Updated',
+ label: 'siteUpdated'.tr(),
value: site.updatedAt.formatSystem(),
icon: Symbols.update,
),
@@ -191,7 +192,7 @@ class PublicationSiteDetailScreen extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
- 'Failed to load site',
+ 'failedToLoadSite'.tr(),
style: Theme.of(context).textTheme.headlineSmall,
),
const Gap(16),
@@ -202,7 +203,7 @@ class PublicationSiteDetailScreen extends HookConsumerWidget {
() => ref.invalidate(
publicationSiteDetailProvider(pubName, siteSlug),
),
- child: const Text('Retry'),
+ child: Text('retry'.tr()),
),
],
),
diff --git a/lib/screens/creators/sites/site_edit.dart b/lib/screens/creators/sites/site_edit.dart
index 6678419a..574e6b00 100644
--- a/lib/screens/creators/sites/site_edit.dart
+++ b/lib/screens/creators/sites/site_edit.dart
@@ -31,20 +31,20 @@ class SiteForm extends HookConsumerWidget {
children: [
TextFormField(
controller: slugController,
- decoration: const InputDecoration(
- labelText: 'Slug',
- hintText: 'my-site',
+ decoration: InputDecoration(
+ labelText: 'siteSlug'.tr(),
+ hintText: 'siteSlugHint'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
- return 'Please enter a slug';
+ return 'siteSlugRequired'.tr();
}
final slugRegex = RegExp(r'^[a-z0-9]+(?:-[a-z0-9]+)*$');
if (!slugRegex.hasMatch(value)) {
- return 'Slug can only contain lowercase letters, numbers, and dashes';
+ return 'siteSlugInvalid'.tr();
}
return null;
},
@@ -53,16 +53,16 @@ class SiteForm extends HookConsumerWidget {
const SizedBox(height: 16),
TextFormField(
controller: nameController,
- decoration: const InputDecoration(
- labelText: 'Site Name',
- hintText: 'My Publication Site',
+ decoration: InputDecoration(
+ labelText: 'siteName'.tr(),
+ hintText: 'siteNameHint'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
- return 'Please enter a site name';
+ return 'siteNameRequired'.tr();
}
return null;
},
@@ -71,8 +71,8 @@ class SiteForm extends HookConsumerWidget {
const SizedBox(height: 16),
TextFormField(
controller: descriptionController,
- decoration: const InputDecoration(
- labelText: 'Description',
+ decoration: InputDecoration(
+ labelText: 'description'.tr(),
alignLabelWithHint: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
@@ -84,15 +84,18 @@ class SiteForm extends HookConsumerWidget {
const SizedBox(height: 16),
DropdownButtonFormField(
value: modeController.value,
- decoration: const InputDecoration(
- labelText: 'Mode',
+ decoration: InputDecoration(
+ labelText: 'siteMode'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
- items: const [
- DropdownMenuItem(value: 0, child: Text('Fully Managed')),
- DropdownMenuItem(value: 1, child: Text('Self-Managed')),
+ items: [
+ DropdownMenuItem(
+ value: 0,
+ child: Text('siteModeFullyManaged'.tr()),
+ ),
+ DropdownMenuItem(value: 1, child: Text('siteModeSelfManaged'.tr())),
],
onChanged: (value) {
if (value != null) {
@@ -104,7 +107,7 @@ class SiteForm extends HookConsumerWidget {
).padding(all: 20);
return SheetScaffold(
- titleText: 'Edit Publication Site',
+ titleText: 'editPublicationSite'.tr(),
child: Builder(
builder:
(context) => SingleChildScrollView(
@@ -116,7 +119,7 @@ class SiteForm extends HookConsumerWidget {
TextButton.icon(
onPressed: deleteSite,
icon: const Icon(Symbols.delete_forever),
- label: const Text('Delete Publication Site'),
+ label: Text('deletePublicationSite'.tr()),
style: TextButton.styleFrom(
foregroundColor: Colors.red,
),
@@ -171,7 +174,7 @@ class SiteForm extends HookConsumerWidget {
ref.invalidate(siteListNotifierProvider(pubName));
if (context.mounted) {
- showSnackBar('Publication site saved successfully');
+ showSnackBar('publicationSiteSavedSuccess'.tr());
Navigator.pop(context);
}
} catch (e) {
@@ -185,8 +188,8 @@ class SiteForm extends HookConsumerWidget {
if (siteSlug == null) return; // Shouldn't happen for editing
final confirmed = await showConfirmAlert(
- 'Are you sure you want to delete this publication site? This action cannot be undone.',
- 'Delete Publication Site',
+ 'publicationSiteDeleteConfirm'.tr(),
+ 'deletePublicationSite'.tr(),
);
if (confirmed != true) return;
@@ -199,7 +202,7 @@ class SiteForm extends HookConsumerWidget {
ref.invalidate(siteListNotifierProvider(pubName));
if (context.mounted) {
- showSnackBar('Publication site deleted successfully');
+ showSnackBar('publicationSiteDeletedSuccess'.tr());
Navigator.pop(context);
}
} catch (e) {
@@ -243,13 +246,13 @@ class SiteForm extends HookConsumerWidget {
editingSiteSlug,
),
loading:
- () => const SheetScaffold(
- titleText: 'Edit Publication Site',
+ () => SheetScaffold(
+ titleText: 'editPublicationSite'.tr(),
child: Center(child: CircularProgressIndicator()),
),
error:
(error, _) => SheetScaffold(
- titleText: 'Edit Publication Site',
+ titleText: 'editPublicationSite'.tr(),
child: ResponseErrorWidget(
error: error.toString(),
onRetry: () {
@@ -327,9 +330,12 @@ class SiteForm extends HookConsumerWidget {
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
- items: const [
- DropdownMenuItem(value: 0, child: Text('Fully Managed')),
- DropdownMenuItem(value: 1, child: Text('Self-Managed')),
+ items: [
+ DropdownMenuItem(
+ value: 0,
+ child: Text('siteModeFullyManaged'.tr()),
+ ),
+ DropdownMenuItem(value: 1, child: Text('siteModeSelfManaged'.tr())),
],
onChanged: (value) {
if (value != null) {
@@ -348,7 +354,9 @@ class SiteForm extends HookConsumerWidget {
return SheetScaffold(
titleText:
- siteSlug == null ? 'New Publication Site' : 'Edit Publication Site',
+ siteSlug == null
+ ? 'newPublicationSite'.tr()
+ : 'editPublicationSite'.tr(),
child: SingleChildScrollView(
child: Column(
children: [
@@ -359,7 +367,7 @@ class SiteForm extends HookConsumerWidget {
TextButton.icon(
onPressed: isLoading.value ? null : deleteSite,
icon: const Icon(Symbols.delete_forever),
- label: const Text('Delete Publication Site'),
+ label: Text('deletePublicationSite'.tr()),
style: TextButton.styleFrom(foregroundColor: Colors.red),
).alignment(Alignment.centerRight),
const SizedBox(height: 16),
diff --git a/lib/screens/creators/sites/site_list.dart b/lib/screens/creators/sites/site_list.dart
index f55e27df..ca9b44de 100644
--- a/lib/screens/creators/sites/site_list.dart
+++ b/lib/screens/creators/sites/site_list.dart
@@ -74,7 +74,7 @@ class CreatorSiteListScreen extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return AppScaffold(
isNoBackground: false,
- appBar: AppBar(title: Text('Publication Sites')),
+ appBar: AppBar(title: Text('publicationSites'.tr())),
floatingActionButton: FloatingActionButton(
onPressed: () => _createSite(context),
child: Icon(Icons.add),
@@ -201,21 +201,19 @@ class _CreatorSiteItem extends HookConsumerWidget {
context: context,
builder:
(context) => AlertDialog(
- title: Text('Delete Site'),
- content: Text(
- 'Are you sure you want to delete this site?',
- ),
+ title: Text('deleteSite'.tr()),
+ content: Text('deleteSiteConfirm'.tr()),
actions: [
TextButton(
onPressed:
() =>
Navigator.of(context).pop(false),
- child: Text('Cancel'),
+ child: Text('cancel'.tr()),
),
TextButton(
onPressed:
() => Navigator.of(context).pop(true),
- child: Text('Delete'),
+ child: Text('delete'.tr()),
),
],
),
@@ -225,7 +223,7 @@ class _CreatorSiteItem extends HookConsumerWidget {
final client = ref.read(apiClientProvider);
await client.delete('/zone/sites/${site.id}');
ref.invalidate(siteListNotifierProvider(pubName));
- showSnackBar('Site deleted successfully');
+ showSnackBar('siteDeletedSuccess'.tr());
} catch (e) {
showErrorAlert(e);
}
diff --git a/lib/screens/lottery.dart b/lib/screens/lottery.dart
index 1f6a8a30..bc3f4f3c 100644
--- a/lib/screens/lottery.dart
+++ b/lib/screens/lottery.dart
@@ -534,7 +534,7 @@ class _LotteryPurchaseSheetState extends State {
),
const Gap(4),
Text(
- 'The last selected number will be your special number.',
+ 'lotteryLastNumberSpecial'.tr(),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.w500,
@@ -738,11 +738,11 @@ class _LotteryPurchaseSheetState extends State {
},
validator: (value) {
if (value == null || value.isEmpty) {
- return 'Please enter a multiplier';
+ return 'lotteryMultiplierRequired'.tr();
}
final parsed = int.tryParse(value);
if (parsed == null || parsed < 1 || parsed > 10) {
- return 'Multiplier must be between 1 and 10';
+ return 'lotteryMultiplierRange'.tr();
}
return null;
},
diff --git a/lib/widgets/sites/file_management_action_section.dart b/lib/widgets/sites/file_management_action_section.dart
index 143e3b3b..f6481d57 100644
--- a/lib/widgets/sites/file_management_action_section.dart
+++ b/lib/widgets/sites/file_management_action_section.dart
@@ -1,4 +1,5 @@
import 'dart:io';
+import 'package:easy_localization/easy_localization.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
@@ -33,7 +34,7 @@ class FileManagementActionSection extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- 'File Actions',
+ 'fileActions'.tr(),
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
@@ -45,10 +46,8 @@ class FileManagementActionSection extends HookConsumerWidget {
Symbols.delete_forever,
color: theme.colorScheme.error,
),
- title: const Text('Purge Files'),
- subtitle: const Text(
- 'Remove all uploaded files from the site',
- ),
+ title: Text('purgeFiles'.tr()),
+ subtitle: Text('purgeFilesDescription'.tr()),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
onTap: () => _purgeFiles(context, ref),
),
@@ -58,10 +57,8 @@ class FileManagementActionSection extends HookConsumerWidget {
Symbols.upload,
color: theme.colorScheme.primary,
),
- title: const Text('Deploy Site'),
- subtitle: const Text(
- 'Upload and deploy a new version from ZIP archive',
- ),
+ title: Text('deploySite'.tr()),
+ subtitle: Text('deploySiteDescription'.tr()),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
onTap: () => _deploySite(context, ref),
),
@@ -79,21 +76,19 @@ class FileManagementActionSection extends HookConsumerWidget {
context: context,
builder:
(context) => AlertDialog(
- title: const Text('Confirm Purge'),
- content: const Text(
- 'This will permanently delete all files uploaded to this site. This action cannot be undone. Are you sure you want to continue?',
- ),
+ title: Text('confirmPurge'.tr()),
+ content: Text('purgeFilesConfirm'.tr()),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
- child: const Text('Cancel'),
+ child: Text('cancel'.tr()),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
style: FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error,
),
- child: const Text('Purge All Files'),
+ child: Text('purgeAllFiles'.tr()),
),
],
),
@@ -105,13 +100,13 @@ class FileManagementActionSection extends HookConsumerWidget {
final apiClient = ref.read(apiClientProvider);
await apiClient.delete('/zone/sites/${site.id}/files/purge');
if (context.mounted) {
- showSnackBar('All files purged successfully');
+ showSnackBar('allFilesPurgedSuccess'.tr());
// Refresh the file management section
ref.invalidate(siteFilesProvider(siteId: site.id));
}
} catch (e) {
if (context.mounted) {
- showSnackBar('Failed to purge files: $e');
+ showSnackBar('failedToPurgeFiles'.tr(args: [e.toString()]));
}
}
}
@@ -147,13 +142,13 @@ class FileManagementActionSection extends HookConsumerWidget {
);
if (context.mounted) {
- showSnackBar('Site deployed successfully');
+ showSnackBar('siteDeployedSuccess'.tr());
// Refresh the file management section
ref.invalidate(siteFilesProvider(siteId: site.id));
}
} catch (e) {
if (context.mounted) {
- showSnackBar('Failed to deploy site: $e');
+ showSnackBar('failedToDeploySite'.tr(args: [e.toString()]));
}
}
}
diff --git a/lib/widgets/sites/file_management_section.dart b/lib/widgets/sites/file_management_section.dart
index 55b3f01e..0f5aa02a 100644
--- a/lib/widgets/sites/file_management_section.dart
+++ b/lib/widgets/sites/file_management_section.dart
@@ -1,4 +1,5 @@
import 'dart:io';
+import 'package:easy_localization/easy_localization.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -42,7 +43,7 @@ class FileManagementSection extends HookConsumerWidget {
Icon(Symbols.folder, size: 20),
const Gap(8),
Text(
- 'File Management',
+ 'fileManagement'.tr(),
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
@@ -75,9 +76,7 @@ class FileManagementSection extends HookConsumerWidget {
files =
results.map((m) => m['file'] as File).toList();
if (files.isEmpty) {
- showSnackBar(
- 'No files found in the selected folder',
- );
+ showSnackBar('noFilesFoundInFolder'.tr());
return;
}
}
@@ -112,23 +111,23 @@ class FileManagementSection extends HookConsumerWidget {
},
itemBuilder:
(BuildContext context) => [
- const PopupMenuItem(
+ PopupMenuItem(
value: 'files',
child: Row(
children: [
Icon(Symbols.file_copy),
Gap(12),
- Text('Files'),
+ Text('siteFiles'.tr()),
],
),
),
- const PopupMenuItem(
+ PopupMenuItem(
value: 'folder',
child: Row(
children: [
Icon(Symbols.folder),
Gap(12),
- Text('Folder'),
+ Text('siteFolder'.tr()),
],
),
),
@@ -182,7 +181,7 @@ class FileManagementSection extends HookConsumerWidget {
children: [
InkWell(
onTap: () => currentPath.value = null,
- child: const Text('Root'),
+ child: Text('siteRoot'.tr()),
),
...() {
final parts =
@@ -230,12 +229,12 @@ class FileManagementSection extends HookConsumerWidget {
),
const Gap(16),
Text(
- 'No files uploaded yet',
+ 'noFilesUploadedYet'.tr(),
style: theme.textTheme.bodyLarge,
),
const Gap(8),
Text(
- 'Upload your first file to get started',
+ 'uploadFirstFile'.tr(),
style: theme.textTheme.bodySmall,
),
],
@@ -265,7 +264,7 @@ class FileManagementSection extends HookConsumerWidget {
(error, stack) => Center(
child: Column(
children: [
- Text('Failed to load files'),
+ Text('failedToLoadFiles'.tr()),
const Gap(8),
ElevatedButton(
onPressed:
@@ -275,7 +274,7 @@ class FileManagementSection extends HookConsumerWidget {
path: currentPath.value,
),
),
- child: const Text('Retry'),
+ child: Text('retry'.tr()),
),
],
),
diff --git a/lib/widgets/sites/pages_section.dart b/lib/widgets/sites/pages_section.dart
index 4163173e..66caf192 100644
--- a/lib/widgets/sites/pages_section.dart
+++ b/lib/widgets/sites/pages_section.dart
@@ -1,3 +1,4 @@
+import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -29,7 +30,7 @@ class PagesSection extends HookConsumerWidget {
const Icon(Symbols.article, size: 20),
const Gap(8),
Text(
- 'Pages',
+ 'sitePages'.tr(),
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
@@ -72,12 +73,12 @@ class PagesSection extends HookConsumerWidget {
),
const Gap(16),
Text(
- 'No pages yet',
+ 'noPagesYet'.tr(),
style: theme.textTheme.bodyLarge,
),
const Gap(8),
Text(
- 'Create your first page to get started',
+ 'createFirstPage'.tr(),
style: theme.textTheme.bodySmall,
),
],
@@ -101,14 +102,14 @@ class PagesSection extends HookConsumerWidget {
(error, stack) => Center(
child: Column(
children: [
- Text('Failed to load pages'),
+ Text('failedToLoadPages'.tr()),
const Gap(8),
ElevatedButton(
onPressed:
() => ref.invalidate(
sitePagesProvider(pubName, site.slug),
),
- child: const Text('Retry'),
+ child: Text('retry'.tr()),
),
],
),
diff --git a/lib/widgets/sites/site_action_menu.dart b/lib/widgets/sites/site_action_menu.dart
index ce44c04e..5df9690d 100644
--- a/lib/widgets/sites/site_action_menu.dart
+++ b/lib/widgets/sites/site_action_menu.dart
@@ -64,18 +64,16 @@ class SiteActionMenu extends HookConsumerWidget {
context: context,
builder:
(context) => AlertDialog(
- title: const Text('Delete Site'),
- content: const Text(
- 'Are you sure you want to delete this publication site? This action cannot be undone.',
- ),
+ title: Text('deleteSite'.tr()),
+ content: Text('publicationSiteDeleteConfirm'.tr()),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
- child: const Text('Cancel'),
+ child: Text('cancel'.tr()),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
- child: const Text('Delete'),
+ child: Text('delete'.tr()),
),
],
),
@@ -86,7 +84,7 @@ class SiteActionMenu extends HookConsumerWidget {
final client = ref.read(apiClientProvider);
await client.delete('/zone/sites/${site.id}');
if (context.mounted) {
- showSnackBar('Site deleted successfully');
+ showSnackBar('siteDeletedSuccess'.tr());
Navigator.of(context).pop();
}
} catch (e) {
diff --git a/lib/widgets/sites/site_detail_content.dart b/lib/widgets/sites/site_detail_content.dart
index 3fd6219d..2362aa92 100644
--- a/lib/widgets/sites/site_detail_content.dart
+++ b/lib/widgets/sites/site_detail_content.dart
@@ -1,3 +1,4 @@
+import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -42,20 +43,20 @@ class SiteDetailContent extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- 'Site Information',
+ 'siteInformation'.tr(),
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const Gap(16),
InfoRow(
- label: 'Name',
+ label: 'name'.tr(),
value: site.name,
icon: Symbols.title,
),
const Gap(8),
InfoRow(
- label: 'Slug',
+ label: 'slug'.tr(),
value: site.slug,
icon: Symbols.tag,
monospace: true,
@@ -63,27 +64,30 @@ class SiteDetailContent extends HookConsumerWidget {
const Gap(8),
InfoRow(
label: 'Mode',
- value: site.mode == 0 ? 'Fully Managed' : 'Self-Managed',
+ value:
+ site.mode == 0
+ ? 'siteModeFullyManaged'.tr()
+ : 'siteModeSelfManaged'.tr(),
icon: Symbols.settings,
),
if (site.description != null &&
site.description!.isNotEmpty) ...[
const Gap(8),
InfoRow(
- label: 'Description',
+ label: 'description'.tr(),
value: site.description!,
icon: Symbols.description,
),
],
const Gap(8),
InfoRow(
- label: 'Created',
+ label: 'siteCreated'.tr(),
value: site.createdAt.formatSystem(),
icon: Symbols.calendar_add_on,
),
const Gap(8),
InfoRow(
- label: 'Updated',
+ label: 'siteUpdated'.tr(),
value: site.updatedAt.formatSystem(),
icon: Symbols.update,
),
diff --git a/lib/widgets/wallet/fund_envelope.dart b/lib/widgets/wallet/fund_envelope.dart
index 93f67a22..f18219b9 100644
--- a/lib/widgets/wallet/fund_envelope.dart
+++ b/lib/widgets/wallet/fund_envelope.dart
@@ -57,7 +57,7 @@ class FundEnvelopeWidget extends HookConsumerWidget {
),
const SizedBox(height: 8),
Text(
- 'Failed to load fund envelope',
+ 'fundEnvelopeLoadFailed'.tr(),
style: TextStyle(
color: Theme.of(context).colorScheme.error,
),
@@ -88,7 +88,7 @@ class FundEnvelopeWidget extends HookConsumerWidget {
const SizedBox(width: 8),
Expanded(
child: Text(
- 'Fund Envelope',
+ 'fundEnvelope'.tr(),
style: Theme.of(context).textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.w600),
),
@@ -116,7 +116,12 @@ class FundEnvelopeWidget extends HookConsumerWidget {
const SizedBox(height: 4),
if (fund.remainingAmount != fund.totalAmount)
Text(
- 'Remaining: ${fund.remainingAmount.toStringAsFixed(2)} ${fund.currency}',
+ 'fundEnvelopeRemaining'.tr(
+ args: [
+ fund.remainingAmount.toStringAsFixed(2),
+ fund.currency,
+ ],
+ ),
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(
@@ -126,7 +131,13 @@ class FundEnvelopeWidget extends HookConsumerWidget {
),
),
Text(
- 'Split: ${fund.splitType == 0 ? 'Evenly' : 'Randomly'}',
+ 'fundEnvelopeSplit'.tr(
+ args: [
+ fund.splitType == 0
+ ? 'fundEnvelopeSplitEvenly'.tr()
+ : 'fundEnvelopeSplitRandomly'.tr(),
+ ],
+ ),
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(
@@ -245,7 +256,7 @@ class FundEnvelopeWidget extends HookConsumerWidget {
if (dialogContext.mounted) {
Navigator.of(dialogContext).pop();
- showSnackBar('Fund claimed successfully!');
+ showSnackBar('fundEnvelopeClaimSuccess'.tr());
}
} catch (e) {
showErrorAlert(e);
@@ -261,23 +272,23 @@ class FundEnvelopeWidget extends HookConsumerWidget {
switch (status) {
case 0:
- text = 'Created';
+ text = 'fundEnvelopeStatusCreated'.tr();
color = Colors.blue;
break;
case 1:
- text = 'Partially Claimed';
+ text = 'fundEnvelopeStatusPartial'.tr();
color = Colors.orange;
break;
case 2:
- text = 'Fully Claimed';
+ text = 'fundEnvelopeStatusCompleted'.tr();
color = Colors.green;
break;
case 3:
- text = 'Expired';
+ text = 'fundEnvelopeStatusExpired'.tr();
color = Colors.red;
break;
default:
- text = 'Unknown';
+ text = 'fundEnvelopeStatusUnknown'.tr();
color = Colors.grey;
}
@@ -339,7 +350,9 @@ class FundEnvelopeWidget extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- 'Recipients ($claimedCount/$totalCount claimed)',
+ 'fundEnvelopeRecipients'.tr(
+ args: [claimedCount.toString(), totalCount.toString()],
+ ),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
@@ -362,15 +375,26 @@ class FundEnvelopeWidget extends HookConsumerWidget {
final difference = date.difference(now);
if (difference.isNegative) {
- return 'Expired ${difference.inDays.abs()} days ago';
+ final days = difference.inDays.abs();
+ return 'fundEnvelopeExpiredDaysAgo'.plural(
+ days,
+ args: [days.toString()],
+ );
} else if (difference.inDays == 0) {
final hours = difference.inHours;
if (hours == 0) {
- return 'Expires soon';
+ return 'fundEnvelopeExpiresSoon'.tr();
}
- return 'Expires in $hours hour${hours == 1 ? '' : 's'}';
+ return 'fundEnvelopeExpiresInHours'.plural(
+ hours,
+ args: [hours.toString()],
+ );
} else if (difference.inDays < 7) {
- return 'Expires in ${difference.inDays} day${difference.inDays == 1 ? '' : 's'}';
+ final days = difference.inDays;
+ return 'fundEnvelopeExpiresInDays'.plural(
+ days,
+ args: [days.toString()],
+ );
} else {
return '${date.day}/${date.month}/${date.year}';
}
@@ -449,7 +473,13 @@ class FundClaimDialog extends HookConsumerWidget {
// Remaining amount
Text(
- '${fund.remainingAmount.toStringAsFixed(2)} ${fund.currency} / $remainingSplits splits',
+ 'fundEnvelopeRemainingWithSplits'.tr(
+ args: [
+ fund.remainingAmount.toStringAsFixed(2),
+ fund.currency,
+ remainingSplits.toString(),
+ ],
+ ),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.w500,
@@ -503,7 +533,8 @@ class FundClaimDialog extends HookConsumerWidget {
const SizedBox(width: 8),
Expanded(
child: Text(
- recipient.recipientAccount?.nick ?? 'Unknown User',
+ recipient.recipientAccount?.nick ??
+ 'fundEnvelopeUnknownUser'.tr(),
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(
@@ -551,7 +582,8 @@ class FundClaimDialog extends HookConsumerWidget {
const SizedBox(width: 8),
Expanded(
child: Text(
- recipient.recipientAccount?.nick ?? 'Unknown User',
+ recipient.recipientAccount?.nick ??
+ 'fundEnvelopeUnknownUser'.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
),