Портирование и локализация приложения на Windows Phone 8

    В предыдущей статье я рассказал как разработать судоку для windows 8.1, в этой расскажу как портировать приложение на Windows Phone 8 и локализовать его на несколько языков.

    image

    Для начала создаем проект шаблона Windows Phone

    Windows Phone Application Template

    В этом решении я скопировал необходимые элементы управления из проекта Windows 8.1 в проект Windows Phone. Это решение не является лучшим, но оно самое быстрое. В следующих статьях я покажу как делать кроссплатформенные решения.
    Сейчас же вернемся к портированию приложения. В игре на телефоне решено было сделать постраничную навигацию. Создано 4 страницы:

    страницы судоку

    Игра начинается со страницы GamePage. Если нет сохраненной игры — необходимо отправить пользователя на экран создания новой игры:

    Исходный код
    protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
    
                GameState game = SettingsProvider.LoadSavedGame();
                if (game == null)
                {
                    NavigationService.Navigate(new Uri("/Pages/NewGame.xaml", UriKind.Relative));
                }
                else
                {
                   //...
                }
            }
    


    Работа с файлами и сессиям в Windows 8.1


    В шаблоне Windows 8.1 автоматически будет создан класс SuspensionManager, который реализует механизм сохранения сессий. Тогда в классе страницы необходимо определить метод NavigationHelperLoadState и NavigationHelperSaveState. Код NavigationHelperLoadState показан ниже, сохранения состояния происходит похожим образом.

    Исходный код
     private async void NavigationHelperLoadState(object sender, LoadStateEventArgs e)
            {
                try
                {
                    if (SuspensionManager.SessionState.ContainsKey("game-data"))
                    {
                        string previousGame = SuspensionManager.SessionState["game-data"] as string;
                        if (!string.IsNullOrEmpty(previousGame))
                        {
                            GameStateModel game = GameStateModel.FromJson(previousGame);
                            if (game != null)
                            {
                                LoadGameToBoard(game.ToGameState());
                            }
                        }
                    }
                }
                catch (FileNotFoundException fileNotFound)
                {
                }
                catch (Exception)
                {
                }
            }
    


    Работа с файлами и сессиям в Windows Phone 8


    На телефоне работа с файлами немного отличается. Есть такое понятия, как IsolatedStorageSettings. Понятие это пришло из Silverlight версии 2. Как понятно из названия этот тип предназначен для хранения файлов и данных изолированно на локальной файловой системе. Это значит, что другое приложение, помимо вашего не получит доступ к этим данным.

    Код загрузки данных об игре
     public static GameState LoadSavedGame()
            {
                try
                {
                    IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
                    if (settings.Contains(SavedGame))
                    {
                        string previousGame = settings[SavedGame] as string;
                        if (!string.IsNullOrEmpty(previousGame))
                        {
                            GameStateModel game = GameStateModel.FromJson(previousGame);
                            if (game != null)
                            {
                                return game.ToGameState();
                            }
                        }
                    }
    
                }
                catch (Exception exception)
                {
    
                }
    
                return null;
            }
    


    Найденные особенности


    Windows Phone 8 не позволяет сохранять шрифты в ресурсах xaml. Такой код на платформе WP8 не валиден:

    <!-- Fonts -->
        <FontFamily x:Key="ThemeFontFamily">Segoe UI Light</FontFamily>
        <FontWeight x:Key="ThemeFontWeight">SemiLight</FontWeight>
    


    Поэтому пришлось разделить файлы ресурсов и очистить лишнее из WP8. 

    Работа с разметкой


    Очень порадовала работа с разметкой. Во-первых нет необходимости делать верстку приложения для 3-х видов как в Win 8.1 (Full, Filled, Snapped). Важно правильно сделать верстку для экрана в целом. В судоку поддерживается только вертикальное положение экрана (горизонтальное поддерживаться на 99% не будет). 
    После адаптации верстки, стилей и размеров объектов необходимо протестировать на различных устройствах и экранах.

    Windows Phone 8

    Локализация названия приложения


    В отличии от Windows 8.1, описание приложений производится на сайте https://dev.windowsphone.com при настройки публикации. А название хранится в приложении. Но не все так просто :)

    Есть такое понятие как Display Name и Tile Title. Первое будет использоваться в маркете и списке приложений на телефоне. Второе используется как надпись на тайле, если такая опция выбрана.

    Как описано в MSDN How to localize an app title for Windows Phone (ссылки в конце) — нам нужно создать C++ DLL, в которой будет файл ресурсов, содержащий 2 поля: название и название для тайла. Для каждого языка нужно создать свою dll. Да, это грустно. Но на помощь приходит проект WP8 Localize. Скачиваем инструмент, заполняем поля и автоматически создаем dll для всех необходимых языков. Пару часов этим мы сэкономили точно.

    После создания всех dll, их необходимо добавить в проект. Мне удобнее, чтобы они все были в отдельной папке. Я назвал ее Langs и поместил их все туда. Не забываем изменить BuildAction=Content.

    Локализация названия приложения Windows Phone 8

    В файле WPAppManifest.xml изменяем название и тайл на @Langs/AppResLib.dll,-100 и @Langs/AppResLib.dll,-200 соответственно.

    Изменение названия приложения

    На этом этап локализации названия приложения и тайла закончен. Приступаем к изменению языка для контента приложения.

    Локализация Xaml


    В Windows Phone 8 инструменты для локализации стали гораздо лучше чем в 7-й версии. Создаем или находим класс LocalizedStrings.

    Исходный код
    /// <summary>
        /// Provides access to string resources.
        /// </summary>
        public class LocalizedStrings
        {
            private static AppResources _localizedResources = new AppResources();
    
            public AppResources LocalizedResources { get { return _localizedResources; } }
        }
    


    Создаем папку Resources в проекте. Для каждого поддерживаемого языка создаем файл ресурсов AppResources.LOCALE.resx (например AppResources.resx и AppResources.ru.resx). Второй шаг — создание ресурсов в файле App.xaml:

    <Application.Resources>
            <winPhone8:LocalizedStrings xmlns:local="clr-namespace:Oxozle.Sudoku.WinPhone8" x:Key="LocalizedStrings"/>
        </Application.Resources>
    


    Тем самым даем возможность биндинга в xaml.

    WP localization

    Сам файл выглядит следующим образом:

    файл локализации

    Важное отличие, что здесь это конкретно файл текстовых ресурсов, а не объектов. Т.к. происходит связывание (биндинг) и генерация кода ресурсов — разделитель точка использоваться не может. Далее необходимо настроить связку для текстовых элементов к ресурсам.

    <TextBlock Text="{Binding LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}" Style="{StaticResource PhoneTextNormalStyle}"/>
    


    Для страницы Новая игра:

    Создание локализованной разметки

    Локализация поддерживается и в режиме верстки (в Expression Blend все правильно подхватывается)

    Локализация из кода


    ApplicationBar не поддерживает локализацию на биндингах. Для этого при создании проекта создается закомментированный код метода BuildLocalizedApplicationBar. Но нет ничего сложного написать несколько строк для построения меню приложения.

    BuildLocalizedApplicationBar
    private void BuildLocalizedApplicationBar()
            {
                // Set the page's ApplicationBar to a new instance of ApplicationBar.
                ApplicationBar = new ApplicationBar();
    
                //// Create a new button and set the text value to the localized string from AppResources.
                //ApplicationBarIconButton appBarButton = new ApplicationBarIconButton(new Uri("/Assets/AppBar/edit.png", UriKind.Relative));
                //appBarButton.Text = AppResources.GamePage_Pencil;
                //ApplicationBar.Buttons.Add(appBarButton);
    
                // Create a new menu item with the localized string from AppResources.
                ApplicationBarMenuItem appBarNewGame = new ApplicationBarMenuItem(AppResources.NewGameTitle);
                appBarNewGame.Click += delegate
                {
                    NavigationService.Navigate(new Uri("/Pages/NewGame.xaml", UriKind.Relative));
                };
                ApplicationBar.MenuItems.Add(appBarNewGame);
    
    
                ApplicationBarMenuItem appBarMenuItem = new ApplicationBarMenuItem(AppResources.GamePage_ButtonAbout);
                appBarMenuItem.Click += delegate
                {
                    NavigationService.Navigate(new Uri("/Pages/AboutPage.xaml", UriKind.Relative));
                };
                ApplicationBar.MenuItems.Add(appBarMenuItem);
    
    
                ApplicationBarMenuItem appBarRate = new ApplicationBarMenuItem(AppResources.WinGame_Rate);
                appBarRate.Click += delegate
                {
                    MarketplaceReviewTask marketplaceReviewTask = new MarketplaceReviewTask();
                    marketplaceReviewTask.Show();
                };
                ApplicationBar.MenuItems.Add(appBarRate);
    
            }
    


    Включение локализации



    В отличии от Windows 8.1 локализацию нужно включить вручную. Для этого в app.xaml.cs в метод App добавляем вызов метода InitializeLanguage.

    InitializeLanguage
    // Language display initialization
    InitializeLanguage();
    //Сам метод (может быть уже создан студией).
    private void InitializeLanguage()
            {
                try
                {
                    // Set the font to match the display language defined by the
                    // ResourceLanguage resource string for each supported language.
                    //
                    // Fall back to the font of the neutral language if the Display
                    // language of the phone is not supported.
                    //
                    // If a compiler error is hit then ResourceLanguage is missing from
                    // the resource file.
                    RootFrame.Language = XmlLanguage.GetLanguage(AppResources.ResourceLanguage);
    
                    // Set the FlowDirection of all elements under the root frame based
                    // on the ResourceFlowDirection resource string for each
                    // supported language.
                    //
                    // If a compiler error is hit then ResourceFlowDirection is missing from
                    // the resource file.
                    FlowDirection flow = (FlowDirection)Enum.Parse(typeof(FlowDirection), AppResources.ResourceFlowDirection);
                    RootFrame.FlowDirection = flow;
                }
                catch
                {
                    // If an exception is caught here it is most likely due to either
                    // ResourceLangauge not being correctly set to a supported language
                    // code or ResourceFlowDirection is set to a value other than LeftToRight
                    // or RightToLeft.
    
                    if (Debugger.IsAttached)
                    {
                        Debugger.Break();
                    }
    
                    throw;
                }
            }
    



    В AssemblyInfo указываем культуру по умолчанию:

    [assembly: NeutralResourcesLanguageAttribute("en-US")]
    


    В файле WPAppManifest.xml на вкладке Packaging указываем настройки приложения: список поддерживаемых языков, язык по умолчанию.

    выбор языков

    Выводы


    Локализация приложения Windows Phone 8 в некоторых местах кардинально отличается от той же локализации в Windows 8.1. Однако ничего сложного в этом нет. Важно иметь полную инструкцию перед созданием локализации или добавления поддержки нового языка. Надеюсь эта статья поможет при создании мультиязычного приложения. Посмотреть на результат можно здесь:

    Судоку+

    Источники


    Share post

    Comments 15

      +1
      В Windows Phone 8 инструменты для локализации стали гораздо лучше чем в 7-й версии. Создаем или находим класс LocalizedStrings.
      но ведь они абсолютно такие же, разве что LocalizedStrings/AppResources и т.д. теперь по умолчанию добавлены в шаблоны, а список языков не надо редактировать в .csproj
        +2
        Все «просто и логично» — для локализации имени создаем нативные dll для каждого языка, содержащие каждая одну строку кода и новое имя…
        Конечно, WP 8 Localize помогает, да и самому их создать не проблема, но очень уж хочется кому-то внедрить что-то в сжатые сроки (или сраки) за такую реализацию…

        P.S. Файлы ресурсов от W8 отлично читаются в WP8, вполне можно их перенести и сделать собственный заменитель W8 класса ResourceLoader, который будет выглядеть как-то так:
            public class ResourceLoader
            {
                public string GetString(string str)
                {
                    return AppResources.ResourceManager.GetString(str, AppResources.Culture);
                }
            }

        В результате имеем тот же синтаксис, что и в коде W8 приложения.
          +3
          Это да… Вообще весь SDK попахивает недоделками. Очень многих стандартных классов из .NET Framework просто нет, порой это добивает…

          Позиция Microsoft по поводу MVVM вообще радует, сами толкают его, а при этом UI компоненты достаточно скудно его поддерживают, элементарно тап обработать через ICommand — надо написать свой UI элемент, очень часто приходится события из UI прокидывать во ViewModel используя страницу как посредник, очень запарывает… + никаких тебе базовых компонентов для MVVM, я свои делал..., MVVMLight не прижился

          В ListBox нет футеров/хедеров, сделать список с несколькими типами элементов очень муторно… В общем, когда встает задача поработать с более менее сложным UI в windows phone, я сразу начинаю думать о костылях, которые придется придумать в этот раз.

          Для навигации на другие страницы из ViewModel пришлось написать свой NavigationHelper, чтобы не прокидывать во ViewModel инстанс NavigationService.

          В стек навигации приложения вообще не влезть, сколько страниц там лежит не узнать, только если считать их самому, что собственно и пришлось сделать.

          То что работа с сетью через HttpWebRequest намерено оставлена только асинхронная — это просто финиш… Я конечно понимаю зачем, чтобы всякие криворукие разработчики не дергали сеть из UI потока, но блин, когда у тебя архитектурно запросы к api это несколько слоев логики, как правило асинхронность добавляют в самом верхнем слоее, чтобы не тащить за собой коллбеки по всем слоям, но тут тебя блин заставляют это делать…

          Радости такая разработка особо не приносит, учитывая что Visual Studio без решарпера умна как пробка, ничего почти не подсказывает (блин, не IDE, а редактор с хорошим дебаггером), особенно когда эти злополучные биндинги строк пишешь, хоть вешайся блин, вот пример:

          это даже не до конца влезло блин, после андроидового "@string/bla_bla" печально смотреть
            +1
            Я извиняюсь, наболело просто, а тут вроде wp разработчики собрались, можно и поплакаться…

            Забыл сказать про самый ад — это Windows Phone Marketplace:
            • Нормально работает только в IE
            • На самом деле глючит даже в самом IE
            • Даже бета билд публикуется в течение 2х часов, очень блин круто, когда тестировщик ждет из за этого
            • Публичные билды публикуются в течение 5ти дней! У них блин и так на платформе приложений не хватает, но нет, надо еще и паблишинг усложнить, достать разработчиков не только скудным SDK, тупой IDE, но еще и стором своим. Ты блин стараешься им помочь, но палки в колеса вставляют как могут ребята
            • Как то раз мы хотели разрешить публикацию только в нескольких странах, а там список почти 200 элементов, есть кнопка выбрать все, а снять все нет! Ручками, ага… я уж думал скрипт написать


            Если тут есть представители Microsoft, связанные с windows phone, откликнитесь пожалуйста, нам есть о чем поговорить
              +1
              Согласен с каждым вашим словом. Прилепили наспех silverlight, теперь не разгрести. Надеялся, что хоть в WP8 будет хоть на йоту лучше, увы.
              Вон, в том же WPF с локализацией все на порядок лучше. Сделали бы хотя бы StaticExtension, xaml получился бы чуть красивее.
                +1
                Windows Store работает только в IE, это вообще бред. У меня Marketplace работает в хроме. Согласен, публикация в магазин для телефонов происходит стабильно 5 дней, а вот в Windows Store у меня за день проверяли.
                Как то раз мы хотели разрешить публикацию только в нескольких странах, а там список почти 200 элементов, есть кнопка выбрать все, а снять все нет! Ручками, ага… я уж думал скрипт написать

                По привычке нажал галочку, пришлось вручную все снимать. А разработчикам магазина за это большой минус. Плюсом до сих пор нет локализации магазина WP Store.
                  +1
                  Windows Store работает только в IE, это вообще бред. У меня Marketplace работает в хроме. Согласен, публикация в магазин для телефонов происходит стабильно 5 дней, а вот в Windows Store у меня за день проверяли.


                  У меня в Google Chrome не завелось на двух машинах с windows 8.1, плагин сильверлайта не установился, а разбираться с этим просто уже не было желания. В самом IE Marketplace бывает зависает на этапе создания обновления, приходится перезапускать вкладку

                  Я не отрицаю, что у вас все ок, прекрасно, но у меня проблемы с Marketplace есть и я о них написал. Возможно, я погорячился с фразой «работает только в IE»
                    +1
                    Нееет) у меня только Windows Phone Store работает не в IE. Windows Store только в IE =( и это боль. А при выборе скриншотов в IE плагин SL всегда сбрасывает предыдущую директорию, это тоже боль.
                      +1
                      Со скриншотами вы вообще больную тему подняли… Я бы руки оторвал тому, кто делал их загрузку и управление в Marketplace.

                      Сбрасывает последнюю выбранную директорию при загрузке, просто поменять порядок скриншотов нельзя, надо удалить и загрузить заново, загрузка одного скриншота блочит весь ui… Это реально боль. Не знаю как оно в AppStore, но Google Play по сравнению с Marketplace просто небо и земля.

                      + Информация о крешах в Marketplace ооочень удобно сделана, прямо офигеть, отдаётся вроде xls файл, который Open Office не понимает, пришлось в Google Drive закинуть, а там ни стектрейса, ни полезной инфы, только имя класса (или что-такое в котором был эксепшн, пишу с телефона, могу ошибаться), причём в моём случае это был какой-то класс из .NET Framework. То есть эта информация вообще никак не помогает найти и устранить проблему. Похоже, придётся в Application класе ловить необработанные исключения и отправлять себе на сервер.
                        0
                        Вроде яндекс метрика для приложений и google предоставляют сервис для сбора информации об ошибках.
                          0
                          Да, почти любая аналитика это предоставляет, просто не каждый заказчик согласится делиться такой информацией со сторонними компаниями :)
                +3
                В целом, соглашусь с каждым словом. Но таки есть и один плюс: VS с решарпером, пусть и становится неповоротливым монстром, но все же очень хороша. Мне после MonoTouch, XCode и Eclipse она была как глоток свежего воздуха. А остальное… Ну в конце-концов к этому привыкаешь, лишь бы был смысл = доходы. А вот с этим плохо, зарабатывать нормально можно только на прямых продажах, но не каждое приложение может ими похвастаться. Реклама AdMob, Smaato, Millenial Media — 0% Fill Rate для фул скрина, реклама LeadBolt и Inneractive не считает клики, MS PubCenter за месяц показов по миру дал мне такую статистику:

                Может и есть где-то провайдер, который готов монетизировать мои десятки тысяч запусков в день, но что-то я его не нашел :)
              +3
              В целом, статья копирует документацию, но спасибо, что поделились своим опытом :)

              Могу добавить приемчик для смены языка в рантайме: ссылка на гист, если что не понятно, спрашивайте, поясню.

              Суть такая: Класс LocalizedStrings должен реализовать INotifyPropertyChanged, тогда все стандартные биндинги будут подписаны на изменения его свойств, нам остается только корректно обработать переключение культуры UI потока, перезагрузить ресурсы с нужной локалью и уведомить об этом слушателей + сохранить выбранный язык, при его отсутсвии загружать наиболее подходящий из доступных, либо дефолтный.

              В общем, вся прелесть в том, что разметку, в которой используется биндинг ресурсов менять не нужно! Все будет работать автоматически :)

              Применил в приложении PayStore, можете зайти в настройки и увидеть переключалку языка, при выборе языка он мгновенно применится по всему приложению без перезапусков и всего такого
                +1
                Планирую создавать мультиязычное приложение на WP8, так что огромное спасибо за статью.
                  0
                  Посмотрите, также, локализацию для W 8.0 (+8.1), вдруг понадобится. Да и, как говорят выше, можно использовать одну систему.

                Only users with full accounts can post comments. Log in, please.