J'ai une simple application Windows Forms (C#, .NET 2.0), construite avec Visual Studio 2008.
J'aimerais prendre en charge plusieurs langues d'interface utilisateur et, grâce à la propriété "Localizable" du formulaire et aux fichiers .resx spécifiques à une culture, l'aspect localisation fonctionne facilement et de manière transparente. Visual Studio compile automatiquement les fichiers resx spécifiques à la culture dans des assemblages satellites. Ainsi, dans le dossier de mon application compilée, il existe des sous-dossiers spécifiques à la culture contenant ces assemblages satellites.
Je voudrais que l'application soit déployée (copiée en place) en tant que assemblée unique tout en conservant la possibilité de contenir plusieurs ensembles de ressources spécifiques à une culture.
Utilisation de ILMerge (ou ILRepack ), je peux fusionner les assemblages satellites dans l'assemblage exécutable principal, mais les mécanismes de repli standard du ResourceManager .NET ne trouvent pas les ressources spécifiques à la culture qui ont été compilées dans l'assemblage principal.
Il est intéressant de noter que si je prends mon assemblage fusionné (exécutable) et que je place des copies de celui-ci dans les sous-dossiers spécifiques à la culture, tout fonctionne ! De même, je peux voir les ressources principales et celles spécifiques à la culture dans l'assemblage fusionné lorsque j'utilise la commande Réflecteur (ou ILSpy ). Mais copier l'assemblage principal dans des sous-dossiers spécifiques à chaque culture va à l'encontre de l'objectif de la fusion - j'ai vraiment besoin d'une seule copie de l'assemblage unique...
Je me demande s'il existe un moyen de détourner ou d'influencer les mécanismes de repli du ResourceManager pour qu'ils recherchent les ressources spécifiques à une culture dans le même assemblage plutôt que dans le GAC et dans des sous-dossiers portant un nom de culture. . Je vois le mécanisme de repli décrit dans les articles suivants, mais je n'ai aucune idée de la manière dont il pourrait être modifié : Article du blog de l'équipe BCL sur ResourceManager .
Quelqu'un a-t-il une idée ? Cela semble être une question relativement fréquente en ligne (par exemple, une autre question ici sur Stack Overflow : " ILMerge et assemblages de ressources localisées "), mais je n'ai trouvé aucune réponse faisant autorité nulle part.
UPDATE 1 : Solution de base
Suivant La recommandation de casperOne ci-dessous j'ai finalement réussi à le faire fonctionner.
Je mets le code de la solution ici dans la question parce que casperOne a fourni la seule réponse, je ne veux pas ajouter la mienne.
J'ai réussi à le faire fonctionner en retirant l'essentiel des mécanismes de repli de la recherche de ressources du Framework mis en œuvre dans la méthode "InternalGetResourceSet" et en faisant en sorte que la recherche dans le même assemblage soit effectuée par la méthode "InternalGetResourceSet". premièrement utilisé. Si la ressource n'est pas trouvée dans l'assemblage actuel, alors nous appelons la méthode base pour lancer les mécanismes de recherche par défaut (merci au commentaire de @Wouter ci-dessous).
Pour ce faire, j'ai dérivé la classe "ComponentResourceManager", et surrodé une seule méthode (et réimplémenté une méthode privée du 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);
}
}
}
}
Pour utiliser réellement cette classe, vous devez remplacer le System.ComponentModel.ComponentResourceManager dans les fichiers "XXX.Designer.cs" créés par Visual Studio - et vous devrez le faire chaque fois que vous modifiez le formulaire conçu - Visual Studio remplace ce code automatiquement. (Ce problème a été abordé dans " Personnaliser Windows Forms Designer pour utiliser MyResourceManager ", je n'ai pas trouvé de solution plus élégante - j'utilise fart.exe dans une étape de pré-construction pour remplacer automatiquement).
MISE À JOUR 2 : une autre considération pratique - plus de 2 langues
Au moment où j'ai signalé la solution ci-dessus, je ne supportais en fait que deux langues, et ILMerge faisait un bon travail de fusion de mon assemblage satellite dans l'assemblage fusionné final.
Récemment, j'ai commencé à travailler sur un projet similaire où il y a multiple des langues secondaires, et donc des assemblages satellites multiples, et ILMerge faisait quelque chose de très étrange : Au lieu de fusionner les multiples assemblages satellites que j'avais demandés, il fusionnait plusieurs fois le premier assemblage satellite !
eg ligne de commande :
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1InputProg.exe %1es\InputProg.resources.dll %1fr\InputProg.resources.dll
Avec cette ligne de commande, j'ai obtenu les ensembles de ressources suivants dans l'assemblage fusionné (observé avec le décompilateur ILSpy) :
InputProg.resources
InputProg.es.resources
InputProg.es.resources <-- Duplicated!
Après avoir joué un peu, j'ai fini par réaliser que c'est juste un bug dans ILMerge quand il rencontre plusieurs fichiers avec le même nom en un seul appel de ligne de commande. La solution consiste simplement à fusionner chaque assemblage de satellite dans un appel de ligne de commande différent :
"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
Lorsque je fais cela, les ressources résultantes dans l'assemblage final sont correctes :
InputProg.resources
InputProg.es.resources
InputProg.fr.resources
Enfin, au cas où cela aiderait à clarifier les choses, voici un fichier batch post-construction complet :
"%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
UPDATE 3 : ILRepack
Une autre note rapide - Une des choses qui m'a dérangé avec ILMerge est qu'il s'agit d'un outil propriétaire supplémentaire de Microsoft, qui n'est pas installé par défaut avec Visual Studio, et donc une dépendance supplémentaire qui rend un peu plus difficile pour un tiers de démarrer avec mes projets open-source.
J'ai récemment découvert ILRepack un équivalent open-source (Apache 2.0) qui, jusqu'à présent, fonctionne aussi bien pour moi (remplacement immédiat), et peut être distribué gratuitement avec les sources de votre projet.
J'espère que cela aidera quelqu'un d'autre !
1 votes
Après quelques recherches supplémentaires (relativement obsessionnelles), j'ai trouvé quelques extraits prometteurs : neowin.net/forum/lofiversion/index.php/t625641.html social.msdn.microsoft.com/Forums/fr/vsx/thread/ Il semble que la création d'un ResourceManager personnalisé soit la voie à suivre - je vais décompiler le ResourceManager par défaut (Reflector à la rescousse !) pour voir si je peux mieux comprendre ce qu'il fait / comment il fonctionne.
0 votes
Bonjour. J'ai besoin de faire exactement la même chose. J'ai ajouté cette nouvelle classe et l'ai remplacée dans un fichier de conception, mais je reçois une NullReferenceException à la ligne : this.outputPath.Properties.AutoHeight = ((bool)(resources.GetObject("outputPath.Properties.AutoHeight")) ; (resources est un SingleAssemblyComponentResourceManager) outputPath - est un contrôle DevExpress. Même après avoir fusionné tous les assemblages DevExpress.
0 votes
Savez-vous ce qui est réellement Null ? Est-ce l'objet "ressources" qui n'est pas réellement initialisé ? Je ne suis pas sûr que ce soit le meilleur forum pour vous aider à travailler sur votre code, mais n'hésitez pas à m'envoyer des détails à stuff at klerks dot biz.
1 votes
Cela fonctionne avant la fusion si vous
return base.InternalGetResourceSet(culture, createIfNotExists, tryParents);
cuandostore == null
ors == null
0 votes
@Wouter : merci de me faire passer pour un idiot :) - Je vais jouer avec ce soir et mettre à jour la question/code.
0 votes
Merci encore @Wouter, c'est un résultat BEAUCOUP plus beau/plus propre !
0 votes
Belle solution, merci. Je recommanderais d'utiliser Fody.Ionad au lieu de fart.exe. Cela devrait fonctionner tant que le code des autres assemblées n'utilise pas directement la propriété ResourceManager.