T4 Template - JsResource.tt
在撰寫 ASP.NET 時,.NET 程式部分可用 Resource 去做多語的部分,JavaScript 這邊雖然也有 L10N 的解決方案,但是若走不同的解決方案,難以避免有些詞彙會重複定義。
這邊筆者嘗試使用 T4 來解決這樣的問題。
<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ assembly name="System.Windows.Forms" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="Microsoft.VisualStudio.OLE.Interop" #>
<#@ assembly name="Microsoft.VisualStudio.Shell" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #>
<#@ import namespace="System.Resources" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Microsoft.VisualStudio.Shell" #>
<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ output extension=".js"#>
<#
var path = Path.GetDirectoryName(Host.TemplateFile) + "/App_GlobalResources/";
var resourceFiles= Directory.GetFiles(path, "*.resx");
foreach (var resourceFile in resourceFiles) {
var fileName = Path.GetFileNameWithoutExtension(resourceFile);
var resourceName = Regex.Match(fileName, "([^.]*)").Groups[1].Value;
#>
/**
* Resources
* ---------
* This file is auto-generated by a tool
* 2012 Jochen van Wylick
* 2016 LarryNung
**/
var <#=resourceName #> = {};
<#
var resxSet = new ResXResourceSet(Host.ResolvePath(resourceFile));
foreach (DictionaryEntry item in resxSet) {
#>
<#=resourceName#>.<#=item.Key.ToString() #> = "<#=resxSet.GetString(item.Key.ToString()).Replace("
", string.Empty).Replace("'","\'")#>";
<#
}
#>
<#
SaveOutput(fileName + ".js");
}
DeleteOldOutputs();
#>
<#+
List<string> __savedOutputs = new List<string>();
Engine __engine = new Engine();
void DeleteOldOutputs()
{
EnvDTE.ProjectItem templateProjectItem = __getTemplateProjectItem();
foreach (EnvDTE.ProjectItem childProjectItem in templateProjectItem.ProjectItems)
{
if (!__savedOutputs.Contains(childProjectItem.Name))
childProjectItem.Delete();
}
}
void ProcessTemplate(string templateFileName, string outputFileName)
{
string templateDirectory = Path.GetDirectoryName(Host.TemplateFile);
string outputFilePath = Path.Combine(templateDirectory, outputFileName);
string template = File.ReadAllText(Host.ResolvePath(templateFileName));
string output = __engine.ProcessTemplate(template, Host);
File.WriteAllText(outputFilePath, output);
EnvDTE.ProjectItem templateProjectItem = __getTemplateProjectItem();
templateProjectItem.ProjectItems.AddFromFile(outputFilePath);
__savedOutputs.Add(outputFileName);
}
void SaveOutput(string outputFileName)
{
string templateDirectory = Path.GetDirectoryName(Host.TemplateFile);
string outputFilePath = Path.Combine(templateDirectory, outputFileName);
File.WriteAllText(outputFilePath, this.GenerationEnvironment.ToString(), Encoding.UTF8);
this.GenerationEnvironment = new StringBuilder();
EnvDTE.ProjectItem templateProjectItem = __getTemplateProjectItem();
templateProjectItem.ProjectItems.AddFromFile(outputFilePath);
__savedOutputs.Add(outputFileName);
}
EnvDTE.ProjectItem __getTemplateProjectItem()
{
EnvDTE.Project dteProject = __getTemplateProject();
IVsProject vsProject = __dteProjectToVsProject(dteProject);
int iFound = 0;
uint itemId = 0;
VSDOCUMENTPRIORITY[] pdwPriority = new VSDOCUMENTPRIORITY[1];
int result = vsProject.IsDocumentInProject(Host.TemplateFile, out iFound, pdwPriority, out itemId);
if (result != VSConstants.S_OK)
throw new Exception("Unexpected error calling IVsProject.IsDocumentInProject");
if (iFound == 0)
throw new Exception("Cannot retrieve ProjectItem for template file");
if (itemId == 0)
throw new Exception("Cannot retrieve ProjectItem for template file");
Microsoft.VisualStudio.OLE.Interop.IServiceProvider itemContext = null;
result = vsProject.GetItemContext(itemId, out itemContext);
if (result != VSConstants.S_OK)
throw new Exception("Unexpected error calling IVsProject.GetItemContext");
if (itemContext == null)
throw new Exception("IVsProject.GetItemContext returned null");
ServiceProvider itemContextService = new ServiceProvider(itemContext);
EnvDTE.ProjectItem templateItem = (EnvDTE.ProjectItem)itemContextService.GetService(typeof(EnvDTE.ProjectItem));
Debug.Assert(templateItem != null, "itemContextService.GetService returned null");
return templateItem;
}
EnvDTE.Project __getTemplateProject()
{
IServiceProvider hostServiceProvider = (IServiceProvider)Host;
if (hostServiceProvider == null)
throw new Exception("Host property returned unexpected value (null)");
EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
if (dte == null)
throw new Exception("Unable to retrieve EnvDTE.DTE");
Array activeSolutionProjects = (Array)dte.ActiveSolutionProjects;
if (activeSolutionProjects == null)
throw new Exception("DTE.ActiveSolutionProjects returned null");
EnvDTE.Project dteProject = (EnvDTE.Project)activeSolutionProjects.GetValue(0);
if (dteProject == null)
throw new Exception("DTE.ActiveSolutionProjects[0] returned null");
return dteProject;
}
static IVsProject __dteProjectToVsProject(EnvDTE.Project project)
{
if (project == null)
throw new ArgumentNullException("project");
string projectGuid = null;
// DTE does not expose the project GUID that exists at in the msbuild project file.
// Cannot use MSBuild object model because it uses a static instance of the Engine,
// and using the Project will cause it to be unloaded from the engine when the
// GC collects the variable that we declare.
using (XmlReader projectReader = XmlReader.Create(project.FileName))
{
projectReader.MoveToContent();
object nodeName = projectReader.NameTable.Add("ProjectGuid");
while (projectReader.Read())
{
if (Object.Equals(projectReader.LocalName, nodeName))
{
projectGuid = (string)projectReader.ReadElementContentAsString();
break;
}
}
}
if (string.IsNullOrEmpty(projectGuid))
throw new Exception("Unable to find ProjectGuid element in the project file");
Microsoft.VisualStudio.OLE.Interop.IServiceProvider dteServiceProvider =
(Microsoft.VisualStudio.OLE.Interop.IServiceProvider)project.DTE;
IServiceProvider serviceProvider = new ServiceProvider(dteServiceProvider);
IVsHierarchy vsHierarchy = VsShellUtilities.GetHierarchy(serviceProvider, new Guid(projectGuid));
IVsProject vsProject = (IVsProject)vsHierarchy;
if (vsProject == null)
throw new ArgumentException("Project is not a VS project.");
return vsProject;
}
#>
像是這邊筆者準備了不同的資源檔。
{% img /images/posts/T4JSResource/1.png %}
透過 T4 會產生對應的 js 檔。
{% img /images/posts/T4JSResource/2.png %}
只要將 js 檔引用進來就可以直接使用。
{% img /images/posts/T4JSResource/3.png %}
{% img /images/posts/T4JSResource/4.png %}