Localized the rest of the strings and added a quick overlay to select locale in

the settings screen.
This commit is contained in:
Casey Langen 2017-03-15 18:48:57 -07:00
parent 5881a41e22
commit cfbd8baa3a
18 changed files with 439 additions and 120 deletions

View File

@ -99,7 +99,7 @@ void Locale::Initialize(const std::string& localePath) {
std::vector<std::string> Locale::GetLocales() {
std::vector<std::string> result;
std::copy(this->locales.begin(), this->locales.end(), result.begin());
std::copy(this->locales.begin(), this->locales.end(), std::back_inserter(result));
return result;
}
@ -125,7 +125,11 @@ bool Locale::SetSelectedLocale(const std::string& locale) {
std::string localeFn = this->localePath + "/" + locale + ".json";
this->localeData = loadLocaleData(localeFn);
return !this->localeData.is_null();
if (!this->localeData.is_null()) {
this->LocaleChanged(this->selectedLocale);
return true;
}
}
return false;

View File

@ -37,12 +37,15 @@
#include <core/config.h>
#include <core/support/Preferences.h>
#include <unordered_map>
#include <sigslot/sigslot.h>
#include <json.hpp>
namespace musik { namespace core { namespace i18n {
class Locale {
public:
sigslot::signal1<std::string> LocaleChanged;
~Locale();
static Locale& Instance() {

View File

@ -11,6 +11,7 @@ set (BOX_SRCS
./app/layout/TrackSearchLayout.cpp
./app/model/DirectoryAdapter.cpp
./app/overlay/ColorThemeOverlay.cpp
./app/overlay/LocaleOverlay.cpp
./app/overlay/PlaybackOverlays.cpp
./app/overlay/PlayQueueOverlays.cpp
./app/overlay/PluginOverlay.cpp

View File

@ -112,10 +112,10 @@ void ConsoleLayout::OnLayout() {
void ConsoleLayout::SetShortcutsWindow(ShortcutsWindow* shortcuts) {
if (shortcuts) {
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateConsole), "console");
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibrary), "library");
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateSettings), "settings");
shortcuts->AddShortcut("^D", "quit");
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateConsole), _TSTR("shortcuts_console"));
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibrary), _TSTR("shortcuts_library"));
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateSettings), _TSTR("shortcuts_settings"));
shortcuts->AddShortcut("^D", _TSTR("shortcuts_quit"));
shortcuts->SetActive(Hotkeys::Get(Hotkeys::NavigateConsole));
}
}

View File

@ -163,12 +163,12 @@ void LibraryLayout::SetShortcutsWindow(ShortcutsWindow* shortcuts) {
this->shortcuts = shortcuts;
if (this->shortcuts) {
this->shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibraryBrowse), "browse");
this->shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibraryFilter), "filter");
this->shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibraryTracks), "tracks");
this->shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibraryPlayQueue), "play queue");
this->shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateSettings), "settings");
this->shortcuts->AddShortcut("^D", "quit");
this->shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibraryBrowse), _TSTR("shortcuts_browse"));
this->shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibraryFilter), _TSTR("shortcuts_filter"));
this->shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibraryTracks), _TSTR("shortcuts_tracks"));
this->shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibraryPlayQueue), _TSTR("shortcuts_play_queue"));
this->shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateSettings), _TSTR("shortcuts_settings"));
this->shortcuts->AddShortcut("^D", _TSTR("shortcuts_quit"));
this->UpdateShortcutsWindow();
}
}

View File

@ -49,6 +49,7 @@
#include <app/util/Hotkeys.h>
#include <app/util/PreferenceKeys.h>
#include <app/overlay/ColorThemeOverlay.h>
#include <app/overlay/LocaleOverlay.h>
#include <app/overlay/PlaybackOverlays.h>
#include <app/overlay/PluginOverlay.h>
@ -86,7 +87,7 @@ using namespace std::placeholders;
using EntryPtr = IScrollAdapter::EntryPtr;
static const std::string arrow = "\xe2\x96\xba";
static const std::string arrow = "\xe2\x96\xba ";
static bool showDotfiles = false;
SettingsLayout::SettingsLayout(
@ -131,6 +132,10 @@ void SettingsLayout::OnCheckboxChanged(cursespp::Checkbox* cb, bool checked) {
#endif
}
void SettingsLayout::OnLocaleDropdownActivate(cursespp::TextLabel* label) {
LocaleOverlay::Show([this](){ this->LoadPreferences(); });
}
void SettingsLayout::OnOutputDropdownActivated(cursespp::TextLabel* label) {
std::string currentName;
std::shared_ptr<IOutput> currentPlugin = outputs::SelectedOutput();
@ -169,7 +174,7 @@ void SettingsLayout::OnPluginsDropdownActivate(cursespp::TextLabel* label) {
void SettingsLayout::OnHotkeyDropdownActivate(cursespp::TextLabel* label) {
std::shared_ptr<InputOverlay> overlay(new InputOverlay());
overlay->SetTitle("hotkey tester").SetInputMode(IInput::InputRaw);
overlay->SetTitle(_TSTR("settings_hotkey_tester")).SetInputMode(IInput::InputRaw);
App::Overlays().Push(overlay);
}
@ -181,6 +186,7 @@ void SettingsLayout::OnLayout() {
int x = this->GetX(), y = this->GetY();
int cx = this->GetWidth(), cy = this->GetHeight();
/* top row (directory setup) */
int startY = 1;
int leftX = 0;
int leftWidth = cx / 3; /* 1/3 width */
@ -196,18 +202,26 @@ void SettingsLayout::OnLayout() {
this->browseList->MoveAndResize(leftX, pathListsY, leftWidth, pathsHeight);
this->addedPathsList->MoveAndResize(rightX, pathListsY, rightWidth, pathsHeight);
/* bottom row (dropdowns, checkboxes) */
int columnCx = (cx - 5) / 2; /* 3 = left + right + middle padding */
int column1 = 1;
int column2 = columnCx + 3;
y = BOTTOM(this->browseList);
this->localeDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
this->outputDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
this->transportDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
this->pluginsDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
this->themeDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
this->hotkeyDropdown->MoveAndResize(column1, y++, columnCx, LABEL_HEIGHT);
y = BOTTOM(this->browseList);
this->outputDropdown->MoveAndResize(1, y++, cx - 1, LABEL_HEIGHT);
this->transportDropdown->MoveAndResize(1, y++, cx - 1, LABEL_HEIGHT);
this->pluginsDropdown->MoveAndResize(1, y++, cx - 1, LABEL_HEIGHT);
this->themeDropdown->MoveAndResize(1, y++, cx - 1, LABEL_HEIGHT);
#ifdef ENABLE_256_COLOR_OPTION
this->paletteCheckbox->MoveAndResize(1, y++, cx - 1, LABEL_HEIGHT);
this->paletteCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
#endif
this->hotkeyDropdown->MoveAndResize(1, y++, cx - 1, LABEL_HEIGHT);
this->dotfileCheckbox->MoveAndResize(1, y++, cx - 1, LABEL_HEIGHT);
this->syncOnStartupCheckbox->MoveAndResize(1, y++, cx - 1, LABEL_HEIGHT);
this->removeCheckbox->MoveAndResize(1, y++, cx - 1, LABEL_HEIGHT);
this->dotfileCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
this->syncOnStartupCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
this->removeCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
}
void SettingsLayout::RefreshAddedPaths() {
@ -246,10 +260,10 @@ void SettingsLayout::InitializeWindows() {
this->SetFrameVisible(false);
this->browseLabel.reset(new TextLabel());
this->browseLabel->SetText("browse (SPACE to add)", text::AlignCenter);
this->browseLabel->SetText(_TSTR("settings_space_to_add"), text::AlignCenter);
this->addedPathsLabel.reset(new TextLabel());
this->addedPathsLabel->SetText("indexed paths (BACKSPACE to remove)", text::AlignCenter);
this->addedPathsLabel->SetText(_TSTR("settings_backspace_to_remove"), text::AlignCenter);
this->addedPathsList.reset(new cursespp::ListWindow(this->addedPathsAdapter));
this->browseList.reset(new cursespp::ListWindow(this->browseAdapter));
@ -266,6 +280,9 @@ void SettingsLayout::InitializeWindows() {
this->addedPathsAdapter->SetItemDecorator(decorator);
this->browseAdapter->SetItemDecorator(decorator);
this->localeDropdown.reset(new TextLabel());
this->localeDropdown->Activated.connect(this, &SettingsLayout::OnLocaleDropdownActivate);
this->outputDropdown.reset(new TextLabel());
this->outputDropdown->Activated.connect(this, &SettingsLayout::OnOutputDropdownActivated);
@ -273,35 +290,36 @@ void SettingsLayout::InitializeWindows() {
this->transportDropdown->Activated.connect(this, &SettingsLayout::OnTransportDropdownActivate);
this->pluginsDropdown.reset(new TextLabel());
this->pluginsDropdown->SetText(arrow + " enable/disable plugins");
this->pluginsDropdown->SetText(arrow + _TSTR("settings_enable_disable_plugins"));
this->pluginsDropdown->Activated.connect(this, &SettingsLayout::OnPluginsDropdownActivate);
this->themeDropdown.reset(new TextLabel());
this->themeDropdown->SetText(arrow + " color theme: default");
this->themeDropdown->SetText(arrow + _TSTR("settings_color_theme") + _TSTR("settings_default_theme_name"));
this->themeDropdown->Activated.connect(this, &SettingsLayout::OnThemeDropdownActivate);
#ifdef ENABLE_256_COLOR_OPTION
CREATE_CHECKBOX(this->paletteCheckbox, "degrade to 256 color palette");
CREATE_CHECKBOX(this->paletteCheckbox, _TSTR("settings_degrade_256"));
#endif
this->hotkeyDropdown.reset(new TextLabel());
this->hotkeyDropdown->SetText(arrow + " hotkey tester");
this->hotkeyDropdown->SetText(arrow + _TSTR("settings_hotkey_tester"));
this->hotkeyDropdown->Activated.connect(this, &SettingsLayout::OnHotkeyDropdownActivate);
CREATE_CHECKBOX(this->dotfileCheckbox, "show dotfiles in directory browser");
CREATE_CHECKBOX(this->syncOnStartupCheckbox, "sync metadata on startup");
CREATE_CHECKBOX(this->removeCheckbox, "remove missing files from library");
CREATE_CHECKBOX(this->dotfileCheckbox, _TSTR("settings_show_dotfiles"));
CREATE_CHECKBOX(this->syncOnStartupCheckbox, _TSTR("settings_sync_on_startup"));
CREATE_CHECKBOX(this->removeCheckbox, _TSTR("settings_remove_missing"));
int order = 0;
this->browseList->SetFocusOrder(order++);
this->addedPathsList->SetFocusOrder(order++);
this->localeDropdown->SetFocusOrder(order++);
this->outputDropdown->SetFocusOrder(order++);
this->transportDropdown->SetFocusOrder(order++);
this->pluginsDropdown->SetFocusOrder(order++);
this->themeDropdown->SetFocusOrder(order++);
this->hotkeyDropdown->SetFocusOrder(order++);
#ifdef ENABLE_256_COLOR_OPTION
this->paletteCheckbox->SetFocusOrder(order++);
#endif
this->hotkeyDropdown->SetFocusOrder(order++);
this->dotfileCheckbox->SetFocusOrder(order++);
this->syncOnStartupCheckbox->SetFocusOrder(order++);
this->removeCheckbox->SetFocusOrder(order++);
@ -310,6 +328,7 @@ void SettingsLayout::InitializeWindows() {
this->AddWindow(this->addedPathsLabel);
this->AddWindow(this->browseList);
this->AddWindow(this->addedPathsList);
this->AddWindow(this->localeDropdown);
this->AddWindow(this->outputDropdown);
this->AddWindow(this->transportDropdown);
this->AddWindow(this->pluginsDropdown);
@ -325,10 +344,10 @@ void SettingsLayout::InitializeWindows() {
void SettingsLayout::SetShortcutsWindow(ShortcutsWindow* shortcuts) {
if (shortcuts) {
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateSettings), "settings");
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibrary), "library");
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateConsole), "console");
shortcuts->AddShortcut("^D", "quit");
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateSettings), _TSTR("shortcuts_settings"));
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateLibrary), _TSTR("shortcuts_library"));
shortcuts->AddShortcut(Hotkeys::Get(Hotkeys::NavigateConsole), _TSTR("shortcuts_console"));
shortcuts->AddShortcut("^D", _TSTR("shortcuts_quit"));
shortcuts->SetActive(Hotkeys::Get(Hotkeys::NavigateSettings));
}
}
@ -349,21 +368,22 @@ void SettingsLayout::CheckShowFirstRunDialog() {
if (!this->firstRunDialog) {
this->firstRunDialog.reset(new DialogOverlay());
(*this->firstRunDialog)
.SetTitle("welcome to musikbox!")
.SetMessage(boost::str(boost::format(
"add some directories that contain music files, "
"then press '%s' to show the library view and start listening!\n\n"
"for troubleshooting, press '%s' to enter the console view.\n\n"
"other keyboard shorcuts are displayed in the command bar at the "
"bottom of the screen. toggle command mode by pressing 'ESC'.\n\n"
"select 'ok' to get started.")
std::string message = _TSTR("settings_first_run_dialog_body");
try {
message = boost::str(boost::format(message)
% Hotkeys::Get(Hotkeys::NavigateLibrary)
% Hotkeys::Get(Hotkeys::NavigateConsole)))
% Hotkeys::Get(Hotkeys::NavigateConsole));
}
catch (...) {
}
(*this->firstRunDialog)
.SetTitle(_TSTR("settings_first_run_dialog_title"))
.SetMessage(message)
.AddButton(
"KEY_ENTER",
"ENTER",
"ok",
_TSTR("button_ok"),
[this](std::string key) {
this->libraryPrefs->SetBool(box::prefs::keys::FirstRunSettingsDisplayed, true);
this->firstRunDialog.reset();
@ -378,18 +398,21 @@ void SettingsLayout::LoadPreferences() {
this->syncOnStartupCheckbox->SetChecked(this->libraryPrefs->GetBool(core::prefs::keys::SyncOnStartup, true));
this->removeCheckbox->SetChecked(this->libraryPrefs->GetBool(core::prefs::keys::RemoveMissingFiles, true));
/* locale */
this->localeDropdown->SetText(arrow + _TSTR("settings_selected_locale") + i18n::Locale::Instance().GetSelectedLocale());
/* color theme */
bool disableCustomColors = this->libraryPrefs->GetBool(box::prefs::keys::DisableCustomColors);
std::string colorTheme = this->libraryPrefs->GetString(box::prefs::keys::ColorTheme);
if (colorTheme == "" && !disableCustomColors) {
colorTheme = "default";
colorTheme = _TSTR("settings_default_theme_name");
}
else if (disableCustomColors) {
colorTheme = "8 colors";
colorTheme = _TSTR("settings_8color_theme_name");
}
this->themeDropdown->SetText(arrow + " color theme: " + colorTheme);
this->themeDropdown->SetText(arrow + _TSTR("settings_color_theme") + colorTheme);
#ifdef ENABLE_256_COLOR_OPTION
this->paletteCheckbox->CheckChanged.disconnect(this);
@ -401,16 +424,16 @@ void SettingsLayout::LoadPreferences() {
/* output plugin */
std::shared_ptr<IOutput> output = outputs::SelectedOutput();
if (output) {
this->outputDropdown->SetText(arrow + " output device: " + output->Name());
this->outputDropdown->SetText(arrow + _TSTR("settings_output_device") + output->Name());
}
/* transport type */
std::string transportName =
this->transport.GetType() == MasterTransport::Gapless
? "gapless"
: "crossfade";
? _TSTR("settings_transport_type_gapless")
: _TSTR("settings_transport_type_crossfade");
this->transportDropdown->SetText(arrow + " playback mode: " + transportName);
this->transportDropdown->SetText(arrow + _TSTR("settings_transport_type") + transportName);
}
void SettingsLayout::AddSelectedDirectory() {

View File

@ -99,6 +99,7 @@ namespace musik {
void OnPluginsDropdownActivate(cursespp::TextLabel* label);
void OnHotkeyDropdownActivate(cursespp::TextLabel* label);
void OnThemeDropdownActivate(cursespp::TextLabel* label);
void OnLocaleDropdownActivate(cursespp::TextLabel* label);
int64 ListItemDecorator(
cursespp::ScrollableWindow* w,
@ -113,6 +114,7 @@ namespace musik {
std::shared_ptr<musik::core::Preferences> libraryPrefs;
std::shared_ptr<musik::core::Preferences> playbackPrefs;
std::shared_ptr<cursespp::TextLabel> localeDropdown;
std::shared_ptr<cursespp::TextLabel> outputDropdown;
std::shared_ptr<cursespp::TextLabel> transportDropdown;
std::shared_ptr<cursespp::TextLabel> pluginsDropdown;

View File

@ -62,9 +62,9 @@ static void showNeedsRestart(Callback cb = Callback()) {
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
(*dialog)
.SetTitle("musikbox")
.SetMessage("you will need to restart musikbox for this change to take effect.")
.AddButton("KEY_ENTER", "ENTER", "ok", [cb](std::string key) {
.SetTitle(_TSTR("default_overlay_title"))
.SetMessage(_TSTR("settings_needs_restart"))
.AddButton("KEY_ENTER", "ENTER", _TSTR("button_ok"), [cb](std::string key) {
if (cb) {
cb();
}
@ -93,8 +93,8 @@ void ColorThemeOverlay::Show(std::function<void()> callback) {
int selectedIndex = disableCustomColors ? 1 : 0;
std::shared_ptr<Adapter> adapter(new Adapter());
adapter->AddEntry("default");
adapter->AddEntry("8 colors (compatibilty mode)");
adapter->AddEntry(_TSTR("settings_default_theme_name"));
adapter->AddEntry(_TSTR("settings_8color_theme_name"));
std::shared_ptr<std::vector<std::string>> themes(new std::vector<std::string>());
@ -125,7 +125,7 @@ void ColorThemeOverlay::Show(std::function<void()> callback) {
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
dialog->SetAdapter(adapter)
.SetTitle("color themes")
.SetTitle(_TSTR("color_theme_list_overlay_title"))
.SetSelectedIndex(selectedIndex)
.SetWidth(36)
.SetItemSelectedCallback(
@ -184,20 +184,16 @@ void ColorThemeOverlay::Show256ColorsInfo(bool enabled, std::function<void()> ca
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
(*dialog)
.SetTitle("musikbox")
.SetMessage(
"disabling 256 color degradation will enable RGB color mode, which will replace colors in the stock "
"palette. disabling this option results in higher fidelity themes, but it may cause display "
"issues in other applications until the terminal is reset.\n\n"
"are you sure you want to disable 256 color degradation?")
.SetTitle(_TSTR("default_overlay_title"))
.SetMessage(_TSTR("color_theme_256_overlay_message"))
.AddButton(
"y", "y", "yes", [prefs, callback](std::string key) {
"y", "y", _TSTR("button_yes"), [prefs, callback](std::string key) {
prefs->SetBool(box::prefs::keys::UsePaletteColors, false);
prefs->Save();
showNeedsRestart(callback);
})
.AddButton(
"n", "n", "no", [callback](std::string key) {
"n", "n", _TSTR("button_no"), [callback](std::string key) {
if (callback) {
callback();
}

View File

@ -0,0 +1,108 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2007-2016 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "LocaleOverlay.h"
#include <core/i18n/Locale.h>
#include <cursespp/App.h>
#include <cursespp/SimpleScrollAdapter.h>
#include <cursespp/ListOverlay.h>
#include <cursespp/DialogOverlay.h>
using namespace musik;
using namespace musik::core;
using namespace musik::box;
using namespace cursespp;
using Callback = std::function<void()>;
static void showNeedsRestart(Callback cb = Callback()) {
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
(*dialog)
.SetTitle(_TSTR("default_overlay_title"))
.SetMessage(_TSTR("settings_needs_restart"))
.AddButton("KEY_ENTER", "ENTER", _TSTR("button_ok"), [cb](std::string key) {
if (cb) {
cb();
}
});
App::Overlays().Push(dialog);
}
LocaleOverlay::LocaleOverlay() {
}
static std::vector<std::string> allLocales;
void LocaleOverlay::Show(std::function<void()> callback) {
auto locale = i18n::Locale::Instance();
using Adapter = cursespp::SimpleScrollAdapter;
using ListOverlay = cursespp::ListOverlay;
std::string currentLocale = locale.GetSelectedLocale();
allLocales = locale.GetLocales();
std::shared_ptr<Adapter> adapter(new Adapter());
adapter->SetSelectable(true);
int selectedIndex = 0;
for (size_t i = 0; i < allLocales.size(); i++) {
adapter->AddEntry(allLocales[i]);
if (allLocales[i] == currentLocale) {
selectedIndex = (int) i;
}
}
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
dialog->SetAdapter(adapter)
.SetTitle(_TSTR("locale_overlay_select_title"))
.SetSelectedIndex(selectedIndex)
.SetItemSelectedCallback(
[callback, currentLocale]
(ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
if (allLocales[index] != currentLocale) {
i18n::Locale::Instance().SetSelectedLocale(allLocales[index]);
showNeedsRestart(callback);
}
});
cursespp::App::Overlays().Push(dialog);
}

View File

@ -0,0 +1,49 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2007-2016 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <functional>
namespace musik {
namespace box {
class LocaleOverlay {
public:
static void Show(std::function<void()> callback);
private:
LocaleOverlay();
};
}
}

View File

@ -63,10 +63,20 @@ using namespace cursespp;
using Adapter = cursespp::SimpleScrollAdapter;
static std::string stringWithFormat(const std::string& key, const std::string& value) {
std::string message = _TSTR(key);
try {
message = boost::str(boost::format(message) % value);
}
catch (...) {
}
return message;
}
static std::shared_ptr<Adapter> createAddToAdapter() {
std::shared_ptr<Adapter> adapter(new Adapter());
adapter->AddEntry("add to end");
adapter->AddEntry("add as next");
adapter->AddEntry(_TSTR("playqueue_overlay_add_to_end"));
adapter->AddEntry(_TSTR("playqueue_overlay_add_as_next"));
adapter->SetSelectable(true);
return adapter;
}
@ -120,13 +130,15 @@ static void confirmOverwritePlaylist(
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
(*dialog)
.SetTitle("musikbox")
.SetMessage("are you sure you want to overwrite the playlist '" + playlistName + "'?")
.AddButton("^[", "ESC", "no")
.SetTitle(_TSTR("default_overlay_title"))
.SetMessage(stringWithFormat(
"playqueue_overlay_confirm_overwrite_message",
playlistName))
.AddButton("^[", "ESC", _TSTR("button_no"))
.AddButton(
"KEY_ENTER",
"ENTER",
"yes",
_TSTR("button_yes"),
[library, playlistId, tracks](const std::string& str) {
library->Enqueue(SavePlaylistQuery::Replace(playlistId, tracks));
});
@ -140,7 +152,7 @@ static void createNewPlaylist(
{
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
dialog->SetTitle("playlist name")
dialog->SetTitle(_TSTR("playqueue_overlay_playlist_name_title"))
.SetWidth(DEFAULT_OVERLAY_WIDTH)
.SetText("")
.SetInputAcceptedCallback(
@ -160,7 +172,7 @@ static void renamePlaylist(
{
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
dialog->SetTitle("new playlist name")
dialog->SetTitle(_TSTR("playqueue_overlay_new_playlist_name_title"))
.SetWidth(DEFAULT_OVERLAY_WIDTH)
.SetText(oldName)
.SetInputAcceptedCallback(
@ -181,13 +193,15 @@ static void confirmDeletePlaylist(
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
(*dialog)
.SetTitle("musikbox")
.SetMessage("are you sure you want to delete '" + playlistName + "'?")
.AddButton("^[", "ESC", "no")
.SetTitle(_TSTR("default_overlay_title"))
.SetMessage(stringWithFormat(
"playqueue_overlay_confirm_delete_message",
playlistName))
.AddButton("^[", "ESC", _TSTR("button_no"))
.AddButton(
"KEY_ENTER",
"ENTER",
"yes",
_TSTR("button_yes"),
[library, playlistId](const std::string& str) {
library->Enqueue(std::shared_ptr<DeletePlaylistQuery>(
new DeletePlaylistQuery(playlistId)));
@ -200,9 +214,9 @@ static void showNoPlaylistsDialog() {
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
(*dialog)
.SetTitle("musikbox")
.SetMessage("you haven't saved any playlists!")
.AddButton("KEY_ENTER", "ENTER", "ok");
.SetTitle(_TSTR("default_overlay_title"))
.SetMessage(_TSTR("playqueue_overlay_load_playlists_none_message"))
.AddButton("KEY_ENTER", "ENTER", _TSTR("button_ok"));
App::Overlays().Push(dialog);
}
@ -227,7 +241,7 @@ void PlayQueueOverlays::ShowAddTrackOverlay(
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
dialog->SetAdapter(adapter)
.SetTitle("add to play queue")
.SetTitle(_TSTR("playqueue_overlay_add_to_queue_title"))
.SetSelectedIndex(0)
.SetWidth(DEFAULT_OVERLAY_WIDTH)
.SetItemSelectedCallback(
@ -261,7 +275,7 @@ void PlayQueueOverlays::ShowAddCategoryOverlay(
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
dialog->SetAdapter(adapter)
.SetTitle("add to play queue")
.SetTitle(_TSTR("playqueue_overlay_add_to_queue_title"))
.SetSelectedIndex(0)
.SetWidth(DEFAULT_OVERLAY_WIDTH)
.SetItemSelectedCallback(
@ -318,7 +332,7 @@ void PlayQueueOverlays::ShowLoadPlaylistOverlay(
addPlaylistsToAdapter(adapter, result);
showPlaylistListOverlay(
"load playlist",
_TSTR("playqueue_overlay_load_playlist_title"),
adapter,
[library, result, callback]
(ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
@ -339,7 +353,7 @@ void PlayQueueOverlays::ShowSavePlaylistOverlay(
std::shared_ptr<Adapter> adapter(new Adapter());
adapter->SetSelectable(true);
adapter->AddEntry("new...");
adapter->AddEntry(_TSTR("playqueue_overlay_new"));
addPlaylistsToAdapter(adapter, result);
/* the caller can specify a playlistId that we should try to
@ -355,7 +369,7 @@ void PlayQueueOverlays::ShowSavePlaylistOverlay(
}
showPlaylistListOverlay(
"save playlist",
_TSTR("playqueue_overlay_save_playlist_title"),
adapter,
[&playback, library, result]
(ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
@ -389,7 +403,7 @@ void PlayQueueOverlays::ShowRenamePlaylistOverlay(musik::core::ILibraryPtr libra
addPlaylistsToAdapter(adapter, result);
showPlaylistListOverlay(
"rename playlist",
_TSTR("playqueue_overlay_rename_playlist_title"),
adapter,
[library, result](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
if (index != ListWindow::NO_SELECTION) {
@ -414,7 +428,7 @@ void PlayQueueOverlays::ShowDeletePlaylistOverlay(musik::core::ILibraryPtr libra
addPlaylistsToAdapter(adapter, result);
showPlaylistListOverlay(
"delete playlist",
_TSTR("playqueue_overlay_delete_playlist_title"),
adapter,
[library, result]
(ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {

View File

@ -58,12 +58,12 @@ static void showNoOutputPluginsMessage() {
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
(*dialog)
.SetTitle("musikbox")
.SetMessage("no output plugins found!")
.SetTitle(_TSTR("default_overlay_title"))
.SetMessage(_TSTR("playback_overlay_no_output_plugins_mesage"))
.AddButton(
"KEY_ENTER",
"ENTER",
"ok");
_TSTR("button_ok"));
App::Overlays().Push(dialog);
}
@ -71,13 +71,20 @@ static void showNoOutputPluginsMessage() {
static void showOutputCannotCrossfadeMessage(const std::string& outputName) {
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
std::string message = _TSTR("playback_overlay_invalid_transport");
try {
message = boost::str(boost::format(message) % outputName);
}
catch (...) {
}
(*dialog)
.SetTitle("musikbox")
.SetMessage("the selected output driver (" + outputName + ") doesn't currently support crossfading.")
.SetTitle(_TSTR("default_overlay_title"))
.SetMessage(message)
.AddButton(
"KEY_ENTER",
"ENTER",
"ok");
_TSTR("button_ok"));
App::Overlays().Push(dialog);
}
@ -117,7 +124,7 @@ void PlaybackOverlays::ShowOutputOverlay(
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
dialog->SetAdapter(adapter)
.SetTitle("output plugins")
.SetTitle(_TSTR("playback_overlay_output_plugins_title"))
.SetSelectedIndex(selectedIndex)
.SetItemSelectedCallback(
[callback, transportType](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
@ -148,8 +155,8 @@ void PlaybackOverlays::ShowTransportOverlay(
using ListOverlay = cursespp::ListOverlay;
std::shared_ptr<Adapter> adapter(new Adapter());
adapter->AddEntry("gapless");
adapter->AddEntry("crossfade");
adapter->AddEntry(_TSTR("settings_transport_type_gapless"));
adapter->AddEntry(_TSTR("settings_transport_type_crossfade"));
adapter->SetSelectable(true);
size_t selectedIndex = (transportType == MasterTransport::Gapless) ? 0 : 1;
@ -157,7 +164,7 @@ void PlaybackOverlays::ShowTransportOverlay(
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
dialog->SetAdapter(adapter)
.SetTitle("playback transport")
.SetTitle(_TSTR("playback_overlay_transport_title"))
.SetSelectedIndex(selectedIndex)
.SetItemSelectedCallback(
[callback](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {

View File

@ -137,7 +137,7 @@ void PluginOverlay::Show() {
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
dialog->SetAdapter(pluginAdapter)
.SetTitle("plugins")
.SetTitle(_TSTR("plugin_overlay_title"))
.SetWidthPercent(80)
.SetAutoDismiss(false)
.SetItemSelectedCallback(

View File

@ -51,12 +51,12 @@ static void showNoVisualizersMessage() {
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());
(*dialog)
.SetTitle("musikbox")
.SetMessage("no visualizers found!")
.SetTitle(_TSTR("default_overlay_title"))
.SetMessage(_TSTR("visualizer_overlay_no_visualizers_message"))
.AddButton(
"KEY_ENTER",
"ENTER",
"ok");
_TSTR("button_ok"));
App::Overlays().Push(dialog);
}
@ -84,7 +84,7 @@ void VisualizerOverlay::Show() {
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
dialog->SetAdapter(adapter)
.SetTitle("visualizers")
.SetTitle(_TSTR("visualizer_overlay_title"))
.SetItemSelectedCallback(
[](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
vis::SetSelectedVisualizer(vis::GetVisualizer(index));

View File

@ -82,8 +82,6 @@ using namespace cursespp;
#define ON(w, a) if (a != CURSESPP_DEFAULT_COLOR) { wattron(w, a); }
#define OFF(w, a) if (a != CURSESPP_DEFAULT_COLOR) { wattroff(w, a); }
static std::string playingFormat = "playing $title by $artist from $album";
struct Token {
enum Type { Normal, Placeholder };
@ -133,6 +131,35 @@ void tokenize(const std::string& format, TokenList& tokens) {
}
}
/* a cache of localized, pre-formatted strings we use every second. */
static struct StringCache {
std::string PLAYING_FORMAT;
std::string STOPPED;
std::string EMPTY_SONG;
std::string EMPTY_ALBUM;
std::string EMPTY_ARTIST;
std::string SHUFFLE;
std::string MUTED;
std::string VOLUME;
std::string REPEAT_LIST;
std::string REPEAT_TRACK;
std::string REPEAT_OFF;
void Initialize() {
PLAYING_FORMAT = _TSTR("transport_playing_format");
STOPPED = _TSTR("transport_stopped");
EMPTY_SONG = _TSTR("transport_empty_song");
EMPTY_ALBUM = _TSTR("transport_empty_album");
EMPTY_ARTIST = _TSTR("transport_empty_artist");
SHUFFLE = " " + _TSTR("transport_shuffle");
MUTED = _TSTR("transport_muted") + " ";
VOLUME = _TSTR("transport_volume") + " ";
REPEAT_LIST = " " + _TSTR("transport_repeat_list");
REPEAT_TRACK = " " + _TSTR("transport_repeat_track");
REPEAT_OFF = " " + _TSTR("transport_repeat_off");
}
} Strings;
/* a really boring class that contains a cache of currently playing
information so we don't have to look it up every time we update the
view (every second) */
@ -168,15 +195,15 @@ struct musik::box::TransportDisplayCache {
if (this->track) {
title = this->track->GetValue(constants::Track::TITLE);
title = title.size() ? title : "[song]";
title = title.size() ? title : Strings.EMPTY_SONG;
titleCols = u8cols(title);
album = this->track->GetValue(constants::Track::ALBUM);
album = album.size() ? album : "[album]";
album = album.size() ? album : Strings.EMPTY_ALBUM;
albumCols = u8cols(album);
artist = this->track->GetValue(constants::Track::ARTIST);
artist = artist.size() ? artist : "[artist]";
artist = artist.size() ? artist : Strings.EMPTY_ARTIST;
artistCols = u8cols(artist);
secondsTotal = (int)transport.GetDuration();
@ -204,7 +231,7 @@ static size_t writePlayingFormat(
size_t width)
{
TokenList tokens;
tokenize(playingFormat, tokens);
tokenize(Strings.PLAYING_FORMAT, tokens);
int64 dim = COLOR_PAIR(CURSESPP_TEXT_DISABLED);
int64 gb = COLOR_PAIR(CURSESPP_TEXT_ACTIVE);
@ -282,6 +309,7 @@ TransportWindow::TransportWindow(musik::core::audio::PlaybackService& playback)
, playback(playback)
, transport(playback.GetTransport())
, focus(FocusNone) {
Strings.Initialize();
this->SetFrameVisible(false);
this->playback.TrackChanged.connect(this, &TransportWindow::OnPlaybackServiceTrackChanged);
this->playback.ModeChanged.connect(this, &TransportWindow::OnPlaybackModeChanged);
@ -442,14 +470,14 @@ void TransportWindow::Update(TimeMode timeMode) {
/* prepare the "shuffle" label */
std::string shuffleLabel = " shuffle";
std::string shuffleLabel = Strings.SHUFFLE;
size_t shuffleLabelLen = displayCache->Columns(shuffleLabel);
/* playing SONG TITLE from ALBUM NAME */
if (stopped) {
ON(c, disabled);
wprintw(c, "playback is stopped");
wprintw(c, Strings.STOPPED.c_str());
displayCache->Reset();
OFF(c, disabled);
}
@ -472,10 +500,10 @@ void TransportWindow::Update(TimeMode timeMode) {
std::string volume;
if (muted) {
volume = "muted ";
volume = Strings.MUTED;
}
else {
volume = "vol ";
volume = Strings.VOLUME;
for (int i = 0; i < 10; i++) {
volume += (i == thumbOffset) ? "" : "";
@ -490,19 +518,19 @@ void TransportWindow::Update(TimeMode timeMode) {
/* repeat mode setup */
RepeatMode mode = this->playback.GetRepeatMode();
std::string repeatModeLabel = " repeat ";
std::string repeatModeLabel;
int64 repeatAttrs = CURSESPP_DEFAULT_COLOR;
switch (mode) {
case RepeatList:
repeatModeLabel += "list";
repeatModeLabel += Strings.REPEAT_LIST;
repeatAttrs = gb;
break;
case RepeatTrack:
repeatModeLabel += "track";
repeatModeLabel += Strings.REPEAT_TRACK;
repeatAttrs = gb;
break;
default:
repeatModeLabel += "off";
repeatModeLabel += Strings.REPEAT_OFF;
repeatAttrs = disabled;
break;
}

View File

@ -1,6 +1,12 @@
{
"schemaVersion": 1,
"strings": {
"default_overlay_title": "musikbox",
"button_ok": "ok",
"button_yes": "yes",
"button_no": "no",
"browse_title_artists": "artists",
"browse_title_albums": "albums",
"browse_title_genres": "genres",
@ -8,6 +14,76 @@
"browse_title_category": "category",
"browse_title_tracks": "tracks",
"settings_space_to_add": "browse (SPACE to add)",
"settings_backspace_to_remove": "indexed paths (BACKSPACE to remove)",
"settings_enable_disable_plugins": "enable/disable plugins",
"settings_color_theme": "color theme: ",
"settings_hotkey_tester": "hotkey tester",
"settings_output_device": "output device: ",
"settings_transport_type": "playback mode: ",
"settings_degrade_256": "degrade to 256 color palette",
"settings_show_dotfiles": "show dotfiles in directory browser",
"settings_sync_on_startup": "sync metadata on startup",
"settings_remove_missing": "remove missing files from library",
"settings_default_theme_name": "default",
"settings_8color_theme_name": "8 colors (compatibility mode)",
"settings_transport_type_gapless": "gapless",
"settings_transport_type_crossfade": "crossfade",
"settings_first_run_dialog_title": "welcome to musikbox!",
"settings_first_run_dialog_body": "add some directories that contain music files, then press '%s' to show the library view and start listening!\n\nfor troubleshooting, press '%s' to enter the console view.\n\nother keyboard shorcuts are displayed in the command bar at the bottom of the screen. toggle command mode by pressing 'ESC'.\n\nselect 'ok' to get started.",
"settings_needs_restart": "you will need to restart musikbox for this change to take effect.",
"settings_selected_locale": "locale: ",
"locale_overlay_select_title": "select locale",
"color_theme_list_overlay_title": "color themes",
"color_theme_256_overlay_message": "disabling 256 color degradation will enable RGB color mode, which will replace colors in the stock palette. disabling this option results in higher fidelity themes, but it may cause display issues in other applications until the terminal is reset.\n\nare you sure you want to disable 256 color degradation?",
"playback_overlay_transport_title": "playback mode",
"playback_overlay_output_plugins_title": "output plugins",
"playback_overlay_invalid_transport": "the selected output device (%s) doesn't support crossfading.",
"playback_overlay_no_output_plugins_mesage": "no output plugins found!",
"playqueue_overlay_add_to_end": "add to end",
"playqueue_overlay_add_as_next": "add as next",
"playqueue_overlay_new": "new...",
"playqueue_overlay_confirm_overwrite_message": "are you sure you want to overwrite the playlist '%s'?",
"playqueue_overlay_add_to_queue_title": "add to play queue",
"playqueue_overlay_load_playlist_title": "load playlist",
"playqueue_overlay_save_playlist_title": "save playlist",
"playqueue_overlay_rename_playlist_title": "rename playlist",
"playqueue_overlay_delete_playlist_title": "delete playlist",
"playqueue_overlay_playlist_name_title": "playlist name",
"playqueue_overlay_new_playlist_name_title": "new playlist name",
"playqueue_overlay_confirm_delete_message": "are you sure you want to delete '%s'?",
"playqueue_overlay_load_playlists_none_message": "you haven't saved any playlists yet!",
"visualizer_overlay_title": "visualizers",
"visualizer_overlay_no_visualizers_message": "no visualizers found!",
"plugin_overlay_title": "plugins",
"shortcuts_settings": "settings",
"shortcuts_library": "library",
"shortcuts_console": "console",
"shortcuts_quit": "quit",
"shortcuts_browse": "browse",
"shortcuts_filter": "filter",
"shortcuts_tracks": "tracks",
"shortcuts_play_queue": "play queue",
"transport_playing_format": "playing $title by $artist from $album",
"transport_stopped": "playback is stopped",
"transport_empty_song": "[song]",
"transport_empty_album": "[album]",
"transport_empty_artist": "[artist]",
"transport_shuffle": "shuffle",
"transport_muted": "muted",
"transport_volume": "vol",
"transport_repeat_list": "repeat list",
"transport_repeat_track": "repeat track",
"transport_repeat_off": "repeat off",
"main_syncing_banner": "syncing metadata (%d tracks processed)"
}
}

View File

@ -139,6 +139,7 @@ xcopy "$(ProjectDir)data\locales\*" "$(TargetDir)locales\" /Y /e</Command>
<ClCompile Include="app\layout\TrackSearchLayout.cpp" />
<ClCompile Include="app\model\DirectoryAdapter.cpp" />
<ClCompile Include="app\overlay\ColorThemeOverlay.cpp" />
<ClCompile Include="app\overlay\LocaleOverlay.cpp" />
<ClCompile Include="app\overlay\PlaybackOverlays.cpp" />
<ClCompile Include="app\overlay\PlayQueueOverlays.cpp" />
<ClCompile Include="app\overlay\PluginOverlay.cpp" />
@ -191,6 +192,7 @@ xcopy "$(ProjectDir)data\locales\*" "$(TargetDir)locales\" /Y /e</Command>
<ClInclude Include="app\layout\TrackSearchLayout.h" />
<ClInclude Include="app\model\DirectoryAdapter.h" />
<ClInclude Include="app\overlay\ColorThemeOverlay.h" />
<ClInclude Include="app\overlay\LocaleOverlay.h" />
<ClInclude Include="app\overlay\PlaybackOverlays.h" />
<ClInclude Include="app\overlay\PlayQueueOverlays.h" />
<ClInclude Include="app\overlay\PluginOverlay.h" />

View File

@ -135,6 +135,9 @@
<ClCompile Include="app\overlay\ColorThemeOverlay.cpp">
<Filter>app\overlay</Filter>
</ClCompile>
<ClCompile Include="app\overlay\LocaleOverlay.cpp">
<Filter>app\overlay</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h" />
@ -316,6 +319,9 @@
<ClInclude Include="app\overlay\ColorThemeOverlay.h">
<Filter>app\overlay</Filter>
</ClInclude>
<ClInclude Include="app\overlay\LocaleOverlay.h">
<Filter>app\overlay</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="cursespp">