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

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

Проблема:

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

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

Решение:

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

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

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

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

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

13.10.2009 11:45:14
Чтобы редактировать рекс, используйте встроенный редактор ресурсов. Но это предупреждение должно быть игнорируемым, AFAICT.
psychotik 13.10.2009 20:22:23
Это было с использованием встроенного редактора, и да, это игнорируется, и все, кажется, работает нормально. Такое ощущение, что я делаю что-то, чего не должно быть, когда пытаюсь меня так устроить.
Sam Holder 14.10.2009 08:09:48
3 ОТВЕТА
РЕШЕНИЕ

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

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

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

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

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

1
13.10.2009 20:29:34
В настоящее время у меня такая ситуация, но я пытаюсь уйти от нее. Я не хочу, чтобы моя модель содержала свойство LocalizedName, так как наличие локализованного имени не имеет значения для модели. То, что у вас есть, может быть хорошей инкапсуляцией, но не разделением интересов. Представьте, что у меня есть дерево, представляющее дерево. У него есть имя, которое является латинским именем для дерева. Это не должно волновать, что при отображении английским пользователям они хотят видеть другое имя, чем латинское имя.
Sam Holder 14.10.2009 08:04:33
Я хочу сохранить инкапсуляцию (при наличии ресурса в dll компонента) и иметь SOC, имея представление для доступа к LocalisedName, запрашивая ресурсы компонента, используя имя инварианта в качестве ключа.
Sam Holder 14.10.2009 08:12:26

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

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

[Английский]
Готово = Готово

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

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

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

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

-1
13.10.2009 12:27:14
Я не уверен, что понимаю ваши проблемы. Перевод выполняется через спутниковые сборки и, в частности, не требует обновления приложения для предоставления новых переводов, только предоставление новой спутниковой сборки. Это стандартный метод перевода .net. Проблема с вашим подходом (хотя, по общему признанию, это не относится к моему сценарию) состоит в том, что a / вы должны выполнять весь перевод вручную и b / что, если текст для «Done» не помещается на кнопку, которую вы создали в какой язык? Используя стандартный подход, вы можете предоставить новые размеры кнопок, а также новый текст.
Sam Holder 13.10.2009 12:37:57
ЭКС, то, что ты предлагаешь, примитивно. .NET имеет встроенную поддержку для использования файлов resx и сателлитных сборок, которые не требуют отправки нового кода. Для обновлений / новых переводов вы просто отправляете двоичные файлы ресурсов, которые НЕ являются кодом.
psychotik 13.10.2009 20:21:13
Примитив но работает. Как я пытался почистить в своем посте, это зависит от того, как часто вы обновляете переводы. И ВОЗ делает это, если вы нанимаете кого-то, чтобы сделать это, мой метод может быть не тот, который нужно использовать. но если у вас есть переводы, управляемые сообществом, это хорошо, потому что "любой" может обновить переводы.
EKS 14.10.2009 08:18:52

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

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

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

Оттуда я создаю сам enum

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

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

0
13.10.2009 13:10:57