-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added locale and localized string explorer windows; updated localized…
… string search window
- Loading branch information
Danae Dekker
committed
Apr 3, 2024
1 parent
080b188
commit 00f60eb
Showing
10 changed files
with
756 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
using Audune.Utils.UnityEditor.Editor; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using UnityEditor; | ||
using UnityEditor.IMGUI.Controls; | ||
using UnityEngine; | ||
|
||
namespace Audune.Localization.Editor | ||
{ | ||
// Class that defines a tree view for the locale explorer window | ||
public class LocaleExplorerTreeView : ItemsTreeView<string> | ||
{ | ||
// Default strings | ||
private const string _missingDisplayName = "Strings with missing values"; | ||
private const string _stringsDisplayName = "Strings"; | ||
|
||
|
||
// Default options for the tree view | ||
private static readonly Options _options = new Options { | ||
displayNameSelector = key => key.Split('.')[^1], | ||
iconSelector = key => EditorIcons.text, | ||
groupIconSelector = (path, expanded) => { | ||
if (path.Length > 1) | ||
return expanded ? EditorIcons.folderOpened : EditorIcons.folder; | ||
else if (path[0] == _missingDisplayName) | ||
return EditorIcons.errorMark; | ||
else if (path[0] == _stringsDisplayName) | ||
return EditorIcons.font; | ||
else | ||
return null; | ||
}, | ||
}; | ||
|
||
|
||
// The locales used in the tree view | ||
private List<Locale> _locales; | ||
private Dictionary<string, List<string>> _values; | ||
|
||
|
||
// Constructor | ||
public LocaleExplorerTreeView(IEnumerable<Locale> locales) : base(LocalesToKeys(locales), _options) | ||
{ | ||
_locales = new List<Locale>(locales ?? Enumerable.Empty<Locale>()); | ||
_values = LocalesToKeys(_locales).ToDictionary(key => key, key => _locales.Select(locale => locale.strings.Find(key)).Where(value => value != null).ToList()); | ||
|
||
multiColumnHeader = new MultiColumnHeader(new MultiColumnHeaderState(Enumerable | ||
.Repeat(new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Key"), width = 275, canSort = false, allowToggleVisibility = false }, 1) | ||
.Concat(LocalesToColumms(locales)) | ||
.ToArray())); | ||
} | ||
|
||
|
||
// Build the items of the tree view | ||
protected override void Build(ref TreeViewItem rootItem, ref int id) | ||
{ | ||
// Create root items for different types of items | ||
var missingRootItem = CreateGroupItem(id++, new[] { _missingDisplayName }); | ||
var stringsRootItem = CreateGroupItem(id++, new[] { _stringsDisplayName }); | ||
|
||
// Iterate over the items and create data items for them | ||
var stringsPathItems = new Dictionary<string, GroupItem>(); | ||
foreach (var item in items) | ||
{ | ||
// Create the data item | ||
var dataItem = CreateDataItem(id++, item); | ||
var hasMissingValues = _locales.ContainsMissingString(item); | ||
if (hasMissingValues) | ||
dataItem.icon = EditorIcons.errorMark; | ||
|
||
// Create a separate data item if the localized string of the item contains a missing value | ||
if (hasMissingValues) | ||
missingRootItem.AddChild(CreateDataItem(id++, item, displayName: item, icon: EditorIcons.errorMark)); | ||
|
||
// Create the path items for the string item | ||
var path = item.Split('.'); | ||
var pathItem = stringsRootItem; | ||
for (int i = 0; i < path.Length - 1; i++) | ||
{ | ||
var joinedPath = path[..(i + 1)]; | ||
var joinedPathString = string.Join("/", path[..(i + 1)]); | ||
if (!stringsPathItems.TryGetValue(joinedPathString, out var newPathItem)) | ||
{ | ||
newPathItem = CreateGroupItem(id++, new[] { _stringsDisplayName }.Concat(joinedPath).ToArray(), path[i]); | ||
pathItem.AddChild(newPathItem); | ||
stringsPathItems.Add(joinedPathString, newPathItem); | ||
} | ||
pathItem = newPathItem; | ||
} | ||
|
||
// Add the data item to the correct path item | ||
pathItem.AddChild(dataItem); | ||
} | ||
|
||
// Add root items | ||
if (missingRootItem.hasChildren) | ||
rootItem.AddChild(missingRootItem); | ||
rootItem.AddChild(stringsRootItem); | ||
} | ||
|
||
// Draw a cell that represents a data item in the tree view | ||
protected override void OnDataCellGUI(DataItem item, int columnIndex, Rect columnRect) | ||
{ | ||
if (columnIndex == 0) | ||
{ | ||
// Key column | ||
if (!isSearching) | ||
columnRect = columnRect.ContractLeft(GetContentIndent(item)); | ||
|
||
var hasMissingValues = !string.IsNullOrEmpty(item.data) && _locales.ContainsMissingString(item.data); | ||
EditorGUI.LabelField(columnRect, HighlightSearchString(new GUIContent(isSearching ? item.data : item.displayName, hasMissingValues ? EditorIcons.errorMark : item.icon)), label); | ||
} | ||
else if (!string.IsNullOrEmpty(item.data)) | ||
{ | ||
// Locale column | ||
EditorGUI.LabelField(columnRect, HighlightSearchString(_locales[columnIndex - 1].strings.TryFind(item.data, out var value) ? value.Replace("\n", " ") : "<color=red><Undefined></color>"), label); | ||
} | ||
} | ||
|
||
// Draw a cell that represents a group item in the tree view | ||
protected override void OnGroupCellGUI(GroupItem item, int columnIndex, Rect columnRect) | ||
{ | ||
if (columnIndex == 0) | ||
{ | ||
// Key column | ||
if (!isSearching) | ||
{ | ||
columnRect = columnRect.ContractLeft(GetContentIndent(item)); | ||
EditorGUI.LabelField(columnRect, item, boldLabel); | ||
} | ||
} | ||
} | ||
|
||
// Handler for when an item is double clicked | ||
protected override void OnDoubleClicked(DataItem item) | ||
{ | ||
LocalizedStringExplorerWindow.ShowWindow(searchString: item.data); | ||
} | ||
|
||
// Return a context menu for a data item | ||
protected override GenericMenu GetDataItemContextMenu(DataItem item) | ||
{ | ||
var menu = new GenericMenu(); | ||
|
||
menu.AddItem(new GUIContent("Find References"), false, () => LocalizedStringExplorerWindow.ShowWindow(searchString: item.data)); | ||
|
||
menu.AddSeparator(""); | ||
|
||
menu.AddItem(new GUIContent("Expand All"), false, () => ExpandAll()); | ||
menu.AddItem(new GUIContent("Collapse All"), false, () => CollapseAll()); | ||
|
||
return menu; | ||
} | ||
|
||
// Return if an item matches the specified search query | ||
protected override bool Matches(string data, string search) | ||
{ | ||
if (string.IsNullOrEmpty(data)) | ||
return false; | ||
if (string.IsNullOrEmpty(search)) | ||
return true; | ||
|
||
return data.Contains(search, StringComparison.InvariantCultureIgnoreCase) | ||
|| (_values.TryGetValue(data, out var values) && values.Any(value => value.Contains(search, StringComparison.InvariantCultureIgnoreCase))); | ||
} | ||
|
||
|
||
#region Convert locales to keys and columns | ||
// Convert a list of locales to keys | ||
private static IEnumerable<string> LocalesToKeys(IEnumerable<Locale> locales) | ||
{ | ||
return locales?.SelectMany(locale => locale.strings.Keys).Distinct() ?? Enumerable.Empty<string>(); | ||
} | ||
|
||
// Convert a list of locales to tree view columns | ||
private static IEnumerable<MultiColumnHeaderState.Column> LocalesToColumms(IEnumerable<Locale> locales) | ||
{ | ||
return locales?.Select(locale => new MultiColumnHeaderState.Column() { headerContent = new GUIContent($"{locale.englishName} Value"), width = 150, canSort = false }) ?? Enumerable.Empty<MultiColumnHeaderState.Column>(); | ||
} | ||
#endregion | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
using Audune.Utils.UnityEditor.Editor; | ||
using UnityEditor; | ||
using UnityEngine; | ||
|
||
namespace Audune.Localization.Editor | ||
{ | ||
// Class that defines an editor window for exploring locales in the project | ||
[EditorWindowTitle(title = "Locale Explorer")] | ||
public sealed class LocaleExplorerWindow : EditorWindow | ||
{ | ||
// Show the window | ||
public static void ShowWindow(string searchString = null, string selected = null) | ||
{ | ||
var window = GetWindow<LocaleExplorerWindow>(); | ||
window.minSize = new Vector2(800, 600); | ||
window.Refresh(searchString, selected); | ||
} | ||
|
||
// Show the window without context | ||
[MenuItem("Window/Audune Localization/Locale Explorer _%#&L", secondaryPriority = 0)] | ||
public static void ShowWindow() | ||
{ | ||
ShowWindow(null, null); | ||
} | ||
|
||
|
||
// Tree view for displaying the localized strings | ||
private LocaleExplorerTreeView _treeView; | ||
|
||
|
||
// Refresh the window | ||
private void Refresh(string searchString = null, string selected = null, bool forceRebuild = false) | ||
{ | ||
Rebuild(forceRebuild); | ||
_treeView.Reload(); | ||
|
||
if (!string.IsNullOrEmpty(searchString)) | ||
_treeView.searchString = searchString; | ||
else if (selected != null) | ||
_treeView.SetSelectionData(selected); | ||
} | ||
|
||
// Rebuild the tree view | ||
private void Rebuild(bool forceRebuild = false) | ||
{ | ||
if (forceRebuild || _treeView == null) | ||
_treeView = new LocaleExplorerTreeView(Locale.GetAllLocaleAssets()); | ||
} | ||
|
||
|
||
// OnGUI is called when the editor is drawn | ||
private void OnGUI() | ||
{ | ||
Rebuild(); | ||
|
||
// Draw the search box | ||
GUILayout.BeginHorizontal(EditorStyles.toolbar); | ||
OnToolbarGUI(); | ||
GUILayout.EndHorizontal(); | ||
|
||
// Draw the tree view | ||
OnTreeViewGUI(); | ||
} | ||
|
||
|
||
// OnToolbarGUI is called when the toolbar is drawn | ||
private void OnToolbarGUI() | ||
{ | ||
// Rescan project button | ||
if (GUILayout.Button(new GUIContent("Rescan Project", EditorIcons.refresh, "Rescan the project for locales"), EditorStyles.toolbarButton, GUILayout.Width(111))) | ||
Refresh(searchString: _treeView.searchString, selected: _treeView.GetSelectionData(), forceRebuild: true); | ||
|
||
// Locales dropdown | ||
if (GUILayout.Button(new GUIContent("Locales", EditorIcons.folderOpened, "Edit one of the locales in the project"), EditorStyles.toolbarDropDown, GUILayout.Width(80))) | ||
{ | ||
var menu = new GenericMenu(); | ||
foreach (var locale in Locale.GetAllLocaleAssets()) | ||
{ | ||
menu.AddItem(new GUIContent($"{locale.englishName}/Show Asset"), false, () => { | ||
Selection.SetActiveObjectWithContext(locale, locale); | ||
EditorGUIUtility.PingObject(locale); | ||
}); | ||
menu.AddItem(new GUIContent($"{locale.englishName}/Copy Path"), false, () => GUIUtility.systemCopyBuffer = AssetDatabase.GetAssetPath(locale)); | ||
menu.AddSeparator($"{locale.englishName}/"); | ||
menu.AddItem(new GUIContent($"{locale.englishName}/Edit Source"), false, () => AssetDatabase.OpenAsset(locale)); | ||
} | ||
|
||
var rect = GUILayoutUtility.GetLastRect(); | ||
rect.x += 111; | ||
rect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; | ||
menu.DropDown(rect); | ||
} | ||
|
||
GUILayout.FlexibleSpace(); | ||
|
||
// Search bar | ||
_treeView.searchString = EditorGUILayout.TextField(_treeView.searchString, EditorStyles.toolbarSearchField, GUILayout.MaxWidth(300)); | ||
|
||
// Tree view buttons | ||
if (GUILayout.Button(new GUIContent("Expand All", "Expand all tree view items"), EditorStyles.toolbarButton, GUILayout.ExpandWidth(false))) | ||
_treeView.ExpandAll(); | ||
if (GUILayout.Button(new GUIContent("Collapse All", "Collapse all tree view items"), EditorStyles.toolbarButton, GUILayout.ExpandWidth(false))) | ||
_treeView.CollapseAll(); | ||
} | ||
|
||
// OnTreeViewGUI is called when the tree view is drawn | ||
private void OnTreeViewGUI() | ||
{ | ||
var rect = EditorGUILayout.GetControlRect(GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); | ||
_treeView.Reload(); | ||
_treeView.OnGUI(rect); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.