Единственный блок многоязычное развертывание Windows Forms (ILMerge и сборки-сателлиты / локализация) - возможный?

У меня есть простые Windows Forms (C#.NET 2.0) приложение, созданное с Visual Studio 2008.

Я хотел бы поддерживать несколько языков UI и использования свойства "Localizable" формы и определенных для культуры .resx файлов, работы аспекта локализации беспрепятственно и легко. Visual Studio автоматически компилирует определенные для культуры resx файлы в сборки-сателлиты, таким образом, в моей скомпилированной папке приложения существуют определенные для культуры подпапки, содержащие эти сборки-сателлиты.

Я хотел бы иметь приложение быть развернутым (скопированный в место) как единственный блок и все же сохранил бы способность содержать несколько наборов определенных для культуры ресурсов.

Используя ILMerge (или ILRepack), я могу объединить сборки-сателлиты в основной исполняемый блок, но стандартная.NET, механизмы нейтрализации ResourceManager не находят определенные для культуры ресурсы, которые были скомпилированы в основной блок.

Интересно, если я беру свой объединенный (исполняемый) блок и копии места его в определенные для культуры подпапки, затем все работает! Точно так же я вижу основные и определенные для культуры ресурсы в объединенном assemby, когда я использую Отражатель (или ILSpy). Но копирование основного блока в определенные для культуры подпапки побеждает цель слияния так или иначе - я действительно должен там быть просто единственной копией единственного блока...

Я задаюсь вопросом, существует ли какой-либо способ угнать или влиять на механизмы нейтрализации ResourceManager для поиска определенных для культуры ресурсов в том же блоке, а не в GAC и названных культурой подпапках. Я вижу механизм нейтрализации, описанный в следующих статьях, но никакая подсказка относительно того, как он был бы изменен: Статья Блога Команды BCL о ResourceManager.

У кого-либо есть какая-либо идея? Это, кажется, относительно частый вопрос онлайн (например, другой вопрос здесь на Переполнении стека: "ILMerge и локализованные сборки ресурсов"), но я не нашел авторитетного ответа нигде.


ОБНОВЛЕНИЕ 1: основное решение

Рекомендация следующего casperOne ниже, мне наконец удалось делать эту работу.

Я помещаю код решения здесь в вопрос, потому что casperOne предоставил единственный ответ, я не хочу добавлять свое собственное.

Я смог заставить это работать, вытащив кишки из механизмов нейтрализации нахождения ресурса Платформы, реализованных в методе "InternalGetResourceSet" и заставив наш тот-же-блок искать первый используемый механизм. Если ресурс не найден в текущем блоке, то мы называем базовый метод инициировать поисковые механизмы по умолчанию (благодаря комментарию @Wouter ниже).

Чтобы сделать это, я получил класс "ComponentResourceManager" и переопределил всего один метод (и повторно реализовал частный метод платформы):

class SingleAssemblyComponentResourceManager : 
    System.ComponentModel.ComponentResourceManager
{
    private Type _contextTypeInfo;
    private CultureInfo _neutralResourcesCulture;

    public SingleAssemblyComponentResourceManager(Type t)
        : base(t)
    {
        _contextTypeInfo = t;
    }

    protected override ResourceSet InternalGetResourceSet(CultureInfo culture, 
        bool createIfNotExists, bool tryParents)
    {
        ResourceSet rs = (ResourceSet)this.ResourceSets[culture];
        if (rs == null)
        {
            Stream store = null;
            string resourceFileName = null;

            //lazy-load default language (without caring about duplicate assignment in race conditions, no harm done);
            if (this._neutralResourcesCulture == null)
            {
                this._neutralResourcesCulture = 
                    GetNeutralResourcesLanguage(this.MainAssembly);
            }

            // if we're asking for the default language, then ask for the
            // invariant (non-specific) resources.
            if (_neutralResourcesCulture.Equals(culture))
                culture = CultureInfo.InvariantCulture;
            resourceFileName = GetResourceFileName(culture);

            store = this.MainAssembly.GetManifestResourceStream(
                this._contextTypeInfo, resourceFileName);

            //If we found the appropriate resources in the local assembly
            if (store != null)
            {
                rs = new ResourceSet(store);
                //save for later.
                AddResourceSet(this.ResourceSets, culture, ref rs);
            }
            else
            {
                rs = base.InternalGetResourceSet(culture, createIfNotExists, tryParents);
            }
        }
        return rs;
    }

    //private method in framework, had to be re-specified here.
    private static void AddResourceSet(Hashtable localResourceSets, 
        CultureInfo culture, ref ResourceSet rs)
    {
        lock (localResourceSets)
        {
            ResourceSet objA = (ResourceSet)localResourceSets[culture];
            if (objA != null)
            {
                if (!object.Equals(objA, rs))
                {
                    rs.Dispose();
                    rs = objA;
                }
            }
            else
            {
                localResourceSets.Add(culture, rs);
            }
        }
    }
}

Для фактического использования этого класса необходимо заменить Систему. ComponentModel. ComponentResourceManager в файлах "XXX.Designer.cs", созданных Visual Studio - и необходимо будет сделать этот каждый раз, когда Вы изменяете разработанную форму - замены Visual Studio тот код автоматически. (Проблема была обсуждена в, "Настраивают Разработчика Windows Forms для использования MyResourceManager", я не нашел более изящное решение - я использую fart.exe на шаге перед сборкой к автозамене.)


ОБНОВЛЕНИЕ 2: Другое Практическое Соображение - больше чем 2 языка

В то время, когда я сообщил о решении выше, я на самом деле только поддерживал два языка, и ILMerge делал, прекрасное задание слияния моей сборки-сателлита в финал объединило блок.

Недавно я начал работать над подобным проектом, где существует несколько вторичных языков и поэтому несколько сборок-сателлитов, и ILMerge делал что-то очень странное: Вместо того, чтобы объединить несколько сборок-сателлитов я запросил, это объединяло первую сборку-сателлит в многократно!

например, командная строка:

"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1InputProg.exe %1es\InputProg.resources.dll %1fr\InputProg.resources.dll

С той командной строкой я получал следующие наборы ресурсов в объединенном блоке (наблюдаемый с декомпилятором ILSpy):

InputProg.resources
InputProg.es.resources
InputProg.es.resources <-- Duplicated!

После некоторого проигрывания вокруг, я закончил тем, что понял, что это - просто ошибка в ILMerge, когда это встречается с несколькими файлами с тем же именем в единственном вызове командной строки. Решение состоит в том, чтобы просто объединить каждую сборку-сателлит в различном вызове командной строки:

"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1es\InputProg.resources.dll
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1fr\InputProg.resources.dll

Когда я делаю это, получающиеся ресурсы в окончательной сборке корректны:

InputProg.resources
InputProg.es.resources
InputProg.fr.resources

Таким образом, наконец, в случае, если это помогает разъясниться, вот полный пакетный файл постсборки:

"%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1es\InputProg.resources.dll 
IF %ERRORLEVEL% NEQ 0 GOTO END

"%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1fr\InputProg.resources.dll 
IF %ERRORLEVEL% NEQ 0 GOTO END

del %1InputProg.exe 
del %1InputProg.pdb 
del %1TempProg.exe 
del %1TempProg.pdb 
del %1es\*.* /Q 
del %1fr\*.* /Q 
:END

ОБНОВЛЕНИЕ 3: ILRepack

Другое быстрое примечание - Одна из вещей, которые беспокоили меня ILMerge, была то, что это - дополнительный собственный инструмент Microsoft, не установленный по умолчанию с Visual Studio и поэтому дополнительной зависимостью, которая делает его что немного тяжелее для третьего лица для начала работы с моими проектами с открытым исходным кодом.

Я недавно обнаружил ILRepack, открытый исходный код (Apache 2.0), эквивалентный, который до сих пор работает точно также на меня (общедоступная замена) и может быть свободно распределен с Вашими источниками проекта.


Я надеюсь, что это помогает кому-то там!

55
задан Community 23 May 2017 в 02:33
поделиться

1 ответ

Единственный способ увидеть, как это работает, это создать класс, который берет свое начало от ResourceManager и затем переопределяет методы InternalGetResourceSet и GetResourceFileName . Оттуда вы должны иметь возможность переопределить, где получены ресурсы, приведя пример CultureInfo .

.
25
ответ дан 7 November 2019 в 07:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: