Это хорошее решение для локализации подключаемых компонентов?

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

Проблема:

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

Я чувствую, что компонентная dll должна отвечать за предоставление локализованного имени (это похоже на хорошую инкапсуляцию), но приложение, потребляющее компонент, должно нести ответственность за получение / использование локализованного имени (тот факт, что компонент имеет другое имя для цели отображения - это проблема не компонента, а `` представления '', использующего этот компонент)

Решение:

Добавьте ресурс в dll компонентов с тем же именем, что и у файла, в котором находится класс компонента. Добавьте в ресурс строку с ключом, который является именем компонента.

В приложении получите локализованное имя вот так:

ExternalObject obj = GetExternalObject ();            
ResourceManager manager = new ResourceManager (obj.GetType ());
string localisedName= manager.GetString (obj.Name);

Этот код, вероятно, будет инкапсулирован в класс Localiser, но он передает суть. Кажется, это работает, но это хорошая идея или есть лучший / более стандартный способ сделать это?

РЕДАКТИРОВАТЬ: Я должен указать, что одна вещь, в которой я не уверен в этом решении, заключается в том, что ресурсы должны находиться в файле .resx с тем же именем, что и файл, в котором находится класс. Это заставляет его работать, поскольку файл ресурсов можно определить по названию типа. Это то же самое, что и локализация форм, кажется, работает, и заставляет Visual Studio помещать .resx в качестве «подкомпонента» файла .cs, что все кажется приятным. Но затем Visual Studio выдает предупреждение (о редактировании ресурса, который является частью другого элемента проекта), если я пытаюсь отредактировать этот файл, что заставляет меня думать, что, возможно, есть другой способ, которым я должен это делать.


person Sam Holder    schedule 13.10.2009    source источник
comment
Для редактирования рекса используйте встроенный редактор ресурсов. Но это предупреждение должно быть проигнорировано, AFAICT.   -  person psychotik    schedule 14.10.2009
comment
Это было с использованием встроенного редактора, и да, это игнорируется, и, похоже, все работает нормально. Просто мне кажется, что я делаю то, чего не должна делать, когда он пытается меня так ухаживать.   -  person Sam Holder    schedule 14.10.2009


Ответы (3)


Думаю, у вас есть правильная идея, но есть способ добиться этого лучше.

Предположительно, у вас есть интерфейс, который реализует подключаемый компонент. Скажем, IPluggable:

interface IPluggable {
    ...
    string LocalizedName {get;}
    ...
}

Из вашего основного двоичного файла загрузите подключаемую сборку и создайте экземпляр IPluggable с помощью отражения (я предполагаю, что это то, что делает ваш метод GetExternalObject()), а затем получите доступ к локализованному имени с помощью свойства LocalizedName. Внутри реализации IPluggable создайте ResourceManager и получите доступ к LocalizedName из resx этой подключаемой сборки.

Таким образом вы получаете хорошую инкапсуляцию поведения в подключаемой сборке - она ​​отвечает за предоставление вам локализованного имени, однако она решает это сделать, при этом ваша программа man не предполагает, что ResourceManager может быть создан для доступа к локализованному имени.

person psychotik    schedule 13.10.2009
comment
Это то, что я имею в настоящее время, но это то, от чего я пытаюсь уйти. Я не хочу, чтобы моя модель содержала свойство LocalizedName, поскольку наличие локализованного имени не является проблемой для модели. У вас может быть хорошая инкапсуляция, но не разделение проблем. Представьте, что у меня есть ITree, представляющее дерево. У него есть имя, которое является латинским названием дерева. Его не должно волновать, что при отображении для англоязычных пользователей они хотят видеть другое имя, чем латинское. - person Sam Holder; 14.10.2009
comment
Я хочу сохранить инкапсуляцию (имея ресурс в dll компонента) и иметь SOC, имея доступ к LocalisedName путем запроса ресурсов компонента, используя инвариантное имя в качестве ключа. - person Sam Holder; 14.10.2009

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

Начал с создания собственного атрибута локализации

/// <SUMMARY>
/// Attribute used for localization. Description field should contain a reference to the Resource file for correct localization
/// </SUMMARY>
public class LocalizationAttribute : Attribute
{
    public LocalizationAttribute(string description)
    {
        this._description = description;
    }

    private string _description;
    /// <SUMMARY>
    /// Used to reference a resource key
    /// </SUMMARY>
    public string Description
    {
        get
        {
            return this._description;
        }
    }
}

Оттуда я создаю само перечисление

[TypeConverter(typeof(EnumToLocalizedString))]
public enum ReviewReason
{
    [LocalizationAttribute("ReviewReasonNewDocument")]
    NewDocument = 1,


    [LocalizationAttribute("ReviewReasonInternalAudit")]
    InternalAudit = 2,


    [LocalizationAttribute("ReviewReasonExternalAudit")]
    ExternalAudit = 3,


    [LocalizationAttribute("ReviewReasonChangedWorkBehaviour")]
    ChangedWorkBehaviour = 4,


    [LocalizationAttribute("ReviewReasonChangedWorkBehaviourBecauseOfComplaints")]
    ChangedWorkBehaviourBecauseOfComplaints = 5,


    [LocalizationAttribute("ReviewReasonMovedFromOlderSystem")]
    MovedFromOlderSystem = 6,


    [LocalizationAttribute("ReviewReasonPeriodicUpdate")]
    PeriodicUpdate = 7,


    [LocalizationAttribute("ReviewReasonDocumentChanged")]
    DocumentChanged = 8
}

Затем я создал преобразователь типов, который будет получать ключ описания LocalizationAttribute и обращаться к файлу ресурсов, чтобы получить локализацию (описание атрибута должно соответствовать ключу ресурса :))

public class EnumToLocalizedString : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return (sourceType.Equals(typeof(Enum)));
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return (destinationType.Equals(typeof(String)));
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (!destinationType.Equals(typeof(String)))
            {
                throw new ArgumentException("Can only convert to string.", "destinationType");
            }
            if (!value.GetType().BaseType.Equals(typeof(Enum)))
            {
                throw new ArgumentException("Can only convert an instance of enum.", "value");
            }

            string name = value.ToString();
            object[] attrs = value.GetType().GetField(name).GetCustomAttributes(typeof(LocalizationAttribute), false);
            if (attrs.Length != 1  !(attrs[0] is LocalizationAttribute))
            {
                throw new ArgumentException("Invalid enum argument");
            }
            return Handbok.Code.Resources.handbok.ResourceManager.GetString(((LocalizationAttribute)attrs[0]).Description);
        }
    }

Наконец, я создал клиента, который использует TypeConverter, который в данном случае представляет собой коллекцию

public class ReviewReasonCollection
{
    private static Collection<KEYVALUEPAIR<REVIEWREASON,>> _reviewReasons;

    public static Collection<KEYVALUEPAIR<REVIEWREASON,>> AllReviewReasons
    {
        get
        {
            if (_reviewReasons == null)
            {
                _reviewReasons = new Collection<KEYVALUEPAIR<REVIEWREASON,>>();
                TypeConverter t = TypeDescriptor.GetConverter(typeof(ReviewReason));

                foreach (ReviewReason reviewReason in Enum.GetValues(typeof(ReviewReason)))
                {
                    _reviewReasons.Add(new KeyValuePair<REVIEWREASON,>(reviewReason, t.ConvertToString(reviewReason)));
                }
            }
            return _reviewReasons;
        }
    }
}

Первоначально я разместил это решение в моем блоге. Надеюсь, это поможет вам :)

person armannvg    schedule 13.10.2009

Проблема с предложенным вами способом заключается в том, что будет сложно обновить переводы, и для этого может даже потребоваться программист. И как вы собираетесь обновлять переводы, не обновляя все приложения?

Я сделал много переведенных приложений, и все, что я сделал, - это отдельный текстовый файл с переводами, оформленными примерно так:

[English]
Готово = Готово

[Норвежский]
Готово = Фердиг

И у меня есть функция TranslateForm (), которую я вызываю внутри события Form Show, которая переводит все элементы пользовательского интерфейса. Функция TranslateForm () будет иметь такие вещи, как

buttonDone.Text = Translate.GetTranslation("Done");

Последняя часть с TranslateForm не является оптимальным решением, я думаю, что со временем я перейду к решению, в котором сам элемент управления вызывает класс Translate. Преимущество использования этой системы заключается в том, что она проста для программиста, вы можете иметь другие переводы ppl без необходимости выполнять ручную работу впоследствии (это важно для меня, так как у меня есть переводы, поддерживаемые сообществом), поэтому они часто обновляются, и я не хочу тратить на это время. Я также могу обновлять переводы во время работы приложения без перезапуска или обновления приложения.

person EKS    schedule 13.10.2009
comment
Я не уверен, что понимаю ваши опасения. Перевод выполняется через вспомогательные сборки и, в частности, не требует обновления приложения для предоставления новых переводов, а только предоставление новой вспомогательной сборки. Это стандартный метод перевода .net. Проблема с вашим подходом (хотя, по общему признанию, это не относится к моему сценарию) заключается в том, что а / вы должны делать весь перевод вручную и б / что, если текст для «Готово» не подходит для кнопки, которую вы создали в какой-то язык? Используя стандартный подход, вы можете предоставить новые размеры кнопок, а также новый текст. - person Sam Holder; 13.10.2009
comment
EKS, вы предлагаете примитивно. .NET имеет встроенную поддержку для использования файлов resx и вспомогательных сборок, которые не требуют поставки нового кода. Для обновлений / новых переводов вы просто отправляете двоичные файлы ресурсов, а это НЕ код. - person psychotik; 14.10.2009
comment
Примитивно, но работает. Как я и пытался очистить в своем посте, это зависит от того, как часто вы обновляете переводы. И КТО их делает, если вы наймете кого-нибудь для этого, мой метод может оказаться не подходящим. но если у вас есть переводы, созданные сообществом, это хорошо, потому что каждый может обновлять переводы. - person EKS; 14.10.2009