diff --git a/src/AutomationPeers/TableViewAutomationPeer.cs b/src/AutomationPeers/TableViewAutomationPeer.cs
index 84e46213..7bd5b0c9 100644
--- a/src/AutomationPeers/TableViewAutomationPeer.cs
+++ b/src/AutomationPeers/TableViewAutomationPeer.cs
@@ -37,7 +37,7 @@ protected override AutomationControlType GetAutomationControlTypeCore()
///
protected override string GetLocalizedControlTypeCore()
{
- return "table view";
+ return TableViewLocalizedStrings.TableViewControlType;
}
///
diff --git a/src/AutomationPeers/TableViewCellAutomationPeer.cs b/src/AutomationPeers/TableViewCellAutomationPeer.cs
index b4e2b7f6..5beae9fc 100644
--- a/src/AutomationPeers/TableViewCellAutomationPeer.cs
+++ b/src/AutomationPeers/TableViewCellAutomationPeer.cs
@@ -42,7 +42,7 @@ protected override AutomationControlType GetAutomationControlTypeCore()
///
protected override string GetLocalizedControlTypeCore()
{
- return "cell";
+ return TableViewLocalizedStrings.CellControlType;
}
///
@@ -71,7 +71,7 @@ protected override string GetNameCore()
: string.Empty;
var rowIndex = _owner.Row?.Index ?? -1;
- var rowDisplay = rowIndex >= 0 ? $"Row {rowIndex + 1}" : string.Empty;
+ var rowDisplay = rowIndex >= 0 ? TableViewLocalizedStrings.FormatRowNumber(rowIndex + 1) : string.Empty;
var cellValue = _owner.Column is TableViewTemplateColumn
? string.Empty
diff --git a/src/AutomationPeers/TableViewColumnHeaderAutomationPeer.cs b/src/AutomationPeers/TableViewColumnHeaderAutomationPeer.cs
index 4b13c231..0ec8fe6e 100644
--- a/src/AutomationPeers/TableViewColumnHeaderAutomationPeer.cs
+++ b/src/AutomationPeers/TableViewColumnHeaderAutomationPeer.cs
@@ -38,7 +38,7 @@ protected override AutomationControlType GetAutomationControlTypeCore()
///
protected override string GetLocalizedControlTypeCore()
{
- return "column header";
+ return TableViewLocalizedStrings.ColumnHeaderControlType;
}
///
diff --git a/src/AutomationPeers/TableViewRowAutomationPeer.cs b/src/AutomationPeers/TableViewRowAutomationPeer.cs
index 6a0b95f5..9662aa62 100644
--- a/src/AutomationPeers/TableViewRowAutomationPeer.cs
+++ b/src/AutomationPeers/TableViewRowAutomationPeer.cs
@@ -44,7 +44,7 @@ protected override string GetNameCore()
}
var index = _owner.Index;
- return index >= 0 ? $"Row {index + 1}" : "Row";
+ return index >= 0 ? TableViewLocalizedStrings.FormatRowNumber(index + 1) : TableViewLocalizedStrings.Row;
}
///
diff --git a/src/AutomationPeers/TableViewRowHeaderAutomationPeer.cs b/src/AutomationPeers/TableViewRowHeaderAutomationPeer.cs
index aed2b94f..6e13188c 100644
--- a/src/AutomationPeers/TableViewRowHeaderAutomationPeer.cs
+++ b/src/AutomationPeers/TableViewRowHeaderAutomationPeer.cs
@@ -35,7 +35,7 @@ protected override AutomationControlType GetAutomationControlTypeCore()
///
protected override string GetLocalizedControlTypeCore()
{
- return "row header";
+ return TableViewLocalizedStrings.RowHeaderControlType;
}
///
@@ -77,6 +77,6 @@ protected override string GetNameCore()
// Fall back to the row index
var rowIndex = _owner.TableViewRow?.Index ?? -1;
- return rowIndex >= 0 ? $"Row {rowIndex + 1}" : base.GetNameCore();
+ return rowIndex >= 0 ? TableViewLocalizedStrings.FormatRowNumber(rowIndex + 1) : base.GetNameCore();
}
}
diff --git a/src/Strings/de-DE/WinUI.TableView.resw b/src/Strings/de-DE/WinUI.TableView.resw
index d868a18a..c3a87287 100644
--- a/src/Strings/de-DE/WinUI.TableView.resw
+++ b/src/Strings/de-DE/WinUI.TableView.resw
@@ -204,4 +204,28 @@
Gruppen nach Wert sortieren
+
+ Tabellenansicht
+ UI Automation localized control type of the TableView.
+
+
+ Spaltenkopf
+ UI Automation localized control type of a column header.
+
+
+ Zeilenkopf
+ UI Automation localized control type of a row header.
+
+
+ Zelle
+ UI Automation localized control type of a cell.
+
+
+ Zeile {0}
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ Zeile
+ UI Automation name of a row whose index is not known.
+
diff --git a/src/Strings/en-US/WinUI.TableView.resw b/src/Strings/en-US/WinUI.TableView.resw
index aee6f2e4..9434ffd5 100644
--- a/src/Strings/en-US/WinUI.TableView.resw
+++ b/src/Strings/en-US/WinUI.TableView.resw
@@ -204,4 +204,28 @@
Sort Groups by Value
+
+ table view
+ UI Automation localized control type of the TableView.
+
+
+ column header
+ UI Automation localized control type of a column header.
+
+
+ row header
+ UI Automation localized control type of a row header.
+
+
+ cell
+ UI Automation localized control type of a cell.
+
+
+ Row {0}
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ Row
+ UI Automation name of a row whose index is not known.
+
\ No newline at end of file
diff --git a/src/Strings/es-ES/WinUI.TableView.resw b/src/Strings/es-ES/WinUI.TableView.resw
index 01fc0e8b..68708bf3 100644
--- a/src/Strings/es-ES/WinUI.TableView.resw
+++ b/src/Strings/es-ES/WinUI.TableView.resw
@@ -204,4 +204,28 @@
Ordenar grupos por valor
+
+ vista de tabla
+ UI Automation localized control type of the TableView.
+
+
+ encabezado de columna
+ UI Automation localized control type of a column header.
+
+
+ encabezado de fila
+ UI Automation localized control type of a row header.
+
+
+ celda
+ UI Automation localized control type of a cell.
+
+
+ Fila {0}
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ Fila
+ UI Automation name of a row whose index is not known.
+
diff --git a/src/Strings/fa-IR/WinUI.TableView.resw b/src/Strings/fa-IR/WinUI.TableView.resw
index 47a5b9d2..eeced4ce 100644
--- a/src/Strings/fa-IR/WinUI.TableView.resw
+++ b/src/Strings/fa-IR/WinUI.TableView.resw
@@ -204,4 +204,28 @@
مرتب سازی گروه ها بر اساس مقدار
+
+ نمای جدول
+ UI Automation localized control type of the TableView.
+
+
+ سرستون
+ UI Automation localized control type of a column header.
+
+
+ سرسطر
+ UI Automation localized control type of a row header.
+
+
+ خانه
+ UI Automation localized control type of a cell.
+
+
+ سطر {0}
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ سطر
+ UI Automation name of a row whose index is not known.
+
\ No newline at end of file
diff --git a/src/Strings/ja-JP/WinUI.TableView.resw b/src/Strings/ja-JP/WinUI.TableView.resw
index d3d55d16..2d73c1e1 100644
--- a/src/Strings/ja-JP/WinUI.TableView.resw
+++ b/src/Strings/ja-JP/WinUI.TableView.resw
@@ -214,4 +214,28 @@
グループを値で並べ替え
+
+ テーブル ビュー
+ UI Automation localized control type of the TableView.
+
+
+ 列ヘッダー
+ UI Automation localized control type of a column header.
+
+
+ 行ヘッダー
+ UI Automation localized control type of a row header.
+
+
+ セル
+ UI Automation localized control type of a cell.
+
+
+ 行 {0}
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ 行
+ UI Automation name of a row whose index is not known.
+
diff --git a/src/Strings/pl-PL/WinUI.TableView.resw b/src/Strings/pl-PL/WinUI.TableView.resw
index 4b5e09cd..98c899fe 100644
--- a/src/Strings/pl-PL/WinUI.TableView.resw
+++ b/src/Strings/pl-PL/WinUI.TableView.resw
@@ -204,5 +204,29 @@
Sortuj grupy według wartości
+
+ widok tabeli
+ UI Automation localized control type of the TableView.
+
+
+ nagłówek kolumny
+ UI Automation localized control type of a column header.
+
+
+ nagłówek wiersza
+ UI Automation localized control type of a row header.
+
+
+ komórka
+ UI Automation localized control type of a cell.
+
+
+ Wiersz {0}
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ Wiersz
+ UI Automation name of a row whose index is not known.
+
diff --git a/src/Strings/pt-BR/WinUI.TableView.resw b/src/Strings/pt-BR/WinUI.TableView.resw
index eb574cd7..f318d064 100644
--- a/src/Strings/pt-BR/WinUI.TableView.resw
+++ b/src/Strings/pt-BR/WinUI.TableView.resw
@@ -204,4 +204,28 @@
Ordenar grupos por valor
+
+ exibição de tabela
+ UI Automation localized control type of the TableView.
+
+
+ cabeçalho de coluna
+ UI Automation localized control type of a column header.
+
+
+ cabeçalho de linha
+ UI Automation localized control type of a row header.
+
+
+ célula
+ UI Automation localized control type of a cell.
+
+
+ Linha {0}
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ Linha
+ UI Automation name of a row whose index is not known.
+
diff --git a/src/Strings/ru-RU/WinUI.TableView.resw b/src/Strings/ru-RU/WinUI.TableView.resw
index 545c0450..55bc1056 100644
--- a/src/Strings/ru-RU/WinUI.TableView.resw
+++ b/src/Strings/ru-RU/WinUI.TableView.resw
@@ -204,4 +204,28 @@
Сортировать группы по значению
+
+ представление таблицы
+ UI Automation localized control type of the TableView.
+
+
+ заголовок столбца
+ UI Automation localized control type of a column header.
+
+
+ заголовок строки
+ UI Automation localized control type of a row header.
+
+
+ ячейка
+ UI Automation localized control type of a cell.
+
+
+ Строка {0}
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ Строка
+ UI Automation name of a row whose index is not known.
+
diff --git a/src/Strings/sk-SK/WinUI.TableView.resw b/src/Strings/sk-SK/WinUI.TableView.resw
index b4623e9b..2104ab4c 100644
--- a/src/Strings/sk-SK/WinUI.TableView.resw
+++ b/src/Strings/sk-SK/WinUI.TableView.resw
@@ -204,4 +204,28 @@
Zoradiť Skupiny Podľa Hodnoty
+
+ zobrazenie tabuľky
+ UI Automation localized control type of the TableView.
+
+
+ hlavička stĺpca
+ UI Automation localized control type of a column header.
+
+
+ hlavička riadka
+ UI Automation localized control type of a row header.
+
+
+ bunka
+ UI Automation localized control type of a cell.
+
+
+ Riadok {0}
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ Riadok
+ UI Automation name of a row whose index is not known.
+
diff --git a/src/Strings/zh-CN/WinUI.TableView.resw b/src/Strings/zh-CN/WinUI.TableView.resw
index 80cc95a9..d21158c8 100644
--- a/src/Strings/zh-CN/WinUI.TableView.resw
+++ b/src/Strings/zh-CN/WinUI.TableView.resw
@@ -204,4 +204,28 @@
按值排序分组
+
+ 表格视图
+ UI Automation localized control type of the TableView.
+
+
+ 列标题
+ UI Automation localized control type of a column header.
+
+
+ 行标题
+ UI Automation localized control type of a row header.
+
+
+ 单元格
+ UI Automation localized control type of a cell.
+
+
+ 第 {0} 行
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ 行
+ UI Automation name of a row whose index is not known.
+
diff --git a/src/Strings/zh-TW/WinUI.TableView.resw b/src/Strings/zh-TW/WinUI.TableView.resw
index 591cbf65..25269a14 100644
--- a/src/Strings/zh-TW/WinUI.TableView.resw
+++ b/src/Strings/zh-TW/WinUI.TableView.resw
@@ -204,4 +204,28 @@
依值排序群組
+
+ 表格檢視
+ UI Automation localized control type of the TableView.
+
+
+ 欄標題
+ UI Automation localized control type of a column header.
+
+
+ 列標題
+ UI Automation localized control type of a row header.
+
+
+ 儲存格
+ UI Automation localized control type of a cell.
+
+
+ 第 {0} 列
+ UI Automation name of a row; {0} is the one-based row number.
+
+
+ 列
+ UI Automation name of a row whose index is not known.
+
diff --git a/src/TableViewLocalization.cs b/src/TableViewLocalization.cs
new file mode 100644
index 00000000..0391d57e
--- /dev/null
+++ b/src/TableViewLocalization.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+
+namespace WinUI.TableView;
+
+///
+/// Lets the host application supply the TableView's user-facing strings from its own resources,
+/// in place of the ones this library ships.
+///
+///
+/// The library ships its strings in WinUI.TableView.resw for a fixed set of languages. An
+/// application that supports a language the library does not, or that wants its own wording (for
+/// example the UI Automation control types and row names a screen reader announces), sets
+/// . It is asked for a key on every read, so an application that changes
+/// language while running is followed.
+/// The keys are the resource names in WinUI.TableView.resw (see ). The
+/// resolver returns or an empty string to keep the library's own value. A
+/// resolver that throws is treated as returning : these strings are read inside
+/// UI Automation calls, which must not fail. RowNumber is a composite format with one
+/// placeholder, {0}, for the one-based row number; a value that is not a valid format falls
+/// back to the library's own.
+///
+public static class TableViewLocalization
+{
+ ///
+ /// Gets or sets the function the TableView asks for each user-facing string, by resource key.
+ /// (the default) uses the library's own resources only.
+ ///
+ public static Func? StringResolver { get; set; }
+
+ ///
+ /// Gets every key the TableView asks for.
+ ///
+ ///
+ /// Listed here by name rather than read off , so asking for the keys
+ /// does not load the library's resources (which needs a running app).
+ ///
+ public static IReadOnlyList Keys { get; } = Array.AsReadOnly(new[]
+ {
+ nameof(TableViewLocalizedStrings.BlankFilterValue),
+ nameof(TableViewLocalizedStrings.Cancel),
+ nameof(TableViewLocalizedStrings.ClearFilter),
+ nameof(TableViewLocalizedStrings.ClearSorting),
+ nameof(TableViewLocalizedStrings.Copy),
+ nameof(TableViewLocalizedStrings.CopyCommandDescription),
+ nameof(TableViewLocalizedStrings.Paste),
+ nameof(TableViewLocalizedStrings.PasteCommandDescription),
+ nameof(TableViewLocalizedStrings.CopyWithHeaders),
+ nameof(TableViewLocalizedStrings.CopyWithHeadersCommandDescription),
+ nameof(TableViewLocalizedStrings.DatePickerPlaceholder),
+ nameof(TableViewLocalizedStrings.DeselectAll),
+ nameof(TableViewLocalizedStrings.DeselectAllCommandDescription),
+ nameof(TableViewLocalizedStrings.ExportAll),
+ nameof(TableViewLocalizedStrings.ExportSelected),
+ nameof(TableViewLocalizedStrings.Ok),
+ nameof(TableViewLocalizedStrings.SearchBoxPlaceholder),
+ nameof(TableViewLocalizedStrings.SelectAll),
+ nameof(TableViewLocalizedStrings.SelectAllCommandDescription),
+ nameof(TableViewLocalizedStrings.SelectAllParenthesized),
+ nameof(TableViewLocalizedStrings.SortAscending),
+ nameof(TableViewLocalizedStrings.SortDescending),
+ nameof(TableViewLocalizedStrings.TimePickerPlaceholder),
+ nameof(TableViewLocalizedStrings.Filtered),
+ nameof(TableViewLocalizedStrings.Group),
+ nameof(TableViewLocalizedStrings.Ungroup),
+ nameof(TableViewLocalizedStrings.UngroupAll),
+ nameof(TableViewLocalizedStrings.SortGroupsByCount),
+ nameof(TableViewLocalizedStrings.SortGroupsByValue),
+ nameof(TableViewLocalizedStrings.TableViewControlType),
+ nameof(TableViewLocalizedStrings.ColumnHeaderControlType),
+ nameof(TableViewLocalizedStrings.RowHeaderControlType),
+ nameof(TableViewLocalizedStrings.CellControlType),
+ nameof(TableViewLocalizedStrings.RowNumber),
+ nameof(TableViewLocalizedStrings.Row),
+ });
+
+ ///
+ /// The host's value for , or when there is no resolver,
+ /// it supplies nothing, or it throws.
+ ///
+ internal static string? TryResolve(string key)
+ {
+ var resolver = StringResolver;
+ if (resolver is null)
+ {
+ return null;
+ }
+
+ try
+ {
+ return resolver(key);
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+}
diff --git a/src/TableViewLocalizedStrings.cs b/src/TableViewLocalizedStrings.cs
index a4925678..8098b4bf 100644
--- a/src/TableViewLocalizedStrings.cs
+++ b/src/TableViewLocalizedStrings.cs
@@ -1,5 +1,6 @@
using Microsoft.Windows.ApplicationModel.Resources;
using System;
+using System.Globalization;
namespace WinUI.TableView;
@@ -47,6 +48,13 @@ static TableViewLocalizedStrings()
UngroupAll = GetValue(nameof(UngroupAll));
SortGroupsByCount = GetValue(nameof(SortGroupsByCount));
SortGroupsByValue = GetValue(nameof(SortGroupsByValue));
+ TableViewControlType = GetValue(nameof(TableViewControlType));
+ ColumnHeaderControlType = GetValue(nameof(ColumnHeaderControlType));
+ RowHeaderControlType = GetValue(nameof(RowHeaderControlType));
+ CellControlType = GetValue(nameof(CellControlType));
+ RowNumber = GetValue(nameof(RowNumber));
+ _resourceRowNumber = GetValue(nameof(RowNumber));
+ Row = GetValue(nameof(Row));
}
private static string GetValue(string name)
@@ -72,33 +80,69 @@ private static string GetValue(string name)
#endif
}
- public static string BlankFilterValue { get; set; }
- public static string Cancel { get; set; }
- public static string ClearFilter { get; set; }
- public static string ClearSorting { get; set; }
- public static string Copy { get; set; }
- public static string CopyCommandDescription { get; set; }
- public static string Paste { get; set; }
- public static string PasteCommandDescription { get; set; }
- public static string CopyWithHeaders { get; set; }
- public static string CopyWithHeadersCommandDescription { get; set; }
- public static string DatePickerPlaceholder { get; set; }
- public static string DeselectAll { get; set; }
- public static string DeselectAllCommandDescription { get; set; }
- public static string ExportAll { get; set; }
- public static string ExportSelected { get; set; }
- public static string Ok { get; set; }
- public static string SearchBoxPlaceholder { get; set; }
- public static string SelectAll { get; set; }
- public static string SelectAllCommandDescription { get; set; }
- public static string SelectAllParenthesized { get; set; }
- public static string SortAscending { get; set; }
- public static string SortDescending { get; set; }
- public static string TimePickerPlaceholder { get; set; }
- public static string Filtered { get; set; }
- public static string Group { get; set; }
- public static string Ungroup { get; set; }
- public static string UngroupAll { get; set; }
- public static string SortGroupsByCount { get; set; }
- public static string SortGroupsByValue { get; set; }
+ public static string BlankFilterValue { get => Resolve(nameof(BlankFilterValue), field); set; }
+ public static string Cancel { get => Resolve(nameof(Cancel), field); set; }
+ public static string ClearFilter { get => Resolve(nameof(ClearFilter), field); set; }
+ public static string ClearSorting { get => Resolve(nameof(ClearSorting), field); set; }
+ public static string Copy { get => Resolve(nameof(Copy), field); set; }
+ public static string CopyCommandDescription { get => Resolve(nameof(CopyCommandDescription), field); set; }
+ public static string Paste { get => Resolve(nameof(Paste), field); set; }
+ public static string PasteCommandDescription { get => Resolve(nameof(PasteCommandDescription), field); set; }
+ public static string CopyWithHeaders { get => Resolve(nameof(CopyWithHeaders), field); set; }
+ public static string CopyWithHeadersCommandDescription { get => Resolve(nameof(CopyWithHeadersCommandDescription), field); set; }
+ public static string DatePickerPlaceholder { get => Resolve(nameof(DatePickerPlaceholder), field); set; }
+ public static string DeselectAll { get => Resolve(nameof(DeselectAll), field); set; }
+ public static string DeselectAllCommandDescription { get => Resolve(nameof(DeselectAllCommandDescription), field); set; }
+ public static string ExportAll { get => Resolve(nameof(ExportAll), field); set; }
+ public static string ExportSelected { get => Resolve(nameof(ExportSelected), field); set; }
+ public static string Ok { get => Resolve(nameof(Ok), field); set; }
+ public static string SearchBoxPlaceholder { get => Resolve(nameof(SearchBoxPlaceholder), field); set; }
+ public static string SelectAll { get => Resolve(nameof(SelectAll), field); set; }
+ public static string SelectAllCommandDescription { get => Resolve(nameof(SelectAllCommandDescription), field); set; }
+ public static string SelectAllParenthesized { get => Resolve(nameof(SelectAllParenthesized), field); set; }
+ public static string SortAscending { get => Resolve(nameof(SortAscending), field); set; }
+ public static string SortDescending { get => Resolve(nameof(SortDescending), field); set; }
+ public static string TimePickerPlaceholder { get => Resolve(nameof(TimePickerPlaceholder), field); set; }
+ public static string Filtered { get => Resolve(nameof(Filtered), field); set; }
+ public static string Group { get => Resolve(nameof(Group), field); set; }
+ public static string Ungroup { get => Resolve(nameof(Ungroup), field); set; }
+ public static string UngroupAll { get => Resolve(nameof(UngroupAll), field); set; }
+ public static string SortGroupsByCount { get => Resolve(nameof(SortGroupsByCount), field); set; }
+ public static string SortGroupsByValue { get => Resolve(nameof(SortGroupsByValue), field); set; }
+ public static string TableViewControlType { get => Resolve(nameof(TableViewControlType), field); set; }
+ public static string ColumnHeaderControlType { get => Resolve(nameof(ColumnHeaderControlType), field); set; }
+ public static string RowHeaderControlType { get => Resolve(nameof(RowHeaderControlType), field); set; }
+ public static string CellControlType { get => Resolve(nameof(CellControlType), field); set; }
+ public static string RowNumber { get => Resolve(nameof(RowNumber), field); set; }
+ public static string Row { get => Resolve(nameof(Row), field); set; }
+
+ ///
+ /// Formats the UI Automation name of a row from its one-based number, e.g. "Row 3".
+ ///
+ public static string FormatRowNumber(int oneBasedRowNumber)
+ {
+ try
+ {
+ return string.Format(CultureInfo.CurrentCulture, RowNumber, oneBasedRowNumber);
+ }
+ catch (FormatException)
+ {
+ // A host-supplied format that is not a valid composite format must not take down a UI
+ // Automation call; fall back to this library's own resource.
+ return string.Format(CultureInfo.CurrentCulture, _resourceRowNumber, oneBasedRowNumber);
+ }
+ }
+
+ private static string _resourceRowNumber = "Row {0}";
+
+ ///
+ /// The host application's string for when it supplies one through
+ /// ; otherwise ,
+ /// this library's own resource. Read on every access, so a host that changes language at run time
+ /// is followed without restarting.
+ ///
+ private static string Resolve(string key, string resourceValue)
+ {
+ return TableViewLocalization.TryResolve(key) is { Length: > 0 } hostValue ? hostValue : resourceValue;
+ }
}
diff --git a/tests/TableViewAutomationPeerTests.cs b/tests/TableViewAutomationPeerTests.cs
index 150185b0..59fc938f 100644
--- a/tests/TableViewAutomationPeerTests.cs
+++ b/tests/TableViewAutomationPeerTests.cs
@@ -1,9 +1,12 @@
+using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Automation.Provider;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Data;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
+using System.Threading.Tasks;
using WinUI.TableView.AutomationPeers;
namespace WinUI.TableView.Tests;
@@ -423,4 +426,168 @@ public void TableViewRowHeader_OnCreateAutomationPeer_ReturnsTableViewRowHeaderA
Assert.IsInstanceOfType(peer, typeof(TableViewRowHeaderAutomationPeer));
}
+
+ // ─── Localized automation text ──────────────────────────────────────────
+ // Each test swaps the resource value for a sentinel, so it fails on a peer that returns an
+ // English literal even when the test runs under en-US, where the literal and the resource agree.
+
+ [UITestMethod]
+ public void TableViewAutomationPeer_LocalizedControlType_ComesFromResources()
+ {
+ AssertUsesResource(v => TableViewLocalizedStrings.TableViewControlType = v, () => TableViewLocalizedStrings.TableViewControlType,
+ () => new TableViewAutomationPeer(new TableView()).GetLocalizedControlType());
+ }
+
+ [UITestMethod]
+ public void TableViewColumnHeaderAutomationPeer_LocalizedControlType_ComesFromResources()
+ {
+ AssertUsesResource(v => TableViewLocalizedStrings.ColumnHeaderControlType = v, () => TableViewLocalizedStrings.ColumnHeaderControlType,
+ () => new TableViewColumnHeaderAutomationPeer(new TableViewColumnHeader()).GetLocalizedControlType());
+ }
+
+ [UITestMethod]
+ public void TableViewRowHeaderAutomationPeer_LocalizedControlType_ComesFromResources()
+ {
+ AssertUsesResource(v => TableViewLocalizedStrings.RowHeaderControlType = v, () => TableViewLocalizedStrings.RowHeaderControlType,
+ () => new TableViewRowHeaderAutomationPeer(new TableViewRowHeader()).GetLocalizedControlType());
+ }
+
+ [UITestMethod]
+ public void TableViewCellAutomationPeer_LocalizedControlType_ComesFromResources()
+ {
+ AssertUsesResource(v => TableViewLocalizedStrings.CellControlType = v, () => TableViewLocalizedStrings.CellControlType,
+ () => new TableViewCellAutomationPeer(new TableViewCell()).GetLocalizedControlType());
+ }
+
+ [UITestMethod]
+ public void TableViewRowAutomationPeer_NameWithoutIndex_ComesFromResources()
+ {
+ // Row.Index returns -1 when not in a list, so the name is the bare "Row" resource.
+ AssertUsesResource(v => TableViewLocalizedStrings.Row = v, () => TableViewLocalizedStrings.Row,
+ () => new TableViewRowAutomationPeer(new TableViewRow()).GetName());
+ }
+
+ [UITestMethod]
+ public void FormatRowNumber_UsesTheRowNumberResource()
+ {
+ var original = TableViewLocalizedStrings.RowNumber;
+ try
+ {
+ TableViewLocalizedStrings.RowNumber = "R#{0}";
+ Assert.AreEqual("R#3", TableViewLocalizedStrings.FormatRowNumber(3));
+ }
+ finally
+ {
+ TableViewLocalizedStrings.RowNumber = original;
+ }
+
+ StringAssert.Contains(TableViewLocalizedStrings.FormatRowNumber(3), "3");
+ }
+
+ [UITestMethod]
+ public void AutomationResources_AreNotEmpty()
+ {
+ Assert.IsFalse(string.IsNullOrEmpty(TableViewLocalizedStrings.TableViewControlType));
+ Assert.IsFalse(string.IsNullOrEmpty(TableViewLocalizedStrings.ColumnHeaderControlType));
+ Assert.IsFalse(string.IsNullOrEmpty(TableViewLocalizedStrings.RowHeaderControlType));
+ Assert.IsFalse(string.IsNullOrEmpty(TableViewLocalizedStrings.CellControlType));
+ Assert.IsFalse(string.IsNullOrEmpty(TableViewLocalizedStrings.Row));
+ StringAssert.Contains(TableViewLocalizedStrings.RowNumber, "{0}");
+ }
+
+ // The indexed names ("Row {n}") are only composed for a row that has an index, i.e. one realized in a
+ // loaded TableView. Each test swaps RowNumber for a sentinel and reads the peer, so it fails on a peer
+ // that composes the name from an English literal instead of FormatRowNumber.
+
+ [UITestMethod]
+ public async Task TableViewRowAutomationPeer_IndexedName_ComesFromResources()
+ {
+ var row = await RealizeSecondRowAsync();
+
+ AssertUsesRowNumberResource(() => new TableViewRowAutomationPeer(row).GetName(), expected: "R#2");
+ }
+
+ [UITestMethod]
+ public async Task TableViewRowHeaderAutomationPeer_IndexedName_ComesFromResources()
+ {
+ var row = await RealizeSecondRowAsync();
+ var rowHeader = new TableViewRowHeader { TableViewRow = row };
+
+ AssertUsesRowNumberResource(() => new TableViewRowHeaderAutomationPeer(rowHeader).GetName(), expected: "R#2");
+ }
+
+ [UITestMethod]
+ public async Task TableViewCellAutomationPeer_IndexedName_ComesFromResources()
+ {
+ var row = await RealizeSecondRowAsync();
+ Assert.IsTrue(row.Cells.Count > 0, "the realized row has no cells");
+ var cell = row.Cells[0];
+
+ // The cell's name also carries the column header and the value; only its row part is under test.
+ AssertUsesRowNumberResource(() => new TableViewCellAutomationPeer(cell).GetName(), expected: "R#2", whole: false);
+ }
+
+ private static void AssertUsesRowNumberResource(Func read, string expected, bool whole = true)
+ {
+ var original = TableViewLocalizedStrings.RowNumber;
+ try
+ {
+ TableViewLocalizedStrings.RowNumber = "R#{0}";
+ var name = read();
+ if (whole)
+ {
+ Assert.AreEqual(expected, name);
+ }
+ else
+ {
+ StringAssert.Contains(name, expected);
+ }
+ }
+ finally
+ {
+ TableViewLocalizedStrings.RowNumber = original;
+ }
+ }
+
+ private static async Task RealizeSecondRowAsync()
+ {
+ var tableView = new TableView { AutoGenerateColumns = false };
+ tableView.Columns.Add(new TableViewTextColumn
+ {
+ Header = "Name",
+ Binding = new Binding { Path = new PropertyPath(nameof(IndexedNameItem.Name)) }
+ });
+ tableView.ItemsSource = new[]
+ {
+ new IndexedNameItem { Name = "A" },
+ new IndexedNameItem { Name = "B" },
+ };
+
+ await UnitTestApp.Current.MainWindow.LoadTestContentAsync(tableView);
+
+ var row = tableView.ContainerFromIndex(1) as TableViewRow;
+ Assert.IsNotNull(row, "the second row was not realized");
+ Assert.AreEqual(1, row.Index, "the realized row reports no index, so no indexed name would be composed");
+ return row;
+ }
+
+ private sealed class IndexedNameItem
+ {
+ public string Name { get; set; } = string.Empty;
+ }
+
+ private static void AssertUsesResource(Action set, Func get, Func read)
+ {
+ const string sentinel = "localized-sentinel";
+ var original = get();
+ try
+ {
+ set(sentinel);
+ Assert.AreEqual(sentinel, read());
+ }
+ finally
+ {
+ set(original);
+ }
+ }
}
diff --git a/tests/TableViewLocalizationTests.cs b/tests/TableViewLocalizationTests.cs
new file mode 100644
index 00000000..8948f0c7
--- /dev/null
+++ b/tests/TableViewLocalizationTests.cs
@@ -0,0 +1,115 @@
+using Microsoft.UI.Xaml.Automation.Peers;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
+using System;
+using System.Linq;
+using System.Reflection;
+using WinUI.TableView.AutomationPeers;
+
+namespace WinUI.TableView.Tests;
+
+///
+/// DH-1944 — : the host application supplies the
+/// TableView's strings (the automation control types and row names among them) from its own resources.
+///
+[TestClass]
+public class TableViewLocalizationTests
+{
+ [TestCleanup]
+ public void ClearResolver() => TableViewLocalization.StringResolver = null;
+
+ [UITestMethod]
+ public void Keys_AreExactlyTheLocalizedStringProperties()
+ {
+ var properties = typeof(TableViewLocalizedStrings)
+ .GetProperties(BindingFlags.Public | BindingFlags.Static)
+ .Where(p => p.PropertyType == typeof(string))
+ .Select(p => p.Name)
+ .OrderBy(n => n, StringComparer.Ordinal)
+ .ToArray();
+
+ CollectionAssert.AreEqual(properties, TableViewLocalization.Keys.OrderBy(n => n, StringComparer.Ordinal).ToArray(),
+ "every string the TableView reads must be offered to the host's resolver under its resource name");
+ }
+
+ [UITestMethod]
+ public void EveryKey_IsAskedOfTheResolver_AndItsAnswerIsUsed()
+ {
+ TableViewLocalization.StringResolver = key => "host:" + key;
+
+ foreach (var property in typeof(TableViewLocalizedStrings).GetProperties(BindingFlags.Public | BindingFlags.Static)
+ .Where(p => p.PropertyType == typeof(string)))
+ {
+ Assert.AreEqual("host:" + property.Name, property.GetValue(null), property.Name);
+ }
+ }
+
+ [UITestMethod]
+ public void A_null_or_empty_answer_keeps_the_librarys_own_value()
+ {
+ var own = TableViewLocalizedStrings.SortAscending;
+
+ TableViewLocalization.StringResolver = _ => null;
+ Assert.AreEqual(own, TableViewLocalizedStrings.SortAscending);
+
+ TableViewLocalization.StringResolver = _ => string.Empty;
+ Assert.AreEqual(own, TableViewLocalizedStrings.SortAscending);
+ }
+
+ [UITestMethod]
+ public void A_resolver_that_throws_keeps_the_librarys_own_value()
+ {
+ var own = TableViewLocalizedStrings.CellControlType;
+
+ TableViewLocalization.StringResolver = _ => throw new InvalidOperationException("host failure");
+
+ Assert.AreEqual(own, TableViewLocalizedStrings.CellControlType);
+ }
+
+ [UITestMethod]
+ public void The_resolver_is_asked_on_every_read_so_a_language_change_is_followed()
+ {
+ var language = "de";
+ TableViewLocalization.StringResolver = key => key == nameof(TableViewLocalizedStrings.Row) ? "Zeile:" + language : null;
+ Assert.AreEqual("Zeile:de", TableViewLocalizedStrings.Row);
+
+ language = "fr";
+ Assert.AreEqual("Zeile:fr", TableViewLocalizedStrings.Row);
+ }
+
+ [UITestMethod]
+ public void FormatRowNumber_uses_the_hosts_format()
+ {
+ TableViewLocalization.StringResolver = key => key == nameof(TableViewLocalizedStrings.RowNumber) ? "Ligne {0}" : null;
+
+ Assert.AreEqual("Ligne 3", TableViewLocalizedStrings.FormatRowNumber(3));
+ }
+
+ [UITestMethod]
+ public void FormatRowNumber_falls_back_when_the_hosts_format_is_malformed()
+ {
+ var own = TableViewLocalizedStrings.FormatRowNumber(3);
+ TableViewLocalization.StringResolver = key => key == nameof(TableViewLocalizedStrings.RowNumber) ? "Row {0" : null;
+
+ Assert.AreEqual(own, TableViewLocalizedStrings.FormatRowNumber(3));
+ }
+
+ [UITestMethod]
+ public void The_column_headers_localized_control_type_comes_from_the_resolver()
+ {
+ TableViewLocalization.StringResolver = key => key == nameof(TableViewLocalizedStrings.ColumnHeaderControlType) ? "Spaltenkopf" : null;
+
+ var peer = FrameworkElementAutomationPeer.CreatePeerForElement(new TableViewColumnHeader());
+
+ Assert.IsInstanceOfType(peer, typeof(TableViewColumnHeaderAutomationPeer));
+ Assert.AreEqual("Spaltenkopf", peer.GetLocalizedControlType());
+ }
+
+ [UITestMethod]
+ public void A_row_peers_name_comes_from_the_resolver()
+ {
+ TableViewLocalization.StringResolver = key => key == nameof(TableViewLocalizedStrings.Row) ? "Baris" : null;
+
+ Assert.AreEqual("Baris", new TableViewRowAutomationPeer(new TableViewRow()).GetName());
+ }
+}