🌐 Localized files
This commit is contained in:
@@ -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()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String>(
|
||||
PopupMenuItem<String>(
|
||||
value: 'files',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Symbols.file_copy),
|
||||
Gap(12),
|
||||
Text('Files'),
|
||||
Text('siteFiles'.tr()),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
PopupMenuItem<String>(
|
||||
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()),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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()),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user