利用 .NET Core 2.0 和 Angular7 写一套多语言框架

一、实现思想

后台采用CSV文件的形式存储多语言的数据,然后前端通过API请求调用后端多语言接口,业务处理层对数据进行组合,最后根据匹配函数对CSV中的数据进行匹配,取出相对应的语言数据返回给前端。

二、后端 .NET CORE具体实现

  1. 先写一个返回给前端的ResultVM方法。
public class ResultVM
    {
        Int32 _Affected = 0;
        object _Data;

        public Boolean IsSuccess
        {
            get
            {
                if (_Data != null)
                {
                    return true;
                }
                return _Affected > 0;
            }
        }

        public Int32 Affected
        {
            get { return _Affected; }
            set { _Affected = value; }
        }

        public object Data
        {
            get { return _Data; }
            set { _Data = value; }
        }

        public String Message { get; set; }

        public void SetMessageJson(String key, String value)
        {
            this.Message = "{" + String.Format("'{0}':'{1}'", key, value) + "}";
        }

        public void SetMessageJson(IList<String> keys, IList<Object> values)
        {
            if (keys.Count < 1 || values.Count < 1 || keys.Count != values.Count)
            {
                this.Message = "";
            }

            StringBuilder error = new StringBuilder();
            error.Append("{");

            for (int i = 0; i < keys.Count; i++)
            {
                error.Append("'" + keys[i] + "':'" + values[i] + "',");
            }

            error.Remove(error.Length - 1, 1).Append("}");
            this.Message = error.ToString();
        }
    }
  1. 接着写一个读取CSV的方法,读取出来的数据转化成JSON格式。
public string OpenCSVToJson(string filePath, string AreaCode)
        {

            System.Text.Encoding encoding = GetType(filePath);  
            System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
            System.IO.StreamReader sr = new System.IO.StreamReader(fs, encoding);
            string strLine = "";
            string[] aryLine = null;
            bool IsFirst = true;
            string sJson = "{";
            while ((strLine = sr.ReadLine()) != null)
            {
                if (IsFirst == true)
                {
                    IsFirst = false;
                    continue;
                }
                else
                {
                    aryLine = strLine.Split(',');
                    if (aryLine[0].ToString() == AreaCode)
                    {
                        sJson += "\"" + aryLine[1] + "\":" + "\"" + aryLine[2] + "\",";
                    }
                }
            }
            sJson = sJson.Substring(0, sJson.Length - 1) + "}";

            sr.Close();
            fs.Close();
            return sJson;
        }
public static System.Text.Encoding GetType(string FILE_NAME)
        {
            System.IO.FileStream fs = new System.IO.FileStream(FILE_NAME, System.IO.FileMode.Open, System.IO.FileAccess.Read);
            System.Text.Encoding r = GetType(fs);
            fs.Close();
            return r;
        }
public static System.Text.Encoding GetType(System.IO.FileStream fs)
        {
            byte[] Unicode = new byte[] { 0xFF, 0xFE, 0x41 };
            byte[] UnicodeBIG = new byte[] { 0xFE, 0xFF, 0x00 };
            byte[] UTF8 = new byte[] { 0xEF, 0xBB, 0xBF }; // With BOM
            System.Text.Encoding reVal = System.Text.Encoding.Default;

            System.IO.BinaryReader r = new System.IO.BinaryReader(fs, System.Text.Encoding.Default);
            int i;
            int.TryParse(fs.Length.ToString(), out i);
            byte[] ss = r.ReadBytes(i);
            if (IsUTF8Bytes(ss) || (ss[0] == 0xFF && ss[1] == 0xBB && ss[2] == 0xBF))
            {
                reVal = System.Text.Encoding.UTF8;
            }
            else if (ss[0] == 0xFE && ss[1] == 0xFF && ss[2] == 0x00)
            {
                reVal = System.Text.Encoding.BigEndianUnicode;
            }
            else if (ss[0] == 0xFF && ss[1] == 0xFE && ss[2] == 0x41)
            {
                reVal = System.Text.Encoding.Unicode;
            }
            r.Close();
            return reVal;
        }
public static bool IsUTF8Bytes(byte[] data)
        {
            int charByteCounter = 1;
            byte curByte;
            for (int i = 0; i < data.Length; i++)
            {
                curByte = data[i];
                if (charByteCounter == 1)
                {
                    if (curByte >= 0x80)
                    {

                        while (((curByte <<= 1) & 0x80) != 0)
                        {
                            charByteCounter++;
                        }

                        if (charByteCounter == 1 || charByteCounter > 6)
                        {
                            return false;
                        }
                    }
                }
                else
                {

                    if ((curByte & 0xC0) != 0x80)
                    {
                        return false;
                    }
                    charByteCounter--;
                }
            }
            if (charByteCounter > 1)
            {
                throw new Exception("Unexpected byte format");
            }
            return true;
        }
  1. 创建一个新的API Controller,写一个接口。
    在这里插入图片描述
/// <summary>
        /// Get Multilingual To Json
        /// </summary>
        /// <param name="VM"></param>
        /// <returns></returns>
        [HttpGet]
        public ResultVM GetMultilingualToJson(MultilingualVM VM)
        {
            return new ResultVM { Data = BL.GetMultilingualToJson(VM) };
        }
  1. 接口接收的Model MultilingualVM,一个属性是接收前端页面的名字,一个属性是接收语言的类型(比如中文简体2052,英文1033等)
public class MultilingualVM
    {
        public string PageName { get; set; }
        public string Category { get; set; }
    }
  1. 创建业务逻辑层,处理接口接收到的数据。利用刚刚上面写好的读取CSV数据的方法读取对应的多语言数据。
/// <summary>
        /// Get page multilingual
        /// </summary>
        /// <param name="VM"></param>
        /// <returns></returns>
        public ResultVM GetMultilingualToJson(MultilingualVM VM)
        {
            var config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();

            string sResult = "{";
            string[] pageNames = VM.PageName.Split(';');
            foreach (string pageName in pageNames)
            {
                sResult += "\"" + pageName + "\":" + (ReadCSV.OpenCSVToJson(System.IO.Directory.GetCurrentDirectory() + config["MultilingualFilePath:" + "Load_" + pageName + "_Multilingual"], VM.Category)) + ",";
            }
            sResult = sResult.Substring(0, sResult.Length - 1) + "}";

            return new ResultVM { Data = sResult };
        }

至此后端的框架就已经搭建完成。

二、前端框架具体实现

前端实现思想:采用Angular前端框架做的页面往往一个页面会包含多个组件,组件之间经常会用路由来进行切换与交互。那这里就涉及到组件与组件之间交互的问题了。Angular7官网上也提供了几种可供组件与组件之间交互的方法(Angular官网通道)。我们在这里采用共享服务方式来解决这个问题。在最外层的组件作为父组件,提供服务。在里层的其他组件则作为子组件,注册服务。所以只需要让父组件去后端获取一个多语言数据,然后再分配给注册过服务的子组件们。
在这里插入图片描述

  1. 首先写一个共享服务MissionService。
@Injectable({
    providedIn: 'root'
})

export class MissionService {

    constructor(
        private multilingualService: MultilingualService
    ) {
    }

    public languageID: any;
    public arrayCSV = [];
    private languageData = new Subject<any>();

    languageData$ = this.languageData.asObservable();

    setLanguageID(languageID: any) {
        this.arrayCSV.filter(filtrate => {
            filtrate.category = languageID;
            this.multilingualService.getMultilingualToJson(filtrate).subscribe(
                res => {
                    filtrate.data = JSON.parse(res.data.data);
                }
            );
        });
        this.languageData.next(this.arrayCSV);
    }

    getLanguageData(csvName: any): any {
        let vm = { pageName: csvName, category: this.languageID, data: [] };
        //check arrayCSV
        if (this.arrayCSV.filter(filtrate => filtrate.pageName == csvName).length == 0) {
            this.multilingualService.getMultilingualToJson(vm).subscribe(
                res => {
                    vm.data = JSON.parse(res.data.data);
                }
            )
            this.arrayCSV.push(vm);
        }
        this.languageData.next(this.arrayCSV);
    }
}
  1. 接着写一个请求后端多语言接口的服务MultilingualService。
@Injectable({
  providedIn: 'root'
})
export class MultilingualService {
  constructor(
    private http: HttpClient,
    private generateApiUrlService: GenerateApiUrlService
  ) { } 
  getMultilingualToJson(multilingualVM: any):Observable<any>{
    return this.http.get<any>(this.generateApiUrlService.getCommonApiUrl('Multilingual/GetMultilingualToJson'),{params:multilingualVM}).pipe();
 	 }
  }
}

3.写一个共享下拉框多语言组件(这里引用到了NG-ZORRO组件库中的下拉框组件,具体的引用方法可以查询官网的文档https://ng.ant.design/docs/introduce/zh)。这里会将当前多语言的类型存储在cookie中。在这个组件中提供了上面我们所建的服务MissionService,同时也引入了多语言服务MultilingualService进行多语言API请求。当组件通过MultilingualService请求后端接口接收到返回的数据时,会将数据放到共享服务上,最后共享服务会将数据分配给注册了服务的子组件中。

multilingual.component.html

<div class="psms-multilingual form-group pull-right">
    <nz-select style="width: 120px;" [(ngModel)]="languageValue" (ngModelChange)="getChange($event)">
        <nz-option *ngFor="let language of languages" [nzValue]="language.languageCode" [nzLabel]="language.dictDescription"></nz-option>
    </nz-select>
</div>

multilingual.component.ts

export class MultilingualComponent implements OnInit {

  languages = [];
  languageValue: string = '1033';


  constructor(
    private cookieService: CookieServiceService
    , private missionService: MissionService
    , private multilingualService: MultilingualService
  ) {
    this.getMultilingualSelectValue();
    this.missionService.languageID = this.languageValue;
  }

  ngOnInit() {
  }

  getChange(languageCode: string): void {

    this.missionService.languageID = languageCode;
    this.missionService.setLanguageID(languageCode);
    this.cookieService.setCookieValue('multilingual', languageCode);

  }

  getMultilingualSelectValue() {
    this.multilingualService.getMultilingualSelectValueApi().subscribe(Response => {
      this.languages = Response.data.data.table;
    })
    if (this.cookieService.checkCookieName('multilingual')) {
      this.languageValue = this.cookieService.getMultilingualCookie().toString();
    }
  }

}

至此,只需要把这个多语言下拉框共享组件引用到具体的组件中,就可以获取到相对应的多语言数据了。
这里贴一张下拉框的效果图:
在这里插入图片描述

具体的细节有什么不理解的,可以随时提问!