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




собрать проект visual studio в один файл (4)

Добавлено как ответ, так как комментарии не обеспечивали достаточного пространства:

Я не мог найти ресурсы для нейтральных культур ( en вместо en-US ) с решением OPs. Поэтому я распространил InternalGetResourceSet на поиск нейтральных культур, которые сделали для меня работу. С этим вы также можете найти ресурсы, которые не определяют регион. На самом деле это то же поведение, что и нормальный ресурсореформатор, когда не выполняется ILMerging файлов ресурсов.

//Try looking for the neutral culture if the specific culture was not found
if (store == null && !culture.IsNeutralCulture)
{
    resourceFileName = GetResourceFileName(culture.Parent);

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

Это приводит к следующему коду для SingleAssemblyComponentResourceManager

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);

            //Try looking for the neutral culture if the specific culture was not found
            if (store == null && !culture.IsNeutralCulture)
            {
                resourceFileName = GetResourceFileName(culture.Parent);

                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);
            }
        }
    }
}

https://code.i-harness.com

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

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

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

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

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

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

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

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

Следуя приведенной ниже рекомендации casperOne , я наконец смог выполнить эту работу.

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

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

Для этого я получил класс «ComponentResourceManager» и переопределил только один метод (и повторно реализовал метод private framework):

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 для использования MyResourceManager », я не нашел более элегантного решения - я использую fart.exe на 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 , ILRepack Open Source (Apache 2.0), который до сих пор работает для меня (замена на замену) и может свободно распространяться с вашими источниками проекта.

Надеюсь, это поможет кому-то!


Другой подход:

1) добавьте свои ресурсы.DLL как вложенные ресурсы в свой проект.

2) добавьте обработчик событий для AppDomain.CurrentDomain.ResourceResolve. Этот обработчик срабатывает, когда ресурс не может быть найден.

  internal static System.Reflection.Assembly CurrentDomain_ResourceResolve(object sender, ResolveEventArgs args)
        {
            try
            {
                if (args.Name.StartsWith("your.resource.namespace"))
                {
                    return LoadResourcesAssyFromResource(System.Threading.Thread.CurrentThread.CurrentUICulture, "name of your the resource that contains dll");
                }
                return null;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

3) Теперь вам нужно реализовать LoadResourceAssyFromResource что-то вроде

private Assembly LoadResourceAssyFromResource( Culture culture, ResourceName resName)
        {
                    //var x = Assembly.GetExecutingAssembly().GetManifestResourceNames();

                    using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resName))
                    {
                        if (stream == null)
                        {
                            //throw new Exception("Could not find resource: " + resourceName);
                            return null;
                        }

                        Byte[] assemblyData = new Byte[stream.Length];

                        stream.Read(assemblyData, 0, assemblyData.Length);

                        var ass = Assembly.Load(assemblyData);

                        return ass;
        }

}


Просто мысль.

Вы сделали шаг и создали свой SingleAssemblyComponentResourceManager

Итак, почему вы принимаете боль, чтобы включить ваши спутниковые сборки в объединенную сборку?

Вы можете добавить ResourceName.es.resx как двоичный файл в другой ресурс вашего проекта.

Чем вы можете переписать свой код

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

//If we found the appropriate resources in the local assembly
if (store != null)
{
    rs = new ResourceSet(store);

с этим кодом (не проверено, но должно работать)

// we expect the "main" resource file to have a binary resource
// with name of the local (linked at compile time of course)
// which points to the localized resource
var content = Properties.Resources.ResourceManager.GetObject("es");
if (content != null)
{
    using (var stream = new MemoryStream(content))
    using (var reader = new ResourceReader(stream))
    {
        rs = new ResourceSet(reader);
    }
}

Это должно приложить усилия для включения устаревших сборщиков в процессе ilmerge.


У меня есть предложение по части вашей проблемы. В частности, решение для этапа обновления файлов .Designer.cs для замены ComponentResourceManager с помощью SingleAssemblyComponentResourceManager.

  1. Переместите метод InitializeComponent () из .Designer.cs и в файл реализации (включая #region). Visual Studio продолжит автоматическое создание этого раздела, без каких-либо проблем, насколько я могу судить.

  2. Используйте псевдоним C # в верхней части файла реализации, чтобы ComponentResourceManager был псевдонимом SingleAssemblyComponentResourceManager.

К сожалению, я не смог полностью проверить это. Мы нашли другое решение нашей проблемы и поэтому двинулись дальше. Надеюсь, вам это поможет.





assemblies