文章詳情頁
Java中的國際化,第二部分
瀏覽:2日期:2024-06-16 09:55:21
內(nèi)容: 編者按:使你的Java程序能在本地小鎮(zhèn)甚至飄洋過海都運(yùn)行良好,這面臨諸多的挑戰(zhàn).我們已經(jīng)在第三版中關(guān)于國際化探討的第一部分講述了Java國際化的前兩個(gè)步驟:使用Unicode字符集編碼,遵循當(dāng)?shù)厥褂昧?xí)慣.接下來的時(shí)間,我們我們將繼續(xù)探討國際化的第三個(gè)步驟:本地化用戶可視信息.版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請(qǐng)務(wù)必保留以下作者信息和鏈接作者:leniz原文:http://www.onjava.com/pub/a/onjava/excerpt/javaexIAN3_chap8/index1.html譯文:http://www.matrix.org.cn/resource/article/44/44207_Java+Internationalization.html關(guān)鍵字:Java;Internationalization本地化用戶可視信息國際化的第三個(gè)任務(wù)涉及到確保程序中用戶可視信息沒有被硬編碼(hardcode);相反的是,應(yīng)該依據(jù)當(dāng)?shù)氐乃壮?在例8-3,字符Portfolio value,Symbol,Shares,和另外一些在程序中都是用英語進(jìn)行硬編碼的,即使程序可能運(yùn)行在遙遠(yuǎn)的法蘭西國度.唯一避免這種尷尬處境的方法就是:你的程序必須支持在運(yùn)行之時(shí)才實(shí)時(shí)地提取用戶可視的信息,并且這些信息需被實(shí)時(shí)地翻譯成各種語言.Java通過java.util包中的ResourceBundle 類來幫助我們完成這個(gè)任務(wù).這個(gè)類包含了眾多可以通過名稱來查詢的資源.將你所希望等到本地化的地區(qū)的當(dāng)?shù)刭Y源都定義到一個(gè)資源包中,運(yùn)行之時(shí)Java將準(zhǔn)確的加載本地化資源.通過加載正確的bundle,程序就可以實(shí)時(shí)的查找到對(duì)本地化支持所需要的資源(字符是比較有代表).使用Resource Bundles來處理資源為了定義一系列本地化的資源,你需要繼承ResourceBundle類生成一個(gè)類,并提供對(duì)handleGetObject()和getKeys()等方法的定義. 將資源的名稱傳給handleGetObject()方法,它將返回一個(gè)本地版本的資源.getKeys()返回的是一個(gè)Enumeration類型的類,它包含了ResourceBundle中已定義的所有的本地化資源的名稱.然而比直接繼承ResourceBundle類更為簡單的是繼承ListResourceBundle類.你可以簡潔地提供一個(gè)特性文件(參閱java.util.Properties),ResourceBundle.getBundle()方法將通過此文件生成一個(gè)PropertyResourceBundle實(shí)例.在一個(gè)程序中,為了能通過ResourceBundle類獲取本地化資源,你必須首先調(diào)用,一個(gè)靜態(tài)的getBundle()方法,這個(gè)方法將動(dòng)態(tài)加載并實(shí)例化一個(gè)ResourceBundle,正如我們即將要涉及的一樣.返回的這個(gè)ResourceBundle包含了你所定義的資源名稱,并且是針對(duì)某一特定區(qū)域的(如果沒有區(qū)域被顯式的聲明,那么將加載默認(rèn)的區(qū)域).一旦通過getBundle()方法獲取了你所要的ResourceBundle,你就可以通過getObject()方法以提供資源名稱的方式來獲取已本地化的資源.隨便提一下,你可以通過getString()方法更為便捷的把想要獲得的Object資源自動(dòng)轉(zhuǎn)型為String類型.當(dāng)調(diào)用getBundle()方法之時(shí),指定你所希望的ResourceBundle的base name和你所期望的地點(diǎn)(如果你不希望使用默認(rèn)的地點(diǎn)的話).獲取一個(gè)Locale用的是一個(gè)雙字的語言代碼,一個(gè)可選的雙字國家代碼,以及一個(gè)可選的字符變量.getBundle()方法通過把地區(qū)信息加上base name的方式,來獲取正確的本地ResourceBundle.查找一個(gè)合適的類的方法用的正是下面的算法:1.查詢一個(gè)具有下列名字的類 basename_language_country_variant 如果沒有發(fā)現(xiàn)這個(gè)類,或者沒有特別指定locale的字符變量,那么將進(jìn)行下一步查詢2.查詢一個(gè)具有下列名字的類 basename_language_country 如果沒有發(fā)現(xiàn)這個(gè)類,或者沒有特別指定locale的國家編碼,那么將進(jìn)行往下下一步的查詢3.查詢一個(gè)具有下列名字的類 basename_language 如果還是沒有發(fā)現(xiàn)這個(gè)類的話,那么將進(jìn)行最后一步4.查詢一個(gè)與basename同名的類,換句話說,就是找一個(gè)與下列名字同名的類. basenam這里包含的是默認(rèn)的本地資源包,針對(duì)的是所有沒有明確提供本地化的區(qū)域.在以上處理過程的每一步中,getBundle()都首先用給定的名字檢測一個(gè)類文件.如果沒有找到這個(gè)類文件,那么它將借助ClassLoader的getResourceAsStream()方法,通過給定的名字和.Properties后綴名去查找一個(gè)具有上述組合名稱的Properties文件.如果找到這樣的Properties文件的話,那么將通過這個(gè)文件提供的內(nèi)容,實(shí)例化一個(gè)Properties Object,并通過getBundle()初始化,返回一個(gè)PropertyResourceBundle類,這個(gè)類借助ResourceBundle API導(dǎo)入Properties文件中的所有特性.如果上述的四種查詢的任何一個(gè)查詢,針對(duì)某特定區(qū)域的類或Properties文件都沒有被找到,那么它將重復(fù)上述的查詢,但查找的是針對(duì)默認(rèn)區(qū)域的類或Properties文件. 默認(rèn)區(qū)域的查詢?nèi)匀粺o法獲取合適的ResourceBundle,getBundle()方法將拋出一個(gè)MissingResourceException異常.任何一個(gè)ResourceBundle Object 都可以有一個(gè)特別針對(duì)自己的parent ResourceBundle. 當(dāng)你在ResourceBundle中查找某個(gè)資源時(shí),getObejct()首先在一個(gè)特定的bundle中查找,如果這個(gè)資源沒有在這個(gè)bundle中被定義,那么查找將回溯到父bundle中.因此,每一個(gè)ResourceBundle都是繼承了父類的資源,可能 覆寫了某些資源,有時(shí)甚至覆蓋了全部的(需要注意的是這里提到的術(shù)語繼承(inherit)和覆寫(override)區(qū)別與通常我們?cè)谟懻揷lass時(shí)所提到的針對(duì)父類的繼承和覆寫).上述的意思就是說,我們定義的ResourceBundle不一定非要定義你的應(yīng)用程序所需要的每一個(gè)資源.比如你可能定義了一個(gè)專門針對(duì)法語用戶的信息ResourceBundle.而此時(shí)你希望有個(gè)專門針對(duì)加拿大的使用法語的用戶的ResourceBundle,你要做的就是覆寫一些信息來定義一個(gè)更為小型的而且更具有針對(duì)性的信息ResourceBundle.你的程序不需要去查找和建立它所使用的ResourceBundle的父對(duì)象.事實(shí)上getBundle()方法可以為了你自動(dòng)的做這些工作.當(dāng)getBundle()方法查找到一個(gè)符合上述要求的類或properties文件時(shí),它卻不是立即返回它已查找到的ResourceBundle.相反的是,它會(huì)按照原先的步驟繼續(xù)尋找更為普遍性的類或Properties文件,而ResourceBundle可以通過這些來繼承資源.一旦getBundle()方法找到這個(gè)更具普遍性的資源bundle,它就會(huì)把這些資源組裝成最后的bundle的祖先.一旦確定了返回的最初的ResourceBundle的所有可能,它將創(chuàng)建之.為了繼續(xù)不久之前開始的那個(gè)例子,那個(gè)例子實(shí)際運(yùn)行在魁北克,getBundle()可能首先查找一個(gè)不那么特殊的ResourceBundle類,這個(gè)類只包含了有限的特別針對(duì)魁北克的資源.接著,它查找更具一般的ResourceBundle類,這個(gè)類可能包含的是法國的信息.那么它把這個(gè)bundle作為剛剛提及的魁北克的最初的bundle的parent bundle. getBundle()最后查找的(可能發(fā)現(xiàn)的)是這樣一個(gè)類,它定義了一套默認(rèn)的資源,有可能用的是英語(假設(shè)英語是開始的那些編程者的母語).這些bundle作為法語bundle的parent bundle(同時(shí)也就是魁北克bundle的爺字輩的bundle).當(dāng)運(yùn)用程序查找一個(gè)命名的資源之時(shí),魁北克的bundle首先被查找到.如果這個(gè)命名資源沒有在此被定義,那么將查找法語的bundle,如果還是沒能找到所需要的資源,那么最后的可能就落在默認(rèn)的bundle上了.ResourceBundle 舉例調(diào)試一些代碼有助于我們把上面討論的東西,理解的更加清晰,透徹.例8-4是一個(gè)生成Swing菜單的常規(guī)程序.面對(duì)給定一系列菜單的細(xì)目,它在資源bundle中查找針對(duì)這些細(xì)目的標(biāo)簽和快捷鍵的資源,并創(chuàng)建一個(gè)本地化的菜單.下面的例子最后附有簡單的測試.圖8-3,是一個(gè)分別運(yùn)行在美國,英格蘭和法國時(shí)創(chuàng)建的菜單欄程序.如果沒有那些提供菜單標(biāo)簽本地化的resource bundle的存在,我們是不可能實(shí)現(xiàn)這個(gè)程序的. 圖 8-3 本地化菜單panes例8-4 SimpleMenu.javapackage je3.i18n;import javax.swing.*;import java.awt.*;import java.awt.event.*;import java.util.Locale;import java.util.ResourceBundle;import java.util.MissingResourceException;/** A convenience class to automatically create localized menu panes */public class SimpleMenu { /** The convenience method that creates menu panes */ public static JMenu create(ResourceBundle bundle, String menuname, String[] itemnames, ActionListener listener) { // Get the menu title from the bundle. Use name as default label. String menulabel; try { menulabel = bundle.getString(menuname + '.label'); } catch(MissingResourceException e) { menulabel = menuname; } // Create the menu pane. JMenu menu = new JMenu(menulabel); // For each named item in the menu. for(int i = 0; i < itemnames.length; i++) { // Look up the label for the item, using name as default. String itemlabel; try { itemlabel = bundle.getString(menuname+'.'+itemnames[i]+'.label'); } catch (MissingResourceException e) { itemlabel = itemnames[i]} JMenuItem item = new JMenuItem(itemlabel); // Look up an accelerator for the menu item try { String acceleratorText = bundle.getString(menuname+'.'+itemnames[i]+'.accelerator); //綠色為新加bundle.getString(menuname+'.'+itemnames[i]+'.accelerator');//這一行好像有問題. //應(yīng)該是刪除此行,將上一行補(bǔ)充完整. item.setAccelerator(KeyStroke.getKeyStroke(acceleratorText)); } catch (MissingResourceException e) {} // Register an action listener and command for the item. if (listener != null) { item.addActionListener(listener); item.setActionCommand(itemnames[i]); } // Add the item to the menu. menu.add(item); } // Return the automatically created localized menu. return menu; } /** A simple test program for the above code */ public static void main(String[] args) { // Get the locale: default, or specified on command-line Locale locale; if (args.length == 2) locale = new Locale(args[0], args[1]); else locale = Locale.getDefault( ); // Get the resource bundle for that Locale. This will throw an // (unchecked) MissingResourceException if no bundle is found. ResourceBundle bundle = ResourceBundle.getBundle('com.davidflanagan.examples.i18n.Menus', locale); //注一 // Create a simple GUI window to display the menu with final JFrame f = new JFrame('SimpleMenu: ' + // Window title locale.getDisplayName(Locale.getDefault( ))); JMenuBar menubar = new JMenuBar( ); // Create a menubar. f.setJMenuBar(menubar); // Add menubar to window // Define an action listener that our menu will use. ActionListener listener = new ActionListener( ) { public void actionPerformed(ActionEvent e) { String s = e.getActionCommand( ); Component c = f.getContentPane( ); if (s.equals('red')) c.setBackground(Color.red); else if (s.equals('green')) c.setBackground(Color.green); else if (s.equals('blue')) c.setBackground(Color.blue); } }; // Now create a menu using our convenience routine with the resource // bundle and action listener we've created JMenu menu = SimpleMenu.create(bundle, 'colors', new String[] {'red', 'green', 'blue'}, listener); // Finally add the menu to the GUI, and pop it up menubar.add(menu); // Add the menu to the menubar f.setSize(300, 150); // Set the window size. f.setVisible(true); // Pop the window up. }}這個(gè)程序不是獨(dú)立的.它依賴資源bundle來做本地化菜單的工作.接下來,羅列了三份properties文件,這些文件是為例子中的資源bundle提供的.注意下面的這個(gè)列表包含了三個(gè)獨(dú)立的文件主體.(注二)# The file Menus.properties is the default 'Menus' resource bundle.# As an American programmer, I made my own locale the default.colors.label=Colorscolors.red.label=Redcolors.red.accelerator=alt Rcolors.green.label=Greencolors.green.accelerator=alt Gcolors.blue.label=Bluecolors.blue.accelerator=alt B# This is the file Menus_en_GB.properties. It is the resource bundle for# British English. Note that it overrides only a single resource definition# and simply inherits the rest from the default (American) bundle.colors.label=Colours# This is the file Menus_fr.properties. It is the resource bundle for all# French-speaking locales. It overrides most, but not all, of the resources# in the default bundle.colors.label=Couleurscolors.red.label=Rougecolors.green.label=Vertcolors.green.accelerator=control shift Vcolors.blue.label=Bleu格式化信息我們已經(jīng)看到,為了實(shí)現(xiàn)程序的國際化,必須把所有用戶可見的信息都放到資源bundle中.當(dāng)這些文本的本地化只是包含一些象按鍵和菜單欄文本的簡單標(biāo)簽,那么一切都顯得很直觀.然而,面對(duì)的是一部分靜態(tài)內(nèi)容和一部分動(dòng)態(tài)內(nèi)容的組合體,情況就變得微妙了.比如編譯器可能要顯示一條這樣的信息,Error at line 5 of file “Hello.java,在這種情況下,行數(shù)和文件名是動(dòng)態(tài)的,而且是區(qū)域獨(dú)立的,其余的信息都是靜態(tài)的即需要被簡單地本地化操作的.java.text 包中的MessageFormat類,可以幫助我們有效的解決這個(gè)難題.為了使用它們,我們只把那些靜態(tài)的文字部分儲(chǔ)存在ResourceBundle中,對(duì)于那些動(dòng)態(tài)的信息部分中出現(xiàn)的特殊字符也必須被安置好.比如,某個(gè)資源bundle包含下面的一條信息Error at line{0} of file {1}.另一個(gè)資源bundle可能包含的是一個(gè)已被翻譯的Erreur:{1}:{0}的信息.為了使用對(duì)付這樣的本地化信息問題,我們從靜態(tài)信息中創(chuàng)建一個(gè)MessageFormat,然后調(diào)用它的format()方法.對(duì)隊(duì)列中的值進(jìn)行替換.在這種情況下,這個(gè)隊(duì)列中包含一個(gè)用于行數(shù)的Integer 對(duì)象和一個(gè)用于文件名的String對(duì)象MessageFormat對(duì)象可以和java.text包中的其它Format類進(jìn)行通信.它創(chuàng)建和使用NumberFormat對(duì)象來格式化數(shù)字并使用DateFormat對(duì)象格式化日期和時(shí)間.另外,可以設(shè)計(jì)信息,用這些信息創(chuàng)建一個(gè)ChoiceFormat對(duì)象,以此實(shí)現(xiàn)從數(shù)字形式到字符形式的轉(zhuǎn)變.當(dāng)操作計(jì)數(shù)值類型時(shí),這些顯得尤其有用,例如對(duì)應(yīng)月份名稱的數(shù)字,或當(dāng)你需要根據(jù)數(shù)字值的多少來決定某個(gè)詞的單數(shù)或復(fù)數(shù).例8-5 演示了這種MessageFormat的使用.這是一個(gè)很方便的類,有一個(gè)單獨(dú)的靜態(tài)方法用于本地化顯示異常信息或錯(cuò)誤信息.但調(diào)用的時(shí)候,代碼試圖加載一個(gè)名為Errors的ResourceBundle.當(dāng)找到了這個(gè)類,那么它將查詢使用了傳遞過來的異常對(duì)象名字的信息資源.如果這個(gè)信息資源也被找到,它將顯示這個(gè)信息.擁有五個(gè)值的隊(duì)列被傳遞給format()方法.本地化了的錯(cuò)誤提示信息可以包括任何一個(gè)或是全部這些參數(shù).例子中定義的LocalizedError.display()方法,曾經(jīng)在本章開始的例8-2中使用過.與此例配合使用的默認(rèn)Errors.Properties資源bundle, 在代碼后面也有相應(yīng)提供.應(yīng)用程序的錯(cuò)誤信息提示在這里得到了很好的國際化.將一個(gè)應(yīng)用程序的錯(cuò)誤信息提示應(yīng)用到一個(gè)新的區(qū)域中,所要做的無法只是轉(zhuǎn)化(本地化)Errors.Properties文件.Example 8-5. LocalizedError.javapackage je3.i18n;import java.text.*;import java.io.*;import java.util.*;/** * A convenience class that can display a localized exception message * depending on the class of the exception. It uses a MessageFormat, * and passes five arguments that the localized message may include: * {0}: the message included in the exception or error. * {1}: the full class name of the exception or error. * {2}: the file the exception occurred in * {3}: a line number in that file. * {4}: the current date and time. * Messages are looked up in a ResourceBundle with the basename * 'Errors', using a the full class name of the exception object as * the resource name. If no resource is found for a given exception * class, the superclasses are checked. **/public class LocalizedError { public static void display(Throwable error) { ResourceBundle bundle; // Try to get the resource bundle. // If none, print the error in a nonlocalized way. try { String bundleName = 'com.davidflanagan.examples.i18n.Errors'; bundle = ResourceBundle.getBundle(bundleName); } catch (MissingResourceException e) { error.printStackTrace(System.err); return; } // Look up a localized message resource in that bundle, using the // classname of the error (or its superclasses) as the resource name. // If no resource was found, display the error without localization. String message = null; Class c = error.getClass( ); while((message == null) && (c != Object.class)) { try { message = bundle.getString(c.getName( )); } catch (MissingResourceException e) { c = c.getSuperclass( ); } } if (message == null) { error.printStackTrace(System.err); return; } // Get the filename and linenumber for the exception // In Java 1.4, this is easy, but in prior releases, we had to try // parsing the output Throwable.printStackTrace( ); StackTraceElement frame = error.getStackTrace( )[0]// Java 1.4 String filename = frame.getFileName( ); int linenum = frame.getLineNumber( ); // Set up an array of arguments to use with the message String errmsg = error.getMessage( ); Object[ ] args = { ((errmsg!= null)?errmsg:''), error.getClass( ).getName( ), filename, new Integer(linenum), new Date( ) }; // Finally, display the localized error message, using // MessageFormat.format( ) to substitute the arguments into the message. System.err.println(MessageFormat.format(message, args)); } /** * This is a simple test program that demonstrates the display( ) method. * You can use it to generate and display a FileNotFoundException or an * ArrayIndexOutOfBoundsException **/ public static void main(String[ ] args) { try { FileReader in = new FileReader(args[0]); } catch(Exception e) { LocalizedError.display(e); } }}下面羅列的是資源bundle需要的properties文件,這些文件用于本地化由例8-2的ConvertEncoding所產(chǎn)生的錯(cuò)誤信息.## This is the file Errors.properties# One property for each class of exceptions that our program might# report. Note the use of backslashes to continue long lines onto the# next. Also note the use of n and t for newlines and tabs#java.io.FileNotFoundException: Error: File '{0}' not foundntError occurred at line {3} of file '{2}'ntat {4}java.io.UnsupportedEncodingException: Error: Specified encoding not supportedntError occurred at line {3} of file '{2}'ntat {4,time} on {4,date}java.io.CharConversionException:Error: Character conversion failure. Input data is not in specified format.# A generic resource. Display a message for any error or exception that# is not handled by a more specific resource.java.lang.Throwable:Error: {1}: {0}ntError occurred at line {3} of file '{2}'nt{4,time,long} {4,date,long}當(dāng)我們擁有這樣一個(gè)resource bundle后, ConvertEncoding 將會(huì)產(chǎn)生類似下面的 error messages:Error: File 'myfile (No such file or directory)' not found Error occurred at line 64 of file 'FileInputStream.java' at 7/9/00 9:28 PM或者,如果是在法國的話:Error: File 'myfile (Aucun fichier ou repertoire de ce type)' not found Error occurred at line 64 of file 'FileInputStream.java' at 09/07/00 21:28練習(xí) 8-1一些用于國際化的類,例如NumberFormat,DateFormat等,都有一個(gè)靜態(tài)的方法getAvailableLocales(),這個(gè)方法返回一系列他們所支持的本地化對(duì)象.你可以在一個(gè)給定的本地化對(duì)象中,通過getDisplayCountry()方法來獲取這個(gè)給定區(qū)域的國家名稱.這個(gè)方法有兩種方式.一種是不帶參數(shù)的,返回顯示的是默認(rèn)地的國家名稱.另一種是需要提供一個(gè)Locale作為參數(shù),返回的是特定區(qū)域語言下的國家名稱.寫一個(gè)程序,這個(gè)程序通過NumberFormat.getAvaileLocales()方法可以顯示所有區(qū)域的國家名稱.使用Locale定義的靜態(tài)locale常量,用英語,法語,德語,意大利語分別顯示每一個(gè)國家的名稱. 附錄:注一:這里的com.davidflanagan.examples.i18n是放置了幾個(gè)properties文件的package名,如果你將附帶的properties放到其它的package中,請(qǐng)做相應(yīng)的變動(dòng).Menus是默認(rèn)的名稱注二:分別保存到三個(gè)獨(dú)立的文本文件中,用注釋中的名字進(jìn)行命名,以此為針對(duì)中國用戶的菜單可以是:文件四: Menus_zh.propertiescolors.label=顏色設(shè)置colors.red.label=紅色colors.red.accelerator=alt Rcolors.green.label=綠色colors.green.accelerator=alt Gcolors.blue.label=藍(lán)色colors.blue.accelerator=alt B如果出現(xiàn)中文字符亂碼問題,請(qǐng)將加入下列方法,對(duì)菜單字符進(jìn)行轉(zhuǎn)換,所以這里也要多考慮一下字符集的問題/*** @todo傳入一個(gè)以UTF-8編碼的字符,輸出UTF-16編碼的中文* @ param inputStr String 需要轉(zhuǎn)換的字串*/public static String getChineseOut(String inputStr){ try{ String temp_s = inputStr; byte[] temp_b = null; temp_b = temp_s.getBytes('ISO8859-1'); String result = new String(temp_b); return result; } catch(Exception e){ e.printStackTrace(); return inputStr; } } Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd 編者按:使你的Java程序能在本地小鎮(zhèn)甚至飄洋過海都運(yùn)行良好,這面臨諸多的挑戰(zhàn).我們已經(jīng)在第三版中關(guān)于國際化探討的第一部分講述了Java國際化的前兩個(gè)步驟:使用Unicode字符集編碼,遵?
標(biāo)簽:
Java
相關(guān)文章:
1. Python實(shí)現(xiàn)PIL圖像處理庫繪制國際象棋棋盤2. python輸出國際象棋棋盤的實(shí)例分享3. Vue Element前端應(yīng)用開發(fā)之界面語言國際化4. js找出5個(gè)數(shù)中最大的一個(gè)數(shù)和倒數(shù)第二大的數(shù)實(shí)現(xiàn)方法示例小結(jié)5. java虛擬機(jī)詳述-第二章(一)6. spring boot國際化之MessageSource的使用方法7. Python列表獲得最大值和第二大值教程分享8. Python基于Twilio及騰訊云實(shí)現(xiàn)國際國內(nèi)短信接口9. pygame實(shí)現(xiàn)井字棋之第二步邏輯實(shí)現(xiàn)10. 解決VUE-Router 同一頁面第二次進(jìn)入不刷新的問題
排行榜

網(wǎng)公網(wǎng)安備