chore: release v2.4

- Add theme system (Dark/Light palettes, ModernTheme, ThemeManager)
- Add InputDialog, Spinner common view
- Add DuplicatesCsvExportService
- Refresh views, dialogs, and view models across tabs
- Update localization strings (en/fr)
- Tweak services (transfer, permissions, search, user access, ownership elevation, bulk operations)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dev
2026-04-20 11:23:11 +02:00
parent 8f30a60d2a
commit 12dd1de9f2
93 changed files with 8708 additions and 1159 deletions
+215 -2
View File
@@ -109,6 +109,18 @@
<data name="settings.lang.fr" xml:space="preserve">
<value>French</value>
</data>
<data name="settings.theme" xml:space="preserve">
<value>Theme</value>
</data>
<data name="settings.theme.system" xml:space="preserve">
<value>Use system setting</value>
</data>
<data name="settings.theme.light" xml:space="preserve">
<value>Light</value>
</data>
<data name="settings.theme.dark" xml:space="preserve">
<value>Dark</value>
</data>
<data name="settings.folder" xml:space="preserve">
<value>Data output folder</value>
</data>
@@ -130,8 +142,8 @@
<data name="profile.add" xml:space="preserve">
<value>Add</value>
</data>
<data name="profile.rename" xml:space="preserve">
<value>Rename</value>
<data name="profile.save" xml:space="preserve">
<value>Save</value>
</data>
<data name="profile.delete" xml:space="preserve">
<value>Delete</value>
@@ -139,6 +151,9 @@
<data name="status.ready" xml:space="preserve">
<value>Ready</value>
</data>
<data name="status.complete" xml:space="preserve">
<value>Complete</value>
</data>
<data name="status.cancelled" xml:space="preserve">
<value>Operation cancelled</value>
</data>
@@ -357,6 +372,27 @@
<data name="audit.btn.exportHtml" xml:space="preserve">
<value>Export HTML</value>
</data>
<data name="export.split.label" xml:space="preserve">
<value>Split</value>
</data>
<data name="export.split.single" xml:space="preserve">
<value>Single file</value>
</data>
<data name="export.split.bySite" xml:space="preserve">
<value>By site</value>
</data>
<data name="export.split.byUser" xml:space="preserve">
<value>By user</value>
</data>
<data name="export.html.layout.label" xml:space="preserve">
<value>HTML layout</value>
</data>
<data name="export.html.layout.separate" xml:space="preserve">
<value>Separate files</value>
</data>
<data name="export.html.layout.tabbed" xml:space="preserve">
<value>Single tabbed file</value>
</data>
<data name="audit.summary.total" xml:space="preserve">
<value>Total Accesses</value>
</data>
@@ -437,4 +473,181 @@
<data name="settings.ownership.auto" xml:space="preserve"><value>Automatically take site collection admin ownership on access denied</value></data>
<data name="settings.ownership.description" xml:space="preserve"><value>When enabled, the app will automatically elevate to site collection admin when a scan encounters an access denied error. Requires Tenant Admin permissions.</value></data>
<data name="permissions.elevated.tooltip" xml:space="preserve"><value>This site was automatically elevated — ownership was taken to complete the scan</value></data>
<!-- Report export localization -->
<data name="report.title.user_access" xml:space="preserve"><value>User Access Audit Report</value></data>
<data name="report.title.user_access_consolidated" xml:space="preserve"><value>User Access Audit Report (Consolidated)</value></data>
<data name="report.title.permissions" xml:space="preserve"><value>SharePoint Permissions Report</value></data>
<data name="report.title.permissions_simplified" xml:space="preserve"><value>SharePoint Permissions Report (Simplified)</value></data>
<data name="report.title.storage" xml:space="preserve"><value>SharePoint Storage Metrics</value></data>
<data name="report.title.duplicates" xml:space="preserve"><value>SharePoint Duplicate Detection Report</value></data>
<data name="report.title.duplicates_short" xml:space="preserve"><value>Duplicate Detection Report</value></data>
<data name="report.title.search" xml:space="preserve"><value>SharePoint File Search Results</value></data>
<data name="report.title.search_short" xml:space="preserve"><value>File Search Results</value></data>
<data name="report.stat.total_accesses" xml:space="preserve"><value>Total Accesses</value></data>
<data name="report.stat.users_audited" xml:space="preserve"><value>Users Audited</value></data>
<data name="report.stat.sites_scanned" xml:space="preserve"><value>Sites Scanned</value></data>
<data name="report.stat.high_privilege" xml:space="preserve"><value>High Privilege</value></data>
<data name="report.stat.external_users" xml:space="preserve"><value>External Users</value></data>
<data name="report.stat.total_entries" xml:space="preserve"><value>Total Entries</value></data>
<data name="report.stat.unique_permission_sets" xml:space="preserve"><value>Unique Permission Sets</value></data>
<data name="report.stat.distinct_users_groups" xml:space="preserve"><value>Distinct Users/Groups</value></data>
<data name="report.stat.libraries" xml:space="preserve"><value>Libraries</value></data>
<data name="report.stat.files" xml:space="preserve"><value>Files</value></data>
<data name="report.stat.total_size" xml:space="preserve"><value>Total Size</value></data>
<data name="report.stat.version_size" xml:space="preserve"><value>Version Size</value></data>
<data name="report.badge.guest" xml:space="preserve"><value>Guest</value></data>
<data name="report.badge.direct" xml:space="preserve"><value>Direct</value></data>
<data name="report.badge.group" xml:space="preserve"><value>Group</value></data>
<data name="report.badge.inherited" xml:space="preserve"><value>Inherited</value></data>
<data name="report.badge.unique" xml:space="preserve"><value>Unique</value></data>
<data name="report.view.by_user" xml:space="preserve"><value>By User</value></data>
<data name="report.view.by_site" xml:space="preserve"><value>By Site</value></data>
<data name="report.filter.placeholder_results" xml:space="preserve"><value>Filter results...</value></data>
<data name="report.filter.placeholder_permissions" xml:space="preserve"><value>Filter permissions...</value></data>
<data name="report.filter.placeholder_rows" xml:space="preserve"><value>Filter rows…</value></data>
<data name="report.filter.label" xml:space="preserve"><value>Filter:</value></data>
<data name="report.col.site" xml:space="preserve"><value>Site</value></data>
<data name="report.col.sites" xml:space="preserve"><value>Sites</value></data>
<data name="report.col.object_type" xml:space="preserve"><value>Object Type</value></data>
<data name="report.col.object" xml:space="preserve"><value>Object</value></data>
<data name="report.col.permission_level" xml:space="preserve"><value>Permission Level</value></data>
<data name="report.col.access_type" xml:space="preserve"><value>Access Type</value></data>
<data name="report.col.granted_through" xml:space="preserve"><value>Granted Through</value></data>
<data name="report.col.user" xml:space="preserve"><value>User</value></data>
<data name="report.col.title" xml:space="preserve"><value>Title</value></data>
<data name="report.col.url" xml:space="preserve"><value>URL</value></data>
<data name="report.col.users_groups" xml:space="preserve"><value>Users/Groups</value></data>
<data name="report.col.simplified" xml:space="preserve"><value>Simplified</value></data>
<data name="report.col.risk" xml:space="preserve"><value>Risk</value></data>
<data name="report.col.library_folder" xml:space="preserve"><value>Library / Folder</value></data>
<data name="report.col.last_modified" xml:space="preserve"><value>Last Modified</value></data>
<data name="report.col.name" xml:space="preserve"><value>Name</value></data>
<data name="report.col.library" xml:space="preserve"><value>Library</value></data>
<data name="report.col.path" xml:space="preserve"><value>Path</value></data>
<data name="report.col.size" xml:space="preserve"><value>Size</value></data>
<data name="report.col.created" xml:space="preserve"><value>Created</value></data>
<data name="report.col.modified" xml:space="preserve"><value>Modified</value></data>
<data name="report.col.created_by" xml:space="preserve"><value>Created By</value></data>
<data name="report.col.modified_by" xml:space="preserve"><value>Modified By</value></data>
<data name="report.col.file_name" xml:space="preserve"><value>File Name</value></data>
<data name="report.col.extension" xml:space="preserve"><value>Extension</value></data>
<data name="report.col.file_type" xml:space="preserve"><value>File Type</value></data>
<data name="report.col.file_count" xml:space="preserve"><value>File Count</value></data>
<data name="report.col.error" xml:space="preserve"><value>Error</value></data>
<data name="report.col.timestamp" xml:space="preserve"><value>Timestamp</value></data>
<data name="report.col.number" xml:space="preserve"><value>#</value></data>
<data name="report.col.group" xml:space="preserve"><value>Group</value></data>
<data name="report.col.total_size_mb" xml:space="preserve"><value>Total Size (MB)</value></data>
<data name="report.col.version_size_mb" xml:space="preserve"><value>Version Size (MB)</value></data>
<data name="report.col.size_mb" xml:space="preserve"><value>Size (MB)</value></data>
<data name="report.col.size_bytes" xml:space="preserve"><value>Size (bytes)</value></data>
<data name="report.text.accesses" xml:space="preserve"><value>accesses</value></data>
<data name="report.text.access_es" xml:space="preserve"><value>access(es)</value></data>
<data name="report.text.sites_parens" xml:space="preserve"><value>site(s)</value></data>
<data name="report.text.permissions_parens" xml:space="preserve"><value>permission(s)</value></data>
<data name="report.text.copies" xml:space="preserve"><value>copies</value></data>
<data name="report.text.duplicate_groups_found" xml:space="preserve"><value>duplicate group(s) found.</value></data>
<data name="report.text.results_parens" xml:space="preserve"><value>result(s)</value></data>
<data name="report.text.of" xml:space="preserve"><value>of</value></data>
<data name="report.text.shown" xml:space="preserve"><value>shown</value></data>
<data name="report.text.generated" xml:space="preserve"><value>Generated</value></data>
<data name="report.text.generated_colon" xml:space="preserve"><value>Generated:</value></data>
<data name="report.text.members_unavailable" xml:space="preserve"><value>members unavailable</value></data>
<data name="report.text.link" xml:space="preserve"><value>Link</value></data>
<data name="report.text.no_ext" xml:space="preserve"><value>(no ext)</value></data>
<data name="report.text.no_extension" xml:space="preserve"><value>(no extension)</value></data>
<data name="report.text.high_priv" xml:space="preserve"><value>high-priv</value></data>
<data name="report.section.storage_by_file_type" xml:space="preserve"><value>Storage by File Type</value></data>
<data name="report.section.library_details" xml:space="preserve"><value>Library Details</value></data>
<!-- Site picker dialog -->
<data name="sitepicker.title" xml:space="preserve"><value>Select Sites</value></data>
<data name="sitepicker.filter" xml:space="preserve"><value>Filter:</value></data>
<data name="sitepicker.type" xml:space="preserve"><value>Type:</value></data>
<data name="sitepicker.type.all" xml:space="preserve"><value>All</value></data>
<data name="sitepicker.type.team" xml:space="preserve"><value>Team sites (MS Teams)</value></data>
<data name="sitepicker.type.communication" xml:space="preserve"><value>Communication</value></data>
<data name="sitepicker.type.classic" xml:space="preserve"><value>Classic</value></data>
<data name="sitepicker.type.other" xml:space="preserve"><value>Other</value></data>
<data name="sitepicker.size" xml:space="preserve"><value>Size (MB):</value></data>
<data name="sitepicker.size.min" xml:space="preserve"><value>min</value></data>
<data name="sitepicker.size.max" xml:space="preserve"><value>max</value></data>
<data name="sitepicker.col.title" xml:space="preserve"><value>Title</value></data>
<data name="sitepicker.col.url" xml:space="preserve"><value>URL</value></data>
<data name="sitepicker.col.type" xml:space="preserve"><value>Type</value></data>
<data name="sitepicker.col.size" xml:space="preserve"><value>Size</value></data>
<data name="sitepicker.btn.load" xml:space="preserve"><value>Load Sites</value></data>
<data name="sitepicker.btn.selectAll" xml:space="preserve"><value>Select All</value></data>
<data name="sitepicker.btn.deselectAll" xml:space="preserve"><value>Deselect All</value></data>
<data name="sitepicker.btn.ok" xml:space="preserve"><value>OK</value></data>
<data name="sitepicker.btn.cancel" xml:space="preserve"><value>Cancel</value></data>
<data name="sitepicker.status.loading" xml:space="preserve"><value>Loading sites...</value></data>
<data name="sitepicker.status.loaded" xml:space="preserve"><value>{0} sites loaded.</value></data>
<data name="sitepicker.status.shown" xml:space="preserve"><value>{0} / {1} sites shown.</value></data>
<data name="sitepicker.status.error" xml:space="preserve"><value>Error: {0}</value></data>
<data name="sitepicker.kind.teamsite" xml:space="preserve"><value>Team site</value></data>
<data name="sitepicker.kind.communication" xml:space="preserve"><value>Communication</value></data>
<data name="sitepicker.kind.classic" xml:space="preserve"><value>Classic</value></data>
<data name="sitepicker.kind.other" xml:space="preserve"><value>Other</value></data>
<!-- Common UI -->
<data name="common.valid" xml:space="preserve"><value>Valid</value></data>
<data name="common.errors" xml:space="preserve"><value>Errors</value></data>
<data name="common.close" xml:space="preserve"><value>Close</value></data>
<data name="common.new_folder" xml:space="preserve"><value>+ New Folder</value></data>
<data name="common.guest" xml:space="preserve"><value>Guest</value></data>
<!-- InputDialog -->
<data name="input.title" xml:space="preserve"><value>Input</value></data>
<!-- ProfileManagementDialog -->
<data name="profmgmt.title" xml:space="preserve"><value>Manage Profiles</value></data>
<data name="profmgmt.group" xml:space="preserve"><value>Profiles</value></data>
<!-- Duplicates columns -->
<data name="duplicates.col.group" xml:space="preserve"><value>Group</value></data>
<data name="duplicates.col.copies" xml:space="preserve"><value>Copies</value></data>
<!-- Folder structure levels -->
<data name="folderstruct.col.level1" xml:space="preserve"><value>Level 1</value></data>
<data name="folderstruct.col.level2" xml:space="preserve"><value>Level 2</value></data>
<data name="folderstruct.col.level3" xml:space="preserve"><value>Level 3</value></data>
<data name="folderstruct.col.level4" xml:space="preserve"><value>Level 4</value></data>
<!-- Permissions extra columns -->
<data name="perm.col.unique_perms" xml:space="preserve"><value>Unique Perms</value></data>
<data name="perm.col.permission_levels" xml:space="preserve"><value>Permission Levels</value></data>
<data name="perm.col.principal_type" xml:space="preserve"><value>Principal Type</value></data>
<!-- Storage summary labels -->
<data name="storage.lbl.total_size_colon" xml:space="preserve"><value>Total Size: </value></data>
<data name="storage.lbl.version_size_colon" xml:space="preserve"><value>Version Size: </value></data>
<data name="storage.lbl.files_colon" xml:space="preserve"><value>Files: </value></data>
<!-- Templates columns -->
<data name="templates.col.source" xml:space="preserve"><value>Source</value></data>
<data name="templates.col.captured" xml:space="preserve"><value>Captured</value></data>
<!-- Transfer view -->
<data name="transfer.text.files_selected" xml:space="preserve"><value> file(s) selected</value></data>
<data name="transfer.chk.include_source" xml:space="preserve"><value>Include source folder at destination</value></data>
<data name="transfer.chk.include_source.tooltip" xml:space="preserve"><value>When on, recreate the source folder under the destination. When off, drop contents directly into the destination folder.</value></data>
<data name="transfer.chk.copy_contents" xml:space="preserve"><value>Copy folder contents</value></data>
<data name="transfer.chk.copy_contents.tooltip" xml:space="preserve"><value>When on (default), transfer files inside the folder. When off, only the folder is created at the destination.</value></data>
<!-- Shared ViewModel errors and statuses -->
<data name="err.no_tenant" xml:space="preserve"><value>No tenant connected.</value></data>
<data name="err.no_tenant_connected" xml:space="preserve"><value>No tenant selected. Please connect to a tenant first.</value></data>
<data name="err.no_profile_selected" xml:space="preserve"><value>No tenant profile selected. Please connect first.</value></data>
<data name="err.no_sites_selected" xml:space="preserve"><value>Select at least one site from the toolbar.</value></data>
<data name="err.no_users_selected" xml:space="preserve"><value>Add at least one user to audit.</value></data>
<data name="err.no_valid_rows" xml:space="preserve"><value>No valid rows to process. Import a CSV first.</value></data>
<data name="err.template_name_required" xml:space="preserve"><value>Template name is required.</value></data>
<data name="err.site_title_required" xml:space="preserve"><value>New site title is required.</value></data>
<data name="err.site_alias_required" xml:space="preserve"><value>New site alias is required.</value></data>
<data name="err.transfer_source_required" xml:space="preserve"><value>Source site and library must be selected.</value></data>
<data name="err.transfer_dest_required" xml:space="preserve"><value>Destination site and library must be selected.</value></data>
<data name="err.library_title_required" xml:space="preserve"><value>Library title is required.</value></data>
<!-- Templates status -->
<data name="templates.status.capturing" xml:space="preserve"><value>Capturing template...</value></data>
<data name="templates.status.success" xml:space="preserve"><value>Template captured successfully.</value></data>
<data name="templates.status.capture_failed" xml:space="preserve"><value>Capture failed: {0}</value></data>
<data name="templates.status.applying" xml:space="preserve"><value>Applying template...</value></data>
<data name="templates.status.applied" xml:space="preserve"><value>Template applied. Site created at: {0}</value></data>
<data name="templates.status.apply_failed" xml:space="preserve"><value>Apply failed: {0}</value></data>
<!-- UI text -->
<data name="audit.searching" xml:space="preserve"><value>Searching...</value></data>
<!-- Report text -->
<data name="report.text.users_parens" xml:space="preserve"><value>user(s)</value></data>
<data name="report.text.files_unit" xml:space="preserve"><value>files</value></data>
<data name="report.text.sites_unit" xml:space="preserve"><value>sites</value></data>
</root>