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

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

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

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

С помощьюILMerge (или жеILRepack), Я могу объединить сателлитные сборки в основную исполняемую сборку, но стандартные резервные механизмы .NET ResourceManager не находят специфичные для культуры ресурсы, которые были скомпилированы в основную сборку.

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

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

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

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

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

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

Я смог заставить его работать, вытащив кишки из механизмов отката поиска ресурсов Framework, реализованных в методе «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);
            }
        }
    }
}

Чтобы фактически использовать этот класс, вам необходимо заменить System.ComponentModel.ComponentResourceManager в файлах «XXX.Designer.cs», созданных Visual Studio, - и вам придется делать это каждый раз, когда вы изменяете разработанную форму - Visual Studio заменяет эту код автоматически. (Проблема обсуждалась вНастройте Windows Forms Designer для использования 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), который до сих пор работает для меня так же хорошо (замена вставки), и может свободно распространяться с исходными текстами вашего проекта.

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

Ответы на вопрос(5)

Ваш ответ на вопрос