вторник, 10 марта 2015 г.

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

Разрабатывая приложения очень часто встает вопрос с локализацией его. Кто-то делает только английскую версию, кто-то добавляет русский язык, а кто-то делает сразу под множество языков.
Рассказывать, что нужно сразу все строковые данные выносить в файл ресурсов, если подразумевается в дальнейшем локализация на другие языки, думаю не кому не нужно. А вот с шрифтами в XNA возникает небольшая проблема. Стандартный файл шрифта, в XNA с расширением .spritefont имеет

<CharacterRegion>
    <Start>&#32;</Start>
    <End>&#126;</End>
</CharacterRegion>

и пока используются только символы из английского языка и без вывода специальных символов все хорошо, а дальше ждет сюрприз. В целом не чего сложного нет, как я уже писал в статье «Рисование дополнительных символов XNA» делается все довольно просто. Но вот с добавлением в свое приложение нескольких языков не все так просто, а по описанному выше способу это вообще титанический труд, я решил немного упростить этот вопрос для себя.

Перевод готов, файлы ресурсов готовы.
Пихать все символы в файл шрифта не стоит, лично я гружу только необходимый файл шрифта в зависимости от текущей культуры или культуры по умолчанию.
По мимо этого, нужно отредактировать файл .spritefont для определенной культуры.
Для начала, получаем строки из файла ресурсов и добавляем символы которых нету в ресурсах, в моем случае это цифры и спец символы, также у меня в коде строки могут принимать верхний и нижний регистр.

        private IEnumerable<string> GetStringsValue(CultureInfo culture)
        {
            var resourceSet = AppResources.ResourceManager.GetResourceSet(culture, true, true);
            
            var value = new List<string>();
            foreach (DictionaryEntry entry in resourceSet)
            {
                var resource = (string) entry.Value;
                value.Add(resource);

                value.Add(resource.ToLower());

                value.Add(resource.ToUpper());
            }

            value.Add("1234567890-=_+)(*&^%$#@!:;\"'/,.<>№[]{}~`");
            
            return value;

        }

После чего, для каждой поддерживаемой культуры формируем список символов и если возможно мы их объединяем.

        public void CreateRangeCharacters()
        {
            Debug.WriteLine("Start create range characters:");
            foreach (var cultureItem in _supportedCultures)
            {
                Debug.WriteLine("Culture start: " + (string.IsNullOrEmpty(cultureItem) ?
                    "default" : cultureItem));
                var culture = new CultureInfo("en");
                if (!string.IsNullOrEmpty(cultureItem))
                {
                    culture = new CultureInfo(cultureItem);
                } 
                
                var characters = new List<char>();
                
                var resources = GetStringsValue(culture);
                foreach (var resource in resources)
                {
                    foreach (var item in resource)
                    {
                        if (!characters.Contains(item))
                        {
                            characters.Add(item);
                        }
                    }
                }

                characters = characters.OrderBy(s => s).ToList();
                var array = string.Join(" ", characters.Select(PrintChar));
                Debug.WriteLine("Characters: ");
                Debug.WriteLine(array);

                /*Объединение*/
                Debug.WriteLine("    <CharacterRegions>");

                var oldValue = characters[0];

                var value = PrintChar(oldValue);

                var isFurther = false;

                for (var i = 1; i < characters.Count; i++)
                {
                    if (characters[i] - oldValue <= 1)
                    {
                        if (!isFurther)
                        {
                            Debug.WriteLine("      <CharacterRegion>");
                            Debug.WriteLine("        <Start>&#" + PrintChar(oldValue) + ";</Start>");
                            isFurther = true;
                        }
                    }
                    else
                    {
                        if (isFurther)
                        {
                            isFurther = false;
                            Debug.WriteLine("        <End>&#" + PrintChar(oldValue) + ";</End>");
                            Debug.WriteLine("      </CharacterRegion>");
                        }
                        else
                        {
                            if (!string.IsNullOrEmpty(value))
                            {
                                Debug.WriteLine("      <CharacterRegion>");
                                Debug.WriteLine("        <Start>&#" + value + ";</Start>");
                                Debug.WriteLine("        <End>&#" + value + ";</End>");
                                Debug.WriteLine("      </CharacterRegion>");
                            }
                        }
                        value = PrintChar(characters[i]);
                    }
                    oldValue = characters[i];
                }

                if (!string.IsNullOrEmpty(value))
                {
                    Debug.WriteLine("      <CharacterRegion>");
                    Debug.WriteLine("        <Start>&#" + value + ";</Start>");
                    Debug.WriteLine("        <End>&#" + value + ";</End>");
                    Debug.WriteLine("      </CharacterRegion>");
                }

                Debug.WriteLine("    </CharacterRegions>");

                Debug.WriteLine("Culture end!");
            }
            Debug.WriteLine("End create range characters!");
        }

        private static string PrintChar(char c)
        {
            var value = (int)c;
            return value.ToString(CultureInfo.InvariantCulture);

        }

А вот проверка уже получившихся файлов шрифтов, её следует запускать если были изменения.

        public void TestAllFonts(ContentManager contentManager)
        {
            Debug.WriteLine("Start test fonts: ");
            foreach (var cultureItem in _supportedCultures)
            {
                Debug.WriteLine("Culture start: " + (string.IsNullOrEmpty(cultureItem) ? "default" : cultureItem));

                var count = 0;
                foreach (var fontItem in _fonts)
                {
                    var fontPath = "Fonts/" + fontItem;

                    var culture = new CultureInfo("en");
                    if (!string.IsNullOrEmpty(cultureItem))
                    {
                        fontPath += "." + cultureItem;
                        culture = new CultureInfo(cultureItem);
                    }

                    var font = contentManager.Load<SpriteFont>(fontPath);

                    var resources = GetStringsValue(culture);
                    foreach (var resource in resources)
                    {
                        foreach (var item in resource)
                        {
                            if (!font.Characters.Contains(item))
                            {
                                Debug.WriteLine("Error!!! " + PrintChar(item));
                            }
                        }
                        //var size = font.MeasureString(resource);

                        count++;
                    }
                }
                Debug.WriteLine("Culture end count font: " + _fonts.Count + " resource count: " + count/_fonts.Count);
            }
            Debug.WriteLine("End test fonts!");

        }

понедельник, 2 марта 2015 г.

Эффективность раскрутки приложения, на старте, через adduplex.

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

Давайте разберемся, что такое Аdduplex – это рекламная сеть, в которой Вы размещаете у себя в приложении их баннер и в зависимости от показов в соотношении 8:10, рекламируются Ваше приложение или то, которое Вы укажите в настройках. Вы можете купить там показы, а также выступать или заказывать рекламу в других приложения, сеть adduplex выступает посредником.

Плюсы – помогает размывать трафик по разным регионам, другими словами если у Вас приложение популярное в одном регионе, оно таким образом может привлекать дополнительных пользователей из других регионов. Бюджетный способ привлечения дополнительных пользователей.
Минусы – нельзя управлять таргетингом, те если у Вас есть приложение популярное в России, Вы решили расширить рынок, добавив французский язык, и хотите направить туда трафик, не тут то было. Также если Ваше приложение ориентированно только на определённые рынки, смысла использовать adduplex вообще нет. Неясная эффективность – количество кликов, это не установки.

Вот как раз вопрос эффективности я и решил выяснить. Посчитать эффективность такой сети довольно сложно, да и конкретных цифр с пояснениями я не нашел. Меня этот вопрос интересовал довольно давно, а вот возможность хотя бы прикинуть, возник недавно.

У меня имеется приложение с пиковой активностью в праздничные дни, adduplex зеркалит трафик в соотношении 8:10 на следующий день, полученный трафик отправляет на другое приложение. Приложение – игра, имеет русский и английский язык на борту.

Adduplex:

15 февраля 33971 показов 339 кликов.
24 февраля 42357 показов 357 кликов
В среднем 9000 показов и 70 кликов.
Считается, что после перехода в магазин устанавливают приложение 10-25% от кликнувших пользователей, это мы и проверим. Ожидаем дополнительных 27-68 пользователя 15 февраля и 29-72 пользователя 24 февраля.

Google analytics:

Вот тут уже возникает проблема, график новых пользователей очень скачкообразный, скачки в 300 пользователей, никак не прогнозируемые, но что интересного график пользователей в день довольно пологий, поэтому будем смотреть и новых пользователей и активных. Количество ожидаемых пользователей будем считать, как средняя величина между двумя днями - % погрешности.
Новые пользователи:
15 февраля 1049 пользователей, ожидалось 1023, прирост 26
24 февраля 1096 пользователей, ожидалось 949, прирост 147
Активные пользователи:
15 февраля 4481 пользователей, ожидалось 4324, прирост 156
24 февраля 4301 пользователей, ожидалось 4214, прирост 86

Средний прирост (без вычита % погрешности):
15 февраля 91 пользователей
24 февраля 116 пользователей

Рассчитать точные значение не возможно, да и цифре не особо большие для таких подсчетов, но можно сказать точно, что эффект есть, и он совпадает с расчетными 27-68 и 29-72 пользователями соответственно.
Можно сказать, что 50 000 показов, Вам даст в районе 100 новых пользователей, если у Вас довольно привлекательное приложение, рассчитанное на широкую аудиторию.

Сухой остаток:
1. Приложение не сможет поднимать само себя в топе на старте, просто добавлением рекламного баннера, результата вообще  не будет.
2. Это упущенная прибыль для уже зрелого приложения.
3. Для роста в топе нужно покупать в районе 50 - 100 тыс. показов ежедневно для определенной страны, меньше – практически не будет иметь эффекта.
 4. Выводить приложение через рекламу можно, но очень дорого для инди разработчика. 1000 показов - 1$. 50-100 $ в день, на протяжении 4-7 дней минимум, для одной страны и главное не ошибиться с аудиторией и игрой, тк может выйти так, что страна на которую Вы так надеялись, в силу менталитета или других обстоятельств не оценит Вашей игры.
5. Размазывая своих 100+ пользователей на 190+ стран, Вы теряете возможность роста вообще.

6. Сеть может быть переполнена, с чем я и столкнулся, заказывая N показов в день, в одной стране, на следующий день, мы узнаем, что получилось провести 5% от N заказанных показов, а ведь чем раньше мы вылезем в топе новых, тем лучше.