diff --git a/Sharepoint_ToolBox.ps1 b/Sharepoint_ToolBox.ps1 index 6a2c33f..525d25b 100644 --- a/Sharepoint_ToolBox.ps1 +++ b/Sharepoint_ToolBox.ps1 @@ -140,13 +140,13 @@ function Load-Settings { return $data } catch {} } - return [PSCustomObject]@{ dataFolder = "" } + return [PSCustomObject]@{ dataFolder = ""; lang = "en" } } function Save-Settings { - param([string]$DataFolder) + param([string]$DataFolder, [string]$Lang = "en") $path = Get-SettingsFilePath - [PSCustomObject]@{ dataFolder = $DataFolder } | + [PSCustomObject]@{ dataFolder = $DataFolder; lang = $Lang } | ConvertTo-Json | Set-Content $path -Encoding UTF8 } @@ -308,7 +308,7 @@ function Show-SitePicker { $dlg.Controls.AddRange(@($lblFilter, $txtFilter, $btnLoad, $lv, $lblStatus, $btnSelAll, $btnSelNone, $btnOK, $btnDlgCancel)) - # Init script-scope state (modal dialog — no concurrency issue) + # Init script-scope state (modal dialog - no concurrency issue) $script:_pkl = @{ AllSites = @() CheckedUrls = [System.Collections.Generic.HashSet[string]]::new( @@ -483,7 +483,7 @@ function Save-Templates { @{ templates = @($Templates) } | ConvertTo-Json -Depth 20 | Set-Content $path -Encoding UTF8 } -# Script-scope helpers (accessible from all event handlers — no closure tricks) +# Script-scope helpers (accessible from all event handlers - no closure tricks) function _Tpl-Repopulate { $lv = $script:_tpl.Lv $lv.BeginUpdate() @@ -932,7 +932,7 @@ function Show-TemplateManager { rootSiteRel = $srl # site-relative URL of library root folders = @($fld) }) - BgLog " [$($list.BaseType)] $($list.Title) ($srl) — $($fld.Count) dossier(s)" "Cyan" + BgLog " [$($list.BaseType)] $($list.Title) ($srl) - $($fld.Count) dossier(s)" "Cyan" } $result.structure = @($struct) } @@ -954,7 +954,7 @@ function Show-TemplateManager { roles = @($roles) members = @($members) }) - BgLog " Groupe: $($g.Title) — $($members.Count) membre(s)" "Cyan" + BgLog " Groupe: $($g.Title) - $($members.Count) membre(s)" "Cyan" } catch {} } $result.permissions = @($permArr) @@ -1396,7 +1396,7 @@ function Export-PermissionsToHTML { foreach ($mrow in $mergedRows) { $locs = @($mrow.Locations) - # Dominant type badge (use first location's type — entries in a merged group are typically the same type) + # Dominant type badge (use first location's type - entries in a merged group are typically the same type) $dominantType = $locs[0].Object $badgeClass = switch -Regex ($dominantType) { "Site Collection" { "bc"; break } @@ -1525,7 +1525,7 @@ a:hover{text-decoration:underline} $rows -
Generated by SharePoint Exporter v6.0
+
Generated by SharePoint Toolbox
@@ -1601,7 +1601,7 @@ function Export-StorageToHTML { $totalFiles = ($Data | Measure-Object -Property ItemCount -Sum).Sum $libCount = $Data.Count - # Shared toggle-ID counter — must be unique across all levels of nesting + # Shared toggle-ID counter - must be unique across all levels of nesting $script:togIdx = 0 # Recursively builds folder rows; each folder with children gets its own toggle @@ -1732,7 +1732,7 @@ function toggle(i){ $rows
-
Generated by SharePoint Exporter v6.0
+
Generated by SharePoint Toolbox
"@ $html | Out-File -FilePath $OutputPath -Encoding UTF8 @@ -1997,7 +1997,7 @@ function Get-SiteStorageMetrics { $webObj = Get-PnPWeb $wTitle = $webObj.Title $wUrl = $webObj.Url - # ServerRelativeUrl of the web (e.g. "/sites/MySite") — used to compute site-relative paths + # ServerRelativeUrl of the web (e.g. "/sites/MySite") - used to compute site-relative paths $wSrl = $webObj.ServerRelativeUrl.TrimEnd('/') if ($PerLibrary) { @@ -2367,17 +2367,214 @@ function filterGroups(){ #endregion +#region ===== Internationalization ===== + +$script:LangDefault = @{ + "profile" = "Profile:" + "tenant.url" = "Tenant URL:" + "client.id" = "Client ID:" + "site.url" = "Site URL:" + "output.folder" = "Output Folder:" + "btn.new" = "New" + "btn.save" = "Save" + "btn.rename" = "Rename" + "btn.delete" = "Del." + "btn.view.sites" = "View Sites" + "btn.browse" = "Browse..." + "tab.perms" = " Permissions " + "tab.storage" = " Storage " + "tab.templates" = " Templates " + "tab.search" = " File Search " + "tab.dupes" = " Duplicates " + "grp.scan.opts" = "Scan Options" + "chk.scan.folders" = "Scan Folders" + "chk.recursive" = "Recursive (subsites)" + "lbl.folder.depth" = "Folder depth:" + "chk.max.depth" = "Maximum (all levels)" + "chk.inherited.perms" = "Include Inherited Permissions" + "grp.export.fmt" = "Export Format" + "rad.csv.perms" = "CSV" + "rad.html.perms" = "HTML" + "btn.gen.perms" = "Generate Report" + "btn.open.perms" = "Open Report" + "chk.per.lib" = "Per-Library Breakdown" + "chk.subsites" = "Include Subsites" + "stor.note" = "Note: deeper folder scans on large sites may take several minutes." + "btn.gen.storage" = "Generate Metrics" + "btn.open.storage" = "Open Report" + "tpl.desc" = "Create templates from an existing site and apply them to create new sites." + "btn.manage.tpl" = "Manage templates..." + "tpl.count" = "template(s) saved - click to manage" + "grp.search.filters" = "Search Filters" + "lbl.extensions" = "Extension(s):" + "lbl.regex" = "Name / Regex:" + "chk.created.after" = "Created after:" + "chk.created.before" = "Created before:" + "chk.modified.after" = "Modified after:" + "chk.modified.before" = "Modified before:" + "lbl.created.by" = "Created by:" + "lbl.modified.by" = "Modified by:" + "lbl.library" = "Library:" + "grp.search.fmt" = "Export Format" + "lbl.max.results" = "Max results:" + "btn.run.search" = "Run Search" + "btn.open.search" = "Open Results" + "grp.dup.type" = "Duplicate Type" + "rad.dup.files" = "Duplicate files" + "rad.dup.folders" = "Duplicate folders" + "grp.dup.criteria" = "Comparison Criteria" + "lbl.dup.note" = "Name is always the primary criterion. Check additional criteria:" + "chk.dup.size" = "Same size" + "chk.dup.created" = "Same creation date" + "chk.dup.modified" = "Same modification date" + "chk.dup.subfolders" = "Same subfolder count" + "chk.dup.filecount" = "Same file count" + "grp.options" = "Options" + "chk.include.subsites" = "Include subsites" + "btn.run.scan" = "Run Scan" + "btn.open.results" = "Open Results" + "lbl.log" = "Log:" + "menu.settings" = "Settings" + "menu.json.folder" = "JSON Data Folder..." + "menu.language" = "Language" + "dlg.json.folder.desc" = "Select the storage folder for JSON files (profiles, templates)" + "dlg.folder.not.found" = "The folder '{0}' does not exist. Do you want to create it?" + "dlg.folder.not.found.title"= "Folder not found" + "msg.lang.applied" = "Language applied: {0}" + "msg.lang.applied.title" = "Language" + "ph.extensions" = "docx pdf xlsx" + "ph.regex" = "Ex: report.* or \.bak$" + "ph.created.by" = "First Last or email" + "ph.modified.by" = "First Last or email" + "ph.library" = "Optional relative path e.g. Shared Documents" + "ph.dup.lib" = "All (leave empty)" +} + +$script:Lang = $null # null = use LangDefault + +function T([string]$key) { + if ($script:Lang -and $script:Lang.$key) { return $script:Lang.$key } + if ($script:LangDefault.ContainsKey($key)) { return $script:LangDefault[$key] } + return $key +} + +function Get-LangDir { + $base = if ($PSScriptRoot) { $PSScriptRoot } else { $PWD.Path } + return Join-Path $base "lang" +} + +function Get-LangFiles { + $dir = Get-LangDir + if (-not (Test-Path $dir)) { return @() } + return @(Get-ChildItem -Path $dir -Filter "*.json" | ForEach-Object { + $code = $_.BaseName + $name = $code + try { + $data = Get-Content $_.FullName -Raw | ConvertFrom-Json + if ($data.'_name') { $name = $data.'_name' } + } catch {} + [PSCustomObject]@{ Code = $code; Name = $name; Path = $_.FullName } + }) +} + +function Load-Language([string]$LangCode) { + if ([string]::IsNullOrWhiteSpace($LangCode) -or $LangCode -eq "en") { + $script:Lang = $null + $script:CurrentLang = "en" + return + } + $dir = Get-LangDir + $path = Join-Path $dir "$LangCode.json" + if (-not (Test-Path $path)) { return } + try { + $data = Get-Content $path -Raw | ConvertFrom-Json + $ht = @{} + $data.PSObject.Properties | ForEach-Object { $ht[$_.Name] = $_.Value } + $script:Lang = $ht + $script:CurrentLang = $LangCode + } catch {} +} + +function Update-UILanguage { + # Main labels + if ($script:i18nMap) { + foreach ($kv in $script:i18nMap.GetEnumerator()) { + $ctrl = $kv.Value.Control + $key = $kv.Value.Key + if ($ctrl -and !$ctrl.IsDisposed) { $ctrl.Text = T $key } + } + } + # Tab pages (Text property) + if ($script:i18nTabs) { + foreach ($kv in $script:i18nTabs.GetEnumerator()) { + $tab = $kv.Value.Control + $key = $kv.Value.Key + if ($tab -and !$tab.IsDisposed) { $tab.Text = T $key } + } + } + # Menu items + if ($script:i18nMenus) { + foreach ($kv in $script:i18nMenus.GetEnumerator()) { + $mi = $kv.Value.Control + $key = $kv.Value.Key + if ($mi) { $mi.Text = T $key } + } + } + # Placeholder texts + if ($script:i18nPlaceholders) { + foreach ($kv in $script:i18nPlaceholders.GetEnumerator()) { + $ctrl = $kv.Value.Control + $key = $kv.Value.Key + if ($ctrl -and !$ctrl.IsDisposed) { $ctrl.PlaceholderText = T $key } + } + } +} + +$script:CurrentLang = "en" + +#endregion + #region ===== GUI ===== $form = New-Object System.Windows.Forms.Form -$form.Text = "SharePoint Exporter v6.0" +$form.Text = "SharePoint Toolbox" $form.Size = New-Object System.Drawing.Size(700, 840) $form.StartPosition = "CenterScreen" $form.FormBorderStyle = "FixedDialog" $form.MaximizeBox = $false $form.BackColor = [System.Drawing.Color]::WhiteSmoke -# ── Shared: Client ID ────────────────────────────────────────────────────────── +# ── MenuStrip ───────────────────────────────────────────────────────────────── +$menuStrip = New-Object System.Windows.Forms.MenuStrip +$menuStrip.BackColor = [System.Drawing.Color]::WhiteSmoke +$menuStrip.RenderMode = [System.Windows.Forms.ToolStripRenderMode]::System + +$menuSettings = New-Object System.Windows.Forms.ToolStripMenuItem +$menuSettings.Text = T "menu.settings" +$menuJsonFolder = New-Object System.Windows.Forms.ToolStripMenuItem +$menuJsonFolder.Text = T "menu.json.folder" +[void]$menuSettings.DropDownItems.Add($menuJsonFolder) + +$menuLang = New-Object System.Windows.Forms.ToolStripMenuItem +$menuLang.Text = T "menu.language" +$menuLangEn = New-Object System.Windows.Forms.ToolStripMenuItem +$menuLangEn.Text = "English (US)" +$menuLangEn.Tag = "en" +$menuLangEn.Checked = ($script:CurrentLang -eq "en") +[void]$menuLang.DropDownItems.Add($menuLangEn) +[void]$menuLang.DropDownItems.Add([System.Windows.Forms.ToolStripSeparator]::new()) +foreach ($lf in (Get-LangFiles)) { + $mi = New-Object System.Windows.Forms.ToolStripMenuItem + $mi.Text = $lf.Name + $mi.Tag = $lf.Code + $mi.Checked = ($script:CurrentLang -eq $lf.Code) + [void]$menuLang.DropDownItems.Add($mi) +} +[void]$menuStrip.Items.Add($menuSettings) +[void]$menuStrip.Items.Add($menuLang) +$form.MainMenuStrip = $menuStrip + +# ── Label helper (positions offset +24 to account for MenuStrip) ────────────── $lbl = { param($t,$x,$y) $l = New-Object System.Windows.Forms.Label $l.Text = $t; $l.Location = New-Object System.Drawing.Point($x,$y) @@ -2385,85 +2582,74 @@ $lbl = { param($t,$x,$y) } # ── Profile selector ────────────────────────────────────────────────────────── -$lblProfile = (& $lbl "Profil :" 20 22) +$lblProfile = (& $lbl (T "profile") 20 46) $cboProfile = New-Object System.Windows.Forms.ComboBox -$cboProfile.Location = New-Object System.Drawing.Point(140, 20) +$cboProfile.Location = New-Object System.Drawing.Point(140, 44) $cboProfile.Size = New-Object System.Drawing.Size(248, 24) $cboProfile.DropDownStyle = "DropDownList" $cboProfile.Font = New-Object System.Drawing.Font("Segoe UI", 9) $btnProfileNew = New-Object System.Windows.Forms.Button -$btnProfileNew.Text = "Creer" -$btnProfileNew.Location = New-Object System.Drawing.Point(396, 19) +$btnProfileNew.Text = T "btn.new" +$btnProfileNew.Location = New-Object System.Drawing.Point(396, 43) $btnProfileNew.Size = New-Object System.Drawing.Size(60, 26) $btnProfileSave = New-Object System.Windows.Forms.Button -$btnProfileSave.Text = "Sauver" -$btnProfileSave.Location = New-Object System.Drawing.Point(460, 19) +$btnProfileSave.Text = T "btn.save" +$btnProfileSave.Location = New-Object System.Drawing.Point(460, 43) $btnProfileSave.Size = New-Object System.Drawing.Size(60, 26) $btnProfileRename = New-Object System.Windows.Forms.Button -$btnProfileRename.Text = "Renommer" -$btnProfileRename.Location = New-Object System.Drawing.Point(524, 19) +$btnProfileRename.Text = T "btn.rename" +$btnProfileRename.Location = New-Object System.Drawing.Point(524, 43) $btnProfileRename.Size = New-Object System.Drawing.Size(72, 26) $btnProfileDelete = New-Object System.Windows.Forms.Button -$btnProfileDelete.Text = "Suppr." -$btnProfileDelete.Location = New-Object System.Drawing.Point(600, 19) +$btnProfileDelete.Text = T "btn.delete" +$btnProfileDelete.Location = New-Object System.Drawing.Point(600, 43) $btnProfileDelete.Size = New-Object System.Drawing.Size(62, 26) -$lblTenantUrl = (& $lbl "Tenant URL :" 20 52) +$lblTenantUrl = (& $lbl (T "tenant.url") 20 76) $txtTenantUrl = New-Object System.Windows.Forms.TextBox -$txtTenantUrl.Location = New-Object System.Drawing.Point(140, 52) +$txtTenantUrl.Location = New-Object System.Drawing.Point(140, 76) $txtTenantUrl.Size = New-Object System.Drawing.Size(400, 22) $txtTenantUrl.Font = New-Object System.Drawing.Font("Consolas", 9) $btnBrowseSites = New-Object System.Windows.Forms.Button -$btnBrowseSites.Text = "Voir les sites" -$btnBrowseSites.Location = New-Object System.Drawing.Point(548, 50) +$btnBrowseSites.Text = T "btn.view.sites" +$btnBrowseSites.Location = New-Object System.Drawing.Point(548, 74) $btnBrowseSites.Size = New-Object System.Drawing.Size(92, 26) -$lblClientId = (& $lbl "Client ID :" 20 84) +$lblClientId = (& $lbl (T "client.id") 20 108) $txtClientId = New-Object System.Windows.Forms.TextBox -$txtClientId.Location = New-Object System.Drawing.Point(140, 84) +$txtClientId.Location = New-Object System.Drawing.Point(140, 108) $txtClientId.Size = New-Object System.Drawing.Size(500, 22) $txtClientId.Font = New-Object System.Drawing.Font("Consolas", 9) -$lblSiteURL = (& $lbl "Site URL :" 20 116) +$lblSiteURL = (& $lbl (T "site.url") 20 140) $txtSiteURL = New-Object System.Windows.Forms.TextBox -$txtSiteURL.Location = New-Object System.Drawing.Point(140, 116) +$txtSiteURL.Location = New-Object System.Drawing.Point(140, 140) $txtSiteURL.Size = New-Object System.Drawing.Size(500, 22) -$lblOutput = (& $lbl "Output Folder :" 20 148) +$lblOutput = (& $lbl (T "output.folder") 20 172) $txtOutput = New-Object System.Windows.Forms.TextBox -$txtOutput.Location = New-Object System.Drawing.Point(140, 148) +$txtOutput.Location = New-Object System.Drawing.Point(140, 172) $txtOutput.Size = New-Object System.Drawing.Size(408, 22) $txtOutput.Text = $PWD.Path $btnBrowse = New-Object System.Windows.Forms.Button -$btnBrowse.Text = "Browse..." -$btnBrowse.Location = New-Object System.Drawing.Point(558, 146) +$btnBrowse.Text = T "btn.browse" +$btnBrowse.Location = New-Object System.Drawing.Point(558, 170) $btnBrowse.Size = New-Object System.Drawing.Size(82, 26) -$lblDataDir = (& $lbl "Dossier JSON :" 20 178) -$txtDataDir = New-Object System.Windows.Forms.TextBox -$txtDataDir.Location = New-Object System.Drawing.Point(140, 178) -$txtDataDir.Size = New-Object System.Drawing.Size(408, 22) -$txtDataDir.Font = New-Object System.Drawing.Font("Consolas", 9) - -$btnBrowseDataDir = New-Object System.Windows.Forms.Button -$btnBrowseDataDir.Text = "Browse..." -$btnBrowseDataDir.Location = New-Object System.Drawing.Point(558, 176) -$btnBrowseDataDir.Size = New-Object System.Drawing.Size(82, 26) - $sep = New-Object System.Windows.Forms.Panel -$sep.Location = New-Object System.Drawing.Point(20, 212) +$sep.Location = New-Object System.Drawing.Point(20, 206) $sep.Size = New-Object System.Drawing.Size(642, 1) $sep.BackColor = [System.Drawing.Color]::LightGray # ── TabControl ───────────────────────────────────────────────────────────────── $tabs = New-Object System.Windows.Forms.TabControl -$tabs.Location = New-Object System.Drawing.Point(10, 220) +$tabs.Location = New-Object System.Drawing.Point(10, 214) $tabs.Size = New-Object System.Drawing.Size(662, 310) $tabs.Font = New-Object System.Drawing.Font("Segoe UI", 9) @@ -2496,16 +2682,16 @@ function New-ActionBtn($text, $x, $y, $color) { # ══ Tab 1: Permissions ════════════════════════════════════════════════════════ $tabPerms = New-Object System.Windows.Forms.TabPage -$tabPerms.Text = " Permissions Report " +$tabPerms.Text = T "tab.perms" $tabPerms.BackColor = [System.Drawing.Color]::WhiteSmoke -$grpPermOpts = New-Group "Scan Options" 10 10 615 96 -$chkScanFolders = New-Check "Scan Folders" 15 24 150 $true -$chkRecursive = New-Check "Recursive (subsites)" 175 24 185 +$grpPermOpts = New-Group (T "grp.scan.opts") 10 10 615 96 +$chkScanFolders = New-Check (T "chk.scan.folders") 15 24 150 $true +$chkRecursive = New-Check (T "chk.recursive") 175 24 185 # Folder depth controls (only active when Scan Folders is checked) $lblPermDepth = New-Object System.Windows.Forms.Label -$lblPermDepth.Text = "Folder depth :" +$lblPermDepth.Text = T "lbl.folder.depth" $lblPermDepth.Location = New-Object System.Drawing.Point(15, 50) $lblPermDepth.Size = New-Object System.Drawing.Size(100, 22) $lblPermDepth.TextAlign = "MiddleLeft" @@ -2518,11 +2704,11 @@ $nudPermDepth.Maximum = 20 $nudPermDepth.Value = 1 $chkPermMaxDepth = New-Object System.Windows.Forms.CheckBox -$chkPermMaxDepth.Text = "Maximum (all levels)" +$chkPermMaxDepth.Text = T "chk.max.depth" $chkPermMaxDepth.Location = New-Object System.Drawing.Point(182, 52) $chkPermMaxDepth.Size = New-Object System.Drawing.Size(180, 20) -$chkInheritedPerms = New-Check "Include Inherited Permissions" 15 74 230 +$chkInheritedPerms = New-Check (T "chk.inherited.perms") 15 74 230 $grpPermOpts.Controls.AddRange(@($chkScanFolders, $chkRecursive, $lblPermDepth, $nudPermDepth, $chkPermMaxDepth, $chkInheritedPerms)) # Disable depth controls when Scan Folders is unchecked @@ -2537,14 +2723,14 @@ $chkPermMaxDepth.Add_CheckedChanged({ $nudPermDepth.Enabled = $chkScanFolders.Checked -and -not $chkPermMaxDepth.Checked }) -$grpPermFmt = New-Group "Export Format" 10 114 615 58 -$radPermCSV = New-Radio "CSV — raw data, Excel-friendly" 15 24 280 $true -$radPermHTML = New-Radio "HTML — visual report, client-friendly" 305 24 290 +$grpPermFmt = New-Group (T "grp.export.fmt") 10 114 615 58 +$radPermCSV = New-Radio (T "rad.csv.perms") 15 24 280 $true +$radPermHTML = New-Radio (T "rad.html.perms") 305 24 290 $grpPermFmt.Controls.AddRange(@($radPermCSV, $radPermHTML)) -$btnGenPerms = New-ActionBtn "Generate Report" 10 184 ([System.Drawing.Color]::SteelBlue) +$btnGenPerms = New-ActionBtn (T "btn.gen.perms") 10 184 ([System.Drawing.Color]::SteelBlue) $btnOpenPerms = New-Object System.Windows.Forms.Button -$btnOpenPerms.Text = "Open Report" +$btnOpenPerms.Text = T "btn.open.perms" $btnOpenPerms.Location = New-Object System.Drawing.Point(175, 184) $btnOpenPerms.Size = New-Object System.Drawing.Size(120, 34) $btnOpenPerms.Enabled = $false @@ -2553,16 +2739,16 @@ $tabPerms.Controls.AddRange(@($grpPermOpts, $grpPermFmt, $btnGenPerms, $btnOpenP # ══ Tab 2: Storage Metrics ════════════════════════════════════════════════════ $tabStorage = New-Object System.Windows.Forms.TabPage -$tabStorage.Text = " Storage Metrics " +$tabStorage.Text = T "tab.storage" $tabStorage.BackColor = [System.Drawing.Color]::WhiteSmoke -$grpStorOpts = New-Group "Scan Options" 10 10 615 108 -$chkStorPerLib = New-Check "Per-Library Breakdown" 15 24 200 $true -$chkStorSubsites = New-Check "Include Subsites" 230 24 170 +$grpStorOpts = New-Group (T "grp.scan.opts") 10 10 615 108 +$chkStorPerLib = New-Check (T "chk.per.lib") 15 24 200 $true +$chkStorSubsites = New-Check (T "chk.subsites") 230 24 170 # Folder depth controls (only relevant in per-library mode) $lblDepth = New-Object System.Windows.Forms.Label -$lblDepth.Text = "Folder depth :" +$lblDepth.Text = T "lbl.folder.depth" $lblDepth.Location = New-Object System.Drawing.Point(15, 52) $lblDepth.Size = New-Object System.Drawing.Size(100, 22) $lblDepth.TextAlign = "MiddleLeft" @@ -2575,12 +2761,12 @@ $nudDepth.Maximum = 20 $nudDepth.Value = 1 $chkMaxDepth = New-Object System.Windows.Forms.CheckBox -$chkMaxDepth.Text = "Maximum (all levels)" +$chkMaxDepth.Text = T "chk.max.depth" $chkMaxDepth.Location = New-Object System.Drawing.Point(182, 54) $chkMaxDepth.Size = New-Object System.Drawing.Size(180, 20) $lblStorNote = New-Object System.Windows.Forms.Label -$lblStorNote.Text = "Note: deeper folder scans on large sites may take several minutes." +$lblStorNote.Text = T "stor.note" $lblStorNote.Location = New-Object System.Drawing.Point(15, 80) $lblStorNote.Size = New-Object System.Drawing.Size(580, 18) $lblStorNote.ForeColor = [System.Drawing.Color]::Gray @@ -2588,15 +2774,15 @@ $lblStorNote.Font = New-Object System.Drawing.Font("Segoe UI", 8, [System.D $grpStorOpts.Controls.AddRange(@($chkStorPerLib, $chkStorSubsites, $lblDepth, $nudDepth, $chkMaxDepth, $lblStorNote)) -$grpStorFmt = New-Group "Export Format" 10 128 615 58 -$radStorCSV = New-Radio "CSV — raw data, Excel-friendly" 15 24 280 $true -$radStorHTML = New-Radio "HTML — visual report, client-friendly" 305 24 290 +$grpStorFmt = New-Group (T "grp.export.fmt") 10 128 615 58 +$radStorCSV = New-Radio (T "rad.csv.perms") 15 24 280 $true +$radStorHTML = New-Radio (T "rad.html.perms") 305 24 290 $grpStorFmt.Controls.AddRange(@($radStorCSV, $radStorHTML)) $msGreen = [System.Drawing.Color]::FromArgb(16,124,16) -$btnGenStorage = New-ActionBtn "Generate Metrics" 10 200 $msGreen +$btnGenStorage = New-ActionBtn (T "btn.gen.storage") 10 200 $msGreen $btnOpenStorage = New-Object System.Windows.Forms.Button -$btnOpenStorage.Text = "Open Report" +$btnOpenStorage.Text = T "btn.open.storage" $btnOpenStorage.Location = New-Object System.Drawing.Point(175, 200) $btnOpenStorage.Size = New-Object System.Drawing.Size(120, 34) $btnOpenStorage.Enabled = $false @@ -2617,11 +2803,11 @@ $tabStorage.Controls.AddRange(@($grpStorOpts, $grpStorFmt, $btnGenStorage, $btnO # ══ Tab 3: Templates ══════════════════════════════════════════════════════ $tabTemplates = New-Object System.Windows.Forms.TabPage -$tabTemplates.Text = " Templates " +$tabTemplates.Text = T "tab.templates" $tabTemplates.BackColor = [System.Drawing.Color]::WhiteSmoke $lblTplDesc = New-Object System.Windows.Forms.Label -$lblTplDesc.Text = "Creez des templates depuis un site existant et appliquez-les pour creer de nouveaux sites." +$lblTplDesc.Text = T "tpl.desc" $lblTplDesc.Location = New-Object System.Drawing.Point(10, 18) $lblTplDesc.Size = New-Object System.Drawing.Size(580, 20) $lblTplDesc.ForeColor = [System.Drawing.Color]::DimGray @@ -2634,7 +2820,7 @@ $lblTplCount.ForeColor = [System.Drawing.Color]::DimGray $lblTplCount.Font = New-Object System.Drawing.Font("Segoe UI", 8, [System.Drawing.FontStyle]::Italic) $btnOpenTplMgr = New-Object System.Windows.Forms.Button -$btnOpenTplMgr.Text = "Gerer les templates..." +$btnOpenTplMgr.Text = T "btn.manage.tpl" $btnOpenTplMgr.Location = New-Object System.Drawing.Point(10, 72) $btnOpenTplMgr.Size = New-Object System.Drawing.Size(185, 34) $btnOpenTplMgr.BackColor = [System.Drawing.Color]::FromArgb(50, 50, 120) @@ -2646,15 +2832,15 @@ $tabTemplates.Controls.AddRange(@($lblTplDesc, $lblTplCount, $btnOpenTplMgr)) # ══ Tab 4: Recherche de fichiers ══════════════════════════════════════════════ $tabSearch = New-Object System.Windows.Forms.TabPage -$tabSearch.Text = " Recherche de fichiers " +$tabSearch.Text = T "tab.search" $tabSearch.BackColor = [System.Drawing.Color]::WhiteSmoke # ── GroupBox Filtres ─────────────────────────────────────────────────────────── -$grpSearchFilters = New-Group "Filtres de recherche" 10 6 620 170 +$grpSearchFilters = New-Group (T "grp.search.filters") 10 6 620 170 -# Row 1 — Extension & Regex +# Row 1 - Extension & Regex $lblSrchExt = New-Object System.Windows.Forms.Label -$lblSrchExt.Text = "Extension(s) :" +$lblSrchExt.Text = T "lbl.extensions" $lblSrchExt.Location = New-Object System.Drawing.Point(10, 24) $lblSrchExt.Size = New-Object System.Drawing.Size(88, 22) $lblSrchExt.TextAlign = "MiddleLeft" @@ -2662,10 +2848,10 @@ $txtSrchExt = New-Object System.Windows.Forms.TextBox $txtSrchExt.Location = New-Object System.Drawing.Point(100, 24) $txtSrchExt.Size = New-Object System.Drawing.Size(120, 22) $txtSrchExt.Font = New-Object System.Drawing.Font("Consolas", 9) -$txtSrchExt.PlaceholderText = "docx pdf xlsx" +$txtSrchExt.PlaceholderText = T "ph.extensions" $lblSrchRegex = New-Object System.Windows.Forms.Label -$lblSrchRegex.Text = "Nom / Regex :" +$lblSrchRegex.Text = T "lbl.regex" $lblSrchRegex.Location = New-Object System.Drawing.Point(232, 24) $lblSrchRegex.Size = New-Object System.Drawing.Size(88, 22) $lblSrchRegex.TextAlign = "MiddleLeft" @@ -2673,11 +2859,11 @@ $txtSrchRegex = New-Object System.Windows.Forms.TextBox $txtSrchRegex.Location = New-Object System.Drawing.Point(322, 24) $txtSrchRegex.Size = New-Object System.Drawing.Size(286, 22) $txtSrchRegex.Font = New-Object System.Drawing.Font("Consolas", 9) -$txtSrchRegex.PlaceholderText = "Ex: rapport.* ou \.bak$" +$txtSrchRegex.PlaceholderText = T "ph.regex" -# Row 2 — Created dates +# Row 2 - Created dates $chkSrchCrA = New-Object System.Windows.Forms.CheckBox -$chkSrchCrA.Text = "Cree apres le :" +$chkSrchCrA.Text = T "chk.created.after" $chkSrchCrA.Location = New-Object System.Drawing.Point(10, 52) $chkSrchCrA.Size = New-Object System.Drawing.Size(108, 22) $dtpSrchCrA = New-Object System.Windows.Forms.DateTimePicker @@ -2687,7 +2873,7 @@ $dtpSrchCrA.Format = [System.Windows.Forms.DateTimePickerFormat]::Short $dtpSrchCrA.Enabled = $false $chkSrchCrB = New-Object System.Windows.Forms.CheckBox -$chkSrchCrB.Text = "Cree avant le :" +$chkSrchCrB.Text = T "chk.created.before" $chkSrchCrB.Location = New-Object System.Drawing.Point(262, 52) $chkSrchCrB.Size = New-Object System.Drawing.Size(108, 22) $dtpSrchCrB = New-Object System.Windows.Forms.DateTimePicker @@ -2699,9 +2885,9 @@ $dtpSrchCrB.Enabled = $false $chkSrchCrA.Add_CheckedChanged({ $dtpSrchCrA.Enabled = $chkSrchCrA.Checked }) $chkSrchCrB.Add_CheckedChanged({ $dtpSrchCrB.Enabled = $chkSrchCrB.Checked }) -# Row 3 — Modified dates +# Row 3 - Modified dates $chkSrchModA = New-Object System.Windows.Forms.CheckBox -$chkSrchModA.Text = "Modifie apres :" +$chkSrchModA.Text = T "chk.modified.after" $chkSrchModA.Location = New-Object System.Drawing.Point(10, 80) $chkSrchModA.Size = New-Object System.Drawing.Size(108, 22) $dtpSrchModA = New-Object System.Windows.Forms.DateTimePicker @@ -2711,7 +2897,7 @@ $dtpSrchModA.Format = [System.Windows.Forms.DateTimePickerFormat]::Short $dtpSrchModA.Enabled = $false $chkSrchModB = New-Object System.Windows.Forms.CheckBox -$chkSrchModB.Text = "Modifie avant :" +$chkSrchModB.Text = T "chk.modified.before" $chkSrchModB.Location = New-Object System.Drawing.Point(262, 80) $chkSrchModB.Size = New-Object System.Drawing.Size(108, 22) $dtpSrchModB = New-Object System.Windows.Forms.DateTimePicker @@ -2723,37 +2909,37 @@ $dtpSrchModB.Enabled = $false $chkSrchModA.Add_CheckedChanged({ $dtpSrchModA.Enabled = $chkSrchModA.Checked }) $chkSrchModB.Add_CheckedChanged({ $dtpSrchModB.Enabled = $chkSrchModB.Checked }) -# Row 4 — Created by / Modified by +# Row 4 - Created by / Modified by $lblSrchCrBy = New-Object System.Windows.Forms.Label -$lblSrchCrBy.Text = "Cree par :" +$lblSrchCrBy.Text = T "lbl.created.by" $lblSrchCrBy.Location = New-Object System.Drawing.Point(10, 108) $lblSrchCrBy.Size = New-Object System.Drawing.Size(70, 22) $lblSrchCrBy.TextAlign = "MiddleLeft" $txtSrchCrBy = New-Object System.Windows.Forms.TextBox $txtSrchCrBy.Location = New-Object System.Drawing.Point(82, 108) $txtSrchCrBy.Size = New-Object System.Drawing.Size(168, 22) -$txtSrchCrBy.PlaceholderText = "Prenom Nom ou email" +$txtSrchCrBy.PlaceholderText = T "ph.created.by" $lblSrchModBy = New-Object System.Windows.Forms.Label -$lblSrchModBy.Text = "Modifie par :" +$lblSrchModBy.Text = T "lbl.modified.by" $lblSrchModBy.Location = New-Object System.Drawing.Point(262, 108) $lblSrchModBy.Size = New-Object System.Drawing.Size(82, 22) $lblSrchModBy.TextAlign = "MiddleLeft" $txtSrchModBy = New-Object System.Windows.Forms.TextBox $txtSrchModBy.Location = New-Object System.Drawing.Point(346, 108) $txtSrchModBy.Size = New-Object System.Drawing.Size(168, 22) -$txtSrchModBy.PlaceholderText = "Prenom Nom ou email" +$txtSrchModBy.PlaceholderText = T "ph.modified.by" -# Row 5 — Library filter +# Row 5 - Library filter $lblSrchLib = New-Object System.Windows.Forms.Label -$lblSrchLib.Text = "Bibliotheque :" +$lblSrchLib.Text = T "lbl.library" $lblSrchLib.Location = New-Object System.Drawing.Point(10, 136) $lblSrchLib.Size = New-Object System.Drawing.Size(88, 22) $lblSrchLib.TextAlign = "MiddleLeft" $txtSrchLib = New-Object System.Windows.Forms.TextBox $txtSrchLib.Location = New-Object System.Drawing.Point(100, 136) $txtSrchLib.Size = New-Object System.Drawing.Size(508, 22) -$txtSrchLib.PlaceholderText = "Chemin relatif optionnel ex: Documents partages" +$txtSrchLib.PlaceholderText = T "ph.library" $grpSearchFilters.Controls.AddRange(@( $lblSrchExt, $txtSrchExt, $lblSrchRegex, $txtSrchRegex, @@ -2764,11 +2950,11 @@ $grpSearchFilters.Controls.AddRange(@( )) # ── GroupBox Format ──────────────────────────────────────────────────────────── -$grpSearchFmt = New-Group "Format d'export" 10 180 620 48 -$radSrchCSV = New-Radio "CSV (Excel)" 15 22 130 $true -$radSrchHTML = New-Radio "HTML (rapport visuel)" 160 22 180 +$grpSearchFmt = New-Group (T "grp.search.fmt") 10 180 620 48 +$radSrchCSV = New-Radio (T "rad.csv.perms") 15 22 130 $true +$radSrchHTML = New-Radio (T "rad.html.perms") 160 22 180 $lblSrchMax = New-Object System.Windows.Forms.Label -$lblSrchMax.Text = "Max resultats :" +$lblSrchMax.Text = T "lbl.max.results" $lblSrchMax.Location = New-Object System.Drawing.Point(360, 22) $lblSrchMax.Size = New-Object System.Drawing.Size(96, 22) $lblSrchMax.TextAlign = "MiddleLeft" @@ -2782,9 +2968,9 @@ $nudSrchMax.Increment = 100 $grpSearchFmt.Controls.AddRange(@($radSrchCSV, $radSrchHTML, $lblSrchMax, $nudSrchMax)) # ── Buttons ──────────────────────────────────────────────────────────────────── -$btnSearch = New-ActionBtn "Lancer la recherche" 10 232 ([System.Drawing.Color]::FromArgb(0, 120, 212)) +$btnSearch = New-ActionBtn (T "btn.run.search") 10 232 ([System.Drawing.Color]::FromArgb(0, 120, 212)) $btnOpenSearch = New-Object System.Windows.Forms.Button -$btnOpenSearch.Text = "Ouvrir resultats" +$btnOpenSearch.Text = T "btn.open.search" $btnOpenSearch.Location = New-Object System.Drawing.Point(175, 232) $btnOpenSearch.Size = New-Object System.Drawing.Size(130, 34) $btnOpenSearch.Enabled = $false @@ -2793,33 +2979,33 @@ $tabSearch.Controls.AddRange(@($grpSearchFilters, $grpSearchFmt, $btnSearch, $bt # ══ Tab 5: Doublons ═══════════════════════════════════════════════════════════ $tabDupes = New-Object System.Windows.Forms.TabPage -$tabDupes.Text = " Doublons " +$tabDupes.Text = T "tab.dupes" $tabDupes.BackColor = [System.Drawing.Color]::WhiteSmoke # ── GroupBox: Type de doublons (y=4, h=44 → bottom 48) ────────────────────── -$grpDupType = New-Group "Type de doublons" 10 4 638 44 -$radDupFiles = New-Radio "Fichiers en double" 10 16 190 $true -$radDupFolders = New-Radio "Dossiers en double" 210 16 190 +$grpDupType = New-Group (T "grp.dup.type") 10 4 638 44 +$radDupFiles = New-Radio (T "rad.dup.files") 10 16 190 $true +$radDupFolders = New-Radio (T "rad.dup.folders") 210 16 190 $grpDupType.Controls.AddRange(@($radDupFiles, $radDupFolders)) # ── GroupBox: Critères de comparaison (y=52, h=88 → bottom 140) ───────────── -$grpDupCrit = New-Group "Criteres de comparaison" 10 52 638 88 +$grpDupCrit = New-Group (T "grp.dup.criteria") 10 52 638 88 $lblDupNote = New-Object System.Windows.Forms.Label -$lblDupNote.Text = "Le nom est toujours le critere principal. Cochez les criteres supplementaires :" +$lblDupNote.Text = T "lbl.dup.note" $lblDupNote.Location = New-Object System.Drawing.Point(10, 15) $lblDupNote.Size = New-Object System.Drawing.Size(610, 16) $lblDupNote.Font = New-Object System.Drawing.Font("Segoe UI", 8, [System.Drawing.FontStyle]::Italic) $lblDupNote.ForeColor = [System.Drawing.Color]::DimGray -# Row 1 — criteres communs -$chkDupSize = New-Check "Taille identique" 10 34 148 $true -$chkDupCreated = New-Check "Date de creation identique" 164 34 208 -$chkDupModified = New-Check "Date de modification identique" 378 34 226 +# Row 1 - criteres communs +$chkDupSize = New-Check (T "chk.dup.size") 10 34 148 $true +$chkDupCreated = New-Check (T "chk.dup.created") 164 34 208 +$chkDupModified = New-Check (T "chk.dup.modified") 378 34 226 -# Row 2 — criteres dossiers uniquement -$chkDupSubCount = New-Check "Nb sous-dossiers identique" 10 60 210 -$chkDupFileCount = New-Check "Nb fichiers identique" 226 60 200 +# Row 2 - criteres dossiers uniquement +$chkDupSubCount = New-Check (T "chk.dup.subfolders") 10 60 210 +$chkDupFileCount = New-Check (T "chk.dup.filecount") 226 60 200 $chkDupSubCount.Enabled = $false $chkDupFileCount.Enabled = $false @@ -2839,29 +3025,29 @@ $radDupFolders.Add_CheckedChanged({ }) # ── GroupBox: Options (y=144, h=44 → bottom 188) ───────────────────────────── -$grpDupOpts = New-Group "Options" 10 144 638 44 -$chkDupSubsites = New-Check "Inclure les sous-sites" 10 18 192 +$grpDupOpts = New-Group (T "grp.options") 10 144 638 44 +$chkDupSubsites = New-Check (T "chk.include.subsites") 10 18 192 $lblDupLib = New-Object System.Windows.Forms.Label -$lblDupLib.Text = "Bibliotheque :" +$lblDupLib.Text = T "lbl.library" $lblDupLib.Location = New-Object System.Drawing.Point(210, 18) $lblDupLib.Size = New-Object System.Drawing.Size(88, 22) $lblDupLib.TextAlign = "MiddleLeft" $txtDupLib = New-Object System.Windows.Forms.TextBox $txtDupLib.Location = New-Object System.Drawing.Point(300, 18) $txtDupLib.Size = New-Object System.Drawing.Size(326, 22) -$txtDupLib.PlaceholderText = "Toutes (laisser vide)" +$txtDupLib.PlaceholderText = T "ph.dup.lib" $grpDupOpts.Controls.AddRange(@($chkDupSubsites, $lblDupLib, $txtDupLib)) # ── GroupBox: Format (y=192, h=40 → bottom 232) ────────────────────────────── -$grpDupFmt = New-Group "Format d'export" 10 192 638 40 -$radDupCSV = New-Radio "CSV (Excel)" 10 16 130 $true -$radDupHTML = New-Radio "HTML (rapport visuel)" 155 16 200 +$grpDupFmt = New-Group (T "grp.export.fmt") 10 192 638 40 +$radDupCSV = New-Radio (T "rad.csv.perms") 10 16 130 $true +$radDupHTML = New-Radio (T "rad.html.perms") 155 16 200 $grpDupFmt.Controls.AddRange(@($radDupCSV, $radDupHTML)) # ── Buttons (y=236 → bottom 270, within 284px inner) ───────────────────────── -$btnScanDupes = New-ActionBtn "Lancer le scan" 10 236 ([System.Drawing.Color]::FromArgb(136, 0, 21)) +$btnScanDupes = New-ActionBtn (T "btn.run.scan") 10 236 ([System.Drawing.Color]::FromArgb(136, 0, 21)) $btnOpenDupes = New-Object System.Windows.Forms.Button -$btnOpenDupes.Text = "Ouvrir resultats" +$btnOpenDupes.Text = T "btn.open.results" $btnOpenDupes.Location = New-Object System.Drawing.Point(175, 236) $btnOpenDupes.Size = New-Object System.Drawing.Size(130, 34) $btnOpenDupes.Enabled = $false @@ -2879,7 +3065,7 @@ $progressBar.MarqueeAnimationSpeed = 0 # ── Log ──────────────────────────────────────────────────────────────────────── $lblLog = New-Object System.Windows.Forms.Label -$lblLog.Text = "Log :" +$lblLog.Text = T "lbl.log" $lblLog.Location = New-Object System.Drawing.Point(20, 564) $lblLog.Size = New-Object System.Drawing.Size(60, 20) @@ -2902,18 +3088,126 @@ $script:Profiles = @() $script:SelectedSites = @() $form.Controls.AddRange(@( + $menuStrip, $lblProfile, $cboProfile, $btnProfileNew, $btnProfileSave, $btnProfileRename, $btnProfileDelete, $lblTenantUrl, $txtTenantUrl, $btnBrowseSites, $lblClientId, $txtClientId, $lblSiteURL, $txtSiteURL, $lblOutput, $txtOutput, $btnBrowse, - $lblDataDir, $txtDataDir, $btnBrowseDataDir, $sep, $tabs, $progressBar, $lblLog, $txtLog )) +# ── i18n control registration ────────────────────────────────────────────────── +$script:i18nMap = [System.Collections.Generic.Dictionary[string,object]]::new() +$script:i18nTabs = [System.Collections.Generic.Dictionary[string,object]]::new() +$script:i18nMenus = [System.Collections.Generic.Dictionary[string,object]]::new() + +$_reg = { + param($dict, $ctrl, $key) + $dict[[System.Guid]::NewGuid().ToString()] = [PSCustomObject]@{ Control = $ctrl; Key = $key } +} + +# Main labels & buttons +& $_reg $script:i18nMap $lblProfile "profile" +& $_reg $script:i18nMap $btnProfileNew "btn.new" +& $_reg $script:i18nMap $btnProfileSave "btn.save" +& $_reg $script:i18nMap $btnProfileRename "btn.rename" +& $_reg $script:i18nMap $btnProfileDelete "btn.delete" +& $_reg $script:i18nMap $btnBrowseSites "btn.view.sites" +& $_reg $script:i18nMap $lblTenantUrl "tenant.url" +& $_reg $script:i18nMap $lblClientId "client.id" +& $_reg $script:i18nMap $lblSiteURL "site.url" +& $_reg $script:i18nMap $lblOutput "output.folder" +& $_reg $script:i18nMap $btnBrowse "btn.browse" +& $_reg $script:i18nMap $lblLog "lbl.log" + +# Permissions tab controls +& $_reg $script:i18nMap $grpPermOpts "grp.scan.opts" +& $_reg $script:i18nMap $chkScanFolders "chk.scan.folders" +& $_reg $script:i18nMap $chkRecursive "chk.recursive" +& $_reg $script:i18nMap $lblPermDepth "lbl.folder.depth" +& $_reg $script:i18nMap $chkPermMaxDepth "chk.max.depth" +& $_reg $script:i18nMap $chkInheritedPerms "chk.inherited.perms" +& $_reg $script:i18nMap $grpPermFmt "grp.export.fmt" +& $_reg $script:i18nMap $radPermCSV "rad.csv.perms" +& $_reg $script:i18nMap $radPermHTML "rad.html.perms" +& $_reg $script:i18nMap $btnGenPerms "btn.gen.perms" +& $_reg $script:i18nMap $btnOpenPerms "btn.open.perms" + +# Storage tab controls +& $_reg $script:i18nMap $grpStorOpts "grp.scan.opts" +& $_reg $script:i18nMap $chkStorPerLib "chk.per.lib" +& $_reg $script:i18nMap $chkStorSubsites "chk.subsites" +& $_reg $script:i18nMap $lblDepth "lbl.folder.depth" +& $_reg $script:i18nMap $chkMaxDepth "chk.max.depth" +& $_reg $script:i18nMap $lblStorNote "stor.note" +& $_reg $script:i18nMap $grpStorFmt "grp.export.fmt" +& $_reg $script:i18nMap $radStorCSV "rad.csv.perms" +& $_reg $script:i18nMap $radStorHTML "rad.html.perms" +& $_reg $script:i18nMap $btnGenStorage "btn.gen.storage" +& $_reg $script:i18nMap $btnOpenStorage "btn.open.storage" + +# Templates tab controls +& $_reg $script:i18nMap $lblTplDesc "tpl.desc" +& $_reg $script:i18nMap $btnOpenTplMgr "btn.manage.tpl" + +# Search tab controls +& $_reg $script:i18nMap $grpSearchFilters "grp.search.filters" +& $_reg $script:i18nMap $lblSrchExt "lbl.extensions" +& $_reg $script:i18nMap $lblSrchRegex "lbl.regex" +& $_reg $script:i18nMap $chkSrchCrA "chk.created.after" +& $_reg $script:i18nMap $chkSrchCrB "chk.created.before" +& $_reg $script:i18nMap $chkSrchModA "chk.modified.after" +& $_reg $script:i18nMap $chkSrchModB "chk.modified.before" +& $_reg $script:i18nMap $lblSrchCrBy "lbl.created.by" +& $_reg $script:i18nMap $lblSrchModBy "lbl.modified.by" +& $_reg $script:i18nMap $lblSrchLib "lbl.library" +& $_reg $script:i18nMap $grpSearchFmt "grp.search.fmt" +& $_reg $script:i18nMap $lblSrchMax "lbl.max.results" +& $_reg $script:i18nMap $btnSearch "btn.run.search" +& $_reg $script:i18nMap $btnOpenSearch "btn.open.search" + +# Duplicates tab controls +& $_reg $script:i18nMap $grpDupType "grp.dup.type" +& $_reg $script:i18nMap $radDupFiles "rad.dup.files" +& $_reg $script:i18nMap $radDupFolders "rad.dup.folders" +& $_reg $script:i18nMap $grpDupCrit "grp.dup.criteria" +& $_reg $script:i18nMap $lblDupNote "lbl.dup.note" +& $_reg $script:i18nMap $chkDupSize "chk.dup.size" +& $_reg $script:i18nMap $chkDupCreated "chk.dup.created" +& $_reg $script:i18nMap $chkDupModified "chk.dup.modified" +& $_reg $script:i18nMap $chkDupSubCount "chk.dup.subfolders" +& $_reg $script:i18nMap $chkDupFileCount "chk.dup.filecount" +& $_reg $script:i18nMap $grpDupOpts "grp.options" +& $_reg $script:i18nMap $chkDupSubsites "chk.include.subsites" +& $_reg $script:i18nMap $lblDupLib "lbl.library" +& $_reg $script:i18nMap $btnScanDupes "btn.run.scan" +& $_reg $script:i18nMap $btnOpenDupes "btn.open.results" + +# Tab pages +& $_reg $script:i18nTabs $tabPerms "tab.perms" +& $_reg $script:i18nTabs $tabStorage "tab.storage" +& $_reg $script:i18nTabs $tabTemplates "tab.templates" +& $_reg $script:i18nTabs $tabSearch "tab.search" +& $_reg $script:i18nTabs $tabDupes "tab.dupes" + +# Menu items +& $_reg $script:i18nMenus $menuSettings "menu.settings" +& $_reg $script:i18nMenus $menuJsonFolder "menu.json.folder" +& $_reg $script:i18nMenus $menuLang "menu.language" + +# Placeholder texts +$script:i18nPlaceholders = [System.Collections.Generic.Dictionary[string,object]]::new() +& $_reg $script:i18nPlaceholders $txtSrchExt "ph.extensions" +& $_reg $script:i18nPlaceholders $txtSrchRegex "ph.regex" +& $_reg $script:i18nPlaceholders $txtSrchCrBy "ph.created.by" +& $_reg $script:i18nPlaceholders $txtSrchModBy "ph.modified.by" +& $_reg $script:i18nPlaceholders $txtSrchLib "ph.library" +& $_reg $script:i18nPlaceholders $txtDupLib "ph.dup.lib" + #endregion #region ===== Event Handlers ===== @@ -2984,48 +3278,55 @@ $btnBrowse.Add_Click({ if ($dlg.ShowDialog() -eq "OK") { $txtOutput.Text = $dlg.SelectedPath } }) -$btnBrowseDataDir.Add_Click({ +$menuJsonFolder.Add_Click({ $dlg = New-Object System.Windows.Forms.FolderBrowserDialog - $dlg.Description = "Selectionnez le dossier de stockage des fichiers JSON (profils, templates)" - $dlg.SelectedPath = if ($txtDataDir.Text -and (Test-Path $txtDataDir.Text)) { - $txtDataDir.Text + $dlg.Description = T "dlg.json.folder.desc" + $dlg.SelectedPath = if ($script:DataFolder -and (Test-Path $script:DataFolder)) { + $script:DataFolder } else { if ($PSScriptRoot) { $PSScriptRoot } else { $PWD.Path } } - if ($dlg.ShowDialog() -eq "OK") { - $txtDataDir.Text = $dlg.SelectedPath - $script:DataFolder = $dlg.SelectedPath - Save-Settings -DataFolder $dlg.SelectedPath - Refresh-ProfileList - $n = (Load-Templates).Count - $lblTplCount.Text = "$n template(s) enregistre(s) -- cliquez pour gerer" - } -}) - -$txtDataDir.Add_Leave({ - $newDir = $txtDataDir.Text.Trim() - if ([string]::IsNullOrWhiteSpace($newDir)) { return } + if ($dlg.ShowDialog() -ne "OK") { return } + $newDir = $dlg.SelectedPath if (-not (Test-Path $newDir)) { + $msg = (T "dlg.folder.not.found") -f $newDir $res = [System.Windows.Forms.MessageBox]::Show( - "Le dossier '$newDir' n'existe pas. Voulez-vous le creer ?", - "Dossier introuvable", "YesNo", "Question") + $msg, (T "dlg.folder.not.found.title"), "YesNo", "Question") if ($res -eq "Yes") { try { New-Item -ItemType Directory -Path $newDir | Out-Null } catch { [System.Windows.Forms.MessageBox]::Show( - "Impossible de creer le dossier : $($_.Exception.Message)", - "Erreur", "OK", "Error") + $_.Exception.Message, "Error", "OK", "Error") return } } else { return } } $script:DataFolder = $newDir - Save-Settings -DataFolder $newDir + Save-Settings -DataFolder $newDir -Lang $script:CurrentLang Refresh-ProfileList $n = (Load-Templates).Count - $lblTplCount.Text = "$n template(s) enregistre(s) -- cliquez pour gerer" + $lblTplCount.Text = "$n $(T 'tpl.count')" }) +# ── Language menu handlers ───────────────────────────────────────────────────── +function Switch-AppLanguage([string]$code) { + Load-Language $code + Update-UILanguage + foreach ($mi in $menuLang.DropDownItems) { + if ($mi -is [System.Windows.Forms.ToolStripMenuItem]) { + $mi.Checked = ($mi.Tag -eq $script:CurrentLang) + } + } + Save-Settings -DataFolder $script:DataFolder -Lang $script:CurrentLang + $n = (Load-Templates).Count + $lblTplCount.Text = "$n $(T 'tpl.count')" +} + +$menuLangEn.Add_Click({ Switch-AppLanguage "en" }) +foreach ($mi in @($menuLang.DropDownItems | Where-Object { $_ -is [System.Windows.Forms.ToolStripMenuItem] -and $_.Tag -ne "en" })) { + $mi.Add_Click({ Switch-AppLanguage $args[0].Tag }) +} + $btnBrowseSites.Add_Click({ $tenantUrl = $txtTenantUrl.Text.Trim() $clientId = $txtClientId.Text.Trim() @@ -3858,10 +4159,21 @@ $_settings = Load-Settings $script:DataFolder = if ($_settings.dataFolder -and (Test-Path $_settings.dataFolder)) { $_settings.dataFolder } elseif ($PSScriptRoot) { $PSScriptRoot } else { $PWD.Path } -$txtDataDir.Text = $script:DataFolder + +# Load saved language (applies T() translations and updates all registered controls) +$_savedLang = if ($_settings.lang) { $_settings.lang } else { "en" } +if ($_savedLang -ne "en") { + Load-Language $_savedLang + Update-UILanguage + foreach ($mi in $menuLang.DropDownItems) { + if ($mi -is [System.Windows.Forms.ToolStripMenuItem]) { + $mi.Checked = ($mi.Tag -eq $script:CurrentLang) + } + } +} Refresh-ProfileList $n = (Load-Templates).Count -$lblTplCount.Text = "$n template(s) enregistre(s) -- cliquez pour gerer" +$lblTplCount.Text = "$n $(T 'tpl.count')" [System.Windows.Forms.Application]::Run($form)