Tài nguyên chuỗi

Tài nguyên chuỗi cung cấp chuỗi văn bản cho ứng dụng bằng định dạng và định kiểu văn bản tuỳ chọn. Có 3 loại tài nguyên có thể cung cấp chuỗi cho ứng dụng của bạn:

String
Tài nguyên XML cung cấp một chuỗi đơn.
Mảng chuỗi
Tài nguyên XML cung cấp một mảng chuỗi.
Chuỗi số lượng (số nhiều)
Tài nguyên XML chứa các chuỗi khác nhau để số hoá.

Tất cả chuỗi đều có khả năng áp dụng một số đối số định dạng và đánh dấu kiểu. Để biết thông tin về cách định kiểu và định dạng các chuỗi, hãy xem phần Định dạng và định kiểu.

Chuỗi

Một chuỗi đơn có thể được tham chiếu từ mã xử lý ứng dụng (chẳng hạn như hàm composable) hoặc từ các tệp tài nguyên khác.

vị trí tệp:
res/values/filename.xml
Bạn có thể tuỳ ý đặt tên tệp. name của phần tử <string> được dùng làm mã nhận dạng tài nguyên.
loại dữ liệu tài nguyên được biên dịch:
Con trỏ tài nguyên đến String.
mã tham chiếu tài nguyên:
Trong Kotlin: R.string.string_name
Trong XML: @string/string_name
cú pháp:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string
        name="string_name"
        >text_string</string>
</resources>
phần tử:
<resources>
Bắt buộc. Đây phải là nút gốc.

Không có thuộc tính nào.

<string>
Một chuỗi có thể chứa các thẻ định kiểu. Lưu ý là bạn phải thoát khỏi dấu nháy đơn và dấu ngoặc kép. Để biết thêm thông tin về cách định kiểu và định dạng đúng chuỗi của bạn, vui lòng xem phần Định dạng và định kiểu bên dưới.

thuộc tính:

name
Chuỗi. Tên cho chuỗi. Tên này sẽ được dùng làm mã nhận dạng tài nguyên.
ví dụ:
Tệp XML được lưu vào res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello!</string>
</resources>

Mã xử lý ứng dụng này truy xuất một chuỗi từ bên trong một thành phần kết hợp bằng stringResource():

@Composable
fun Greeting() {
    Text(text = stringResource(R.string.hello))
}

Lưu ý: Để truy xuất một chuỗi bên ngoài hàm composable, hãy dùng context.getString(R.string.hello).

Bạn cũng có thể tham chiếu các tài nguyên chuỗi từ các tệp XML khác, chẳng hạn như AndroidManifest.xml:
<activity
    android:name=".MainActivity"
    android:label="@string/hello" />

Mảng chuỗi

Một mảng chuỗi có thể được tham chiếu từ ứng dụng.

vị trí tệp:
res/values/filename.xml
Bạn có thể tuỳ ý đặt tên tệp. name của phần tử <string-array> được dùng làm mã nhận dạng tài nguyên.
loại dữ liệu tài nguyên được biên dịch:
Con trỏ tài nguyên đến một mảng String.
mã tham chiếu tài nguyên:
Trong Kotlin: R.array.string_array_name
Trong XML: @[package:]array/string_array_name
cú pháp:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array
        name="string_array_name">
        <item
            >text_string</item>
    </string-array>
</resources>
phần tử:
<resources>
Bắt buộc. Đây phải là nút gốc.

Không có thuộc tính nào.

<string-array>
Xác định một mảng chuỗi. Chứa một hoặc nhiều phần tử <item>.

thuộc tính:

name
Chuỗi. Tên cho mảng. Tên này được dùng làm mã nhận dạng tài nguyên tham chiếu đến mảng.
<item>
Một chuỗi có thể chứa các thẻ định kiểu. Giá trị này có thể là một tham chiếu đến tài nguyên chuỗi khác. Phải là phần tử con của phần tử <string-array>. Lưu ý là bạn phải thoát khỏi dấu nháy đơn và dấu ngoặc kép. Vui lòng xem phần Định dạng và định kiểu bên dưới để biết thông tin về cách định kiểu và định dạng đúng cách các chuỗi.

Không có thuộc tính nào.

ví dụ:
Tệp XML được lưu vào res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="planets_array">
        <item>Mercury</item>
        <item>Venus</item>
        <item>Earth</item>
        <item>Mars</item>
    </string-array>
</resources>

Mã xử lý ứng dụng này truy xuất một mảng chuỗi từ bên trong một thành phần kết hợp bằng stringArrayResource():

@Composable
fun PlanetList() {
    val planets: Array =
        stringArrayResource(R.array.planets_array)
    // Render the array, e.g. inside a LazyColumn.
}

Lưu ý: Để truy xuất một mảng chuỗi bên ngoài hàm composable, hãy dùng context.resources.getStringArray(R.array.planets_array).

Chuỗi số lượng (số nhiều)

Mỗi ngôn ngữ có những quy tắc riêng đối với thoả thuận ngữ pháp về số lượng. Chẳng hạn như trong tiếng Anh, số lượng 1 là một trường hợp đặc biệt. Chúng tôi viết "1 cuốn sách", nhưng đối với bất kỳ số lượng nào khác, chúng tôi sẽ viết "n cuốn sách". Sự khác biệt giữa số ít và số nhiều này rất phổ biến, nhưng các ngôn ngữ khác cũng giúp phân biệt rõ hơn. Tập hợp đầy đủ do Android hỗ trợ là zero, one, two, few, manyother.

Quy tắc quyết định trường hợp sử dụng cho một ngôn ngữ và số lượng nhất định có thể rất phức tạp, vì vậy, Android cung cấp cho bạn các phương thức như pluralStringResource() để chọn tài nguyên phù hợp cho bạn.

Mặc dù trước đây được gọi là "chuỗi số lượng" (và vẫn được gọi như vậy trong API), nhưng bạn chỉ nên sử dụng chuỗi số lượng cho số nhiều. Chẳng hạn sẽ là một sai lầm nếu bạn sử dụng chuỗi số lượng để triển khai một số mục như "Hộp thư đến" của Gmail so với "Hộp thư đến (12)" khi có thư chưa đọc. Việc sử dụng chuỗi số lượng thay vì một câu lệnh if có vẻ thuận tiện, nhưng quan trọng là bạn cần lưu ý một số ngôn ngữ (chẳng hạn như tiếng Trung) không tạo ra khác biệt nào về ngữ pháp. Vì vậy, bạn sẽ luôn nhận chuỗi other.

Việc lựa chọn chuỗi cần sử dụng chỉ dựa trên sự cần thiết về mặt ngữ pháp. Trong tiếng Anh, một chuỗi cho zero sẽ bị bỏ qua ngay cả khi số lượng là 0, vì số 0 không khác về mặt ngữ pháp với số 2 hoặc bất kỳ số nào khác trừ số 1 ("0 cuốn sách", "một cuốn sách", "2 cuốn sách", v.v.). Ngược lại, trong tiếng Hàn, chỉ chuỗi other được là sử dụng.

Cũng đừng nhầm lẫn bởi thực tế two có vẻ như chỉ có thể áp dụng cho số lượng 2: một ngôn ngữ có thể yêu cầu xử lý số 2, 12, 102 (v.v.) như nhau, nhưng khác với các số lượng khác. Hãy bám sát vào người biên dịch để biết ngôn ngữ của họ thực sự nhấn mạnh đến những điểm khác biệt nào.

Nếu thông báo của bạn không chứa số lượng, thì đó có thể không phải là lựa chọn phù hợp cho số nhiều. Ví dụ: trong tiếng Lithuania, dạng số ít được dùng cho cả 1 và 101, vì vậy "1 cuốn sách" được dịch thành "1 knyga" và "101 cuốn sách" được dịch thành "101 knyga". Trong khi đó, "một cuốn sách" là "knyga" và "nhiều cuốn sách" là "daug knygų". Nếu một thông báo số nhiều bằng tiếng Anh có chứa "a book" (số ít) và "many books" (số nhiều) mà không có số lượng thực tế, thì thông báo đó có thể được dịch là "knyga" (một cuốn sách)/"daug knygų" (nhiều cuốn sách), nhưng theo quy tắc của tiếng Lithuania, thông báo đó sẽ hiển thị "knyga" (một cuốn sách) khi số lượng là 101.

Bạn cũng có thể tránh các chuỗi số lượng bằng cách sử dụng các công thức trung lập về số lượng, chẳng hạn như "Sách: 1". Việc này giúp cuộc sống của bạn và những người biên dịch trở nên dễ chịu hơn nếu đó là phong cách chấp nhận được cho ứng dụng của bạn.

Thay vào đó, trên API cấp 24 trở lên, bạn có thể sử dụng lớp ICU MessageFormat mạnh mẽ hơn nhiều.

vị trí tệp:
res/values/filename.xml
Bạn có thể tuỳ ý đặt tên tệp. name của phần tử <plurals> được dùng làm mã nhận dạng tài nguyên.
mã tham chiếu tài nguyên:
Trong Kotlin: R.plurals.plural_name
cú pháp:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals
        name="plural_name">
        <item
            quantity=["zero" | "one" | "two" | "few" | "many" | "other"]
            >text_string</item>
    </plurals>
</resources>
phần tử:
<resources>
Bắt buộc. Đây phải là nút gốc.

Không có thuộc tính nào.

<plurals>
Một bộ sưu tập các chuỗi, trong đó một chuỗi được cung cấp tuỳ thuộc vào số lượng của một nội dung nào đó. Chứa một hoặc nhiều phần tử <item>.

thuộc tính:

name
Chuỗi. Tên của cặp chuỗi. Tên này sẽ được dùng làm mã nhận dạng tài nguyên.
<item>
Một chuỗi số nhiều hoặc số ít. Giá trị này có thể là một tham chiếu đến tài nguyên chuỗi khác. Phải là phần tử con của phần tử <plurals>. Lưu ý là bạn phải thoát khỏi dấu nháy đơn và dấu ngoặc kép. Vui lòng xem phần Định dạng và định kiểu bên dưới để biết thông tin về cách định kiểu và định dạng đúng cách các chuỗi.

thuộc tính:

quantity
Từ khoá. Một giá trị cho biết thời điểm nên sử dụng chuỗi này. Các giá trị hợp lệ, với những ví dụ không đầy đủ trong ngoặc đơn:
Giá trịMô tả
zeroKhi ngôn ngữ yêu cầu cách xử lý đặc biệt đối với số 0 (như trong tiếng Ả Rập).
oneKhi ngôn ngữ yêu cầu xử lý đặc biệt các số chẳng hạn như số một (như với số 1 trong tiếng Anh và hầu hết ngôn ngữ khác; trong tiếng Nga, bất kỳ số nào kết thúc bằng số 1 nhưng không kết thúc bằng số 11 đều ở trong lớp này).
twoKhi ngôn ngữ yêu cầu xử lý đặc biệt các số chẳng hạn như số hai (như với số 2 trong tiếng Wales hoặc 102 trong tiếng Slovenia).
fewKhi ngôn ngữ yêu cầu cách xử lý đặc biệt các số "nhỏ" (như với số 2, 3 và 4 trong tiếng Séc; hoặc các số kết thúc bằng số 2, 3 hoặc 4 nhưng không phải là số 12, 13 hoặc 14 trong tiếng Ba Lan).
manyKhi ngôn ngữ yêu cầu xử lý đặc biệt các số "lớn" (như các số kết thúc từ 11-99 trong tiếng Malta).
otherKhi ngôn ngữ không yêu cầu xử lý đặc biệt về số lượng đã cho (như với tất cả các số trong tiếng Trung, hoặc số 42 trong tiếng Anh).
ví dụ:

Tệp XML được lưu vào res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals name="numberOfSongsAvailable">
        <!--
             As a developer, you should always supply "one" and "other"
             strings. Your translators will know which strings are actually
             needed for their language. Always include %d in "one" because
             translators will need to use %d for languages where "one"
             doesn't mean 1 (as explained above).
          -->
        <item quantity="one">%d song found.</item>
        <item quantity="other">%d songs found.</item>
    </plurals>
</resources>

Tệp XML được lưu vào res/values-pl/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals name="numberOfSongsAvailable">
        <item quantity="one">Znaleziono %d piosenkę.</item>
        <item quantity="few">Znaleziono %d piosenki.</item>
        <item quantity="other">Znaleziono %d piosenek.</item>
    </plurals>
</resources>

Mã xử lý ứng dụng này truy xuất một chuỗi số nhiều từ bên trong một thành phần kết hợp bằng pluralStringResource():

@Composable
fun SongCount(count: Int) {
    Text(
        text = pluralStringResource(
            R.plurals.numberOfSongsAvailable,
            count,
            count,
        )
    )
}

Khi sử dụng hàm pluralStringResource(), bạn cần phải truyền count hai lần nếu chuỗi của bạn mang định dạng chuỗi có số. Chẳng hạn như đối với chuỗi %d songs found, tham số count đầu tiên sẽ chọn chuỗi số nhiều phù hợp, và tham số count thứ hai được chèn vào phần giữ chỗ %d. Nếu các chuỗi số nhiều không chứa định dạng chuỗi thì bạn không cần truyền tham số thứ ba tới pluralStringResource.

Lưu ý: Để truy xuất một chuỗi số nhiều bên ngoài một hàm composable, hãy sử dụng context.resources.getQuantityString(R.plurals.numberOfSongsAvailable, count, count).

Định dạng và kiểu

Dưới đây là một số điều quan trọng mà bạn nên biết về cách định dạng và định kiểu đúng cách cho tài nguyên chuỗi.

Xử lý các ký tự đặc biệt

Khi một chuỗi chứa các ký tự có cách sử dụng đặc biệt trong XML, bạn phải thoát các ký tự theo quy tắc thoát XML/HTML chuẩn. Nếu cần thoát một ký tự có ý nghĩa đặc biệt trong Android, bạn nên sử dụng dấu gạch chéo ngược phía trước.

Theo mặc định, Android sẽ thu gọn các chuỗi ký tự có khoảng trắng thành một dấu cách. Bạn có thể tránh trường hợp này bằng cách đặt phần liên quan của chuỗi trong dấu ngoặc kép. Trong trường hợp này, mọi ký tự có khoảng trắng (kể cả các dòng mới) sẽ được giữ nguyên trong vùng đã được đóng ngoặc. Dấu ngoặc kép cũng sẽ cho phép bạn sử dụng dấu nháy đơn lẻ thông thường.

Nhân vật (Các) Biểu mẫu thoát
@ \@
? \?
Dòng mới \n
Thẻ \t
Ký tự Unicode U+XXXX \uXXXX
Dấu nháy đơn (')

Bất kỳ trường hợp nào sau đây:

  • \'
  • Đính kèm toàn bộ chuỗi trong dấu ngoặc kép (Chẳng hạn như "This'll work")
Dấu ngoặc kép (") \"

Lưu ý là bạn không thể bao quanh chuỗi bằng dấu nháy đơn.

Việc thu gọn khoảng trắng và ký tự thoát trên Android sẽ xảy ra sau khi tệp tài nguyên của bạn được phân tích cú pháp dưới dạng XML. Tức là <string> &#32; &#8200; &#8195;</string> (dấu cách, dấu chấm câu, dấu cách Unicode Em) đều thu gọn thành một dấu cách duy nhất (" "), vì tất cả đều là dấu cách Unicode sau khi tệp được phân tích cú pháp dưới dạng XML. Để giữ nguyên các dấu cách đó, bạn có thể trích dẫn các dấu cách đó (<string>" &#32; &#8200; &#8195;"</string>) hoặc sử dụng ký tự thoát của Android (<string> \u0032 \u8200 \u8195</string>).

Định dạng chuỗi

Nếu cần định dạng chuỗi, bạn có thể thực hiện bằng cách đặt các đối số định dạng vào tài nguyên chuỗi, như được minh hoạ bằng tài nguyên ví dụ sau.

<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

Mã xử lý ứng dụng này định dạng chuỗi từ bên trong một thành phần kết hợp bằng cách truyền trực tiếp các đối số vào stringResource():

@Composable
fun WelcomeMessage(username: String, mailCount: Int) {
    Text(
        text = stringResource(
            R.string.welcome_messages,
            username,
            mailCount,
        )
    )
}

Tạo kiểu bằng mã đánh dấu HTML

Bạn có thể thêm kiểu cho chuỗi bằng mã đánh dấu HTML. Ví dụ:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="welcome">Welcome to <b>Android</b>!</string>
</resources>

Các phần tử HTML sau được hỗ trợ:

  • In đậm: <b>
  • In nghiêng: <i>, <cite>, <dfn>, <em>
  • Văn bản lớn hơn 25%: <big>
  • Văn bản nhỏ hơn 20%: <small>
  • Đặt thuộc tính phông chữ: <font face="font_family" color="hex_color">. Ví dụ về các bộ phông chữ có thể có: monospace, serifsans_serif.
  • Đặt một bộ phông chữ đơn cách: <tt>
  • Gạch ngang chữ: <s>, <strike>, <del>
  • Gạch dưới: <u>
  • Chỉ số trên: <sup>
  • Chỉ số dưới: <sub>
  • Dấu đầu dòng: <ul>, <li>
  • Ngắt dòng: <br>
  • Phân chia: <div>
  • Kiểu CSS: <span style="color|background_color|text-decoration">
  • Đoạn văn: <p dir="rtl | ltr" style="…">

Trong một số trường hợp, có thể bạn muốn tạo một tài nguyên văn bản được định kiểu cũng được dùng làm chuỗi định dạng. Thường thì cách này không hiệu quả vì các phương thức định dạng, chẳng hạn như stringResource(), sẽ tách mọi thông tin về kiểu khỏi chuỗi. Giải pháp cho trường hợp này là viết các thẻ HTML chứa các thực thể thoát, sau đó được khôi phục bằng AnnotatedString.fromHtml() sau khi định dạng. Ví dụ:

  1. Lưu trữ tài nguyên văn bản đã được tạo kiểu dưới dạng chuỗi thoát HTML:
    <resources>
      <string name="welcome_messages">Hello, %1$s! You have &lt;b>%2$d new messages&lt;/b>.</string>
    </resources>

    Trong chuỗi được định dạng này, phần tử <b> sẽ được thêm. Lưu ý dấu ngoặc mở là ký tự thoát HTML, sử dụng ký hiệu &lt;.

  2. Sau đó, định dạng chuỗi như bình thường, nhưng cũng gọi AnnotatedString.fromHtml() để chuyển đổi văn bản HTML thành một chuỗi Compose được tạo kiểu.

fromHtml() định dạng mọi thực thể HTML, hãy nhớ thoát mọi ký tự HTML có thể có trong chuỗi mà bạn sử dụng bằng văn bản đã định dạng, bằng cách sử dụng TextUtils.htmlEncode().

import android.text.TextUtils
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.fromHtml

@Composable
fun WelcomeHtmlMessage(username: String, mailCount: Int) {
    // Escape the username in case it contains characters like "<" or "&"
    val escapedUsername = TextUtils.htmlEncode(username)

    val text = stringResource(
        R.string.welcome_messages,
        escapedUsername,
        mailCount,
    )

    Text(
        text = AnnotatedString.fromHtml(text)
    )
}

Tạo kiểu bằng AnnotatedString

AnnotatedString là một đối tượng văn bản Compose mà bạn có thể tạo kiểu bằng các thuộc tính như màu sắc và độ đậm của phông chữ. Tạo văn bản có kiểu theo phương thức lập trình bằng cách sử dụng buildAnnotatedStringwithStyle.

Mã xử lý ứng dụng này tạo một phần tử văn bản duy nhất có nhiều kiểu kết hợp:

@Composable
fun StyledGreeting() {
    val styled = buildAnnotatedString {
        append("Welcome to ")
        withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
            append("Android")
        }
        append("!")
    }
    Text(text = styled)
}

Để áp dụng màu sắc, cỡ chữ và hiệu ứng trang trí văn bản, hãy sử dụng SpanStyle. Để áp dụng kiểu ở cấp đoạn văn (chẳng hạn như căn chỉnh hoặc chiều cao dòng), hãy sử dụng ParagraphStyle:

@Composable
fun RichText() {
    val text = buildAnnotatedString {
        withStyle(ParagraphStyle(lineHeight = 24.sp, textAlign = TextAlign.Center)) {
            withStyle(SpanStyle(color = Color.Gray)) {
                append("Hello, ")
            }
            withStyle(
                SpanStyle(
                    fontWeight = FontWeight.Bold,
                    color = Color.Red,
                )
            ) {
                append("world")
            }
            append("!")
        }
    }
    Text(text = text)
}

Cách tốt nhất là tạo AnnotatedString trực tiếp cho các ứng dụng một ngôn ngữ hoặc văn bản tĩnh trong Compose. Tuy nhiên, đối với văn bản có kiểu cần bản địa hoá, hãy xem phương pháp <annotation> XML được trình bày chi tiết trong phần tiếp theo.

Tạo kiểu cho chuỗi đã dịch bằng chú thích

Đối với những chuỗi cần có kiểu tuỳ chỉnh bản dịch, hãy xác định thẻ <annotation> trong strings.xml của từng ngôn ngữ. Người dịch giữ nguyên chú thích bất kể chú thích xuất hiện ở đâu trong câu. Đọc chuỗi bằng context.resources.getText(), đi qua các khoảng Annotation và chuyển đổi kết quả thành AnnotatedString:

@Composable
fun AnnotatedTitle() {
    val context = LocalContext.current
    val source = context.resources.getText(R.string.title) as SpannedString
    val text = buildAnnotatedString {
        append(source.toString())
        source.getSpans(0, source.length, Annotation::class.java)
            .forEach { annotation ->
                if (annotation.key == "font" &&
                    annotation.value == "title_emphasis") {
                    addStyle(
                        SpanStyle(
                            fontFamily = FontFamily(
                                Font(R.font.permanent_marker)
                            )
                        ),
                        source.getSpanStart(annotation),
                        source.getSpanEnd(annotation),
                    )
                }
            }
    }
    Text(text = text)
}

Thẻ <annotation> trong XML của bạn không thay đổi. Chỉ có mã truy xuất là khác nhau. Người dịch vẫn di chuyển thẻ để bao bọc từ chính xác trong mỗi ngôn ngữ.

Tài nguyên khác

Để biết thêm thông tin về tài nguyên chuỗi, hãy xem các tài nguyên bổ sung sau đây:

Tài liệu

Xem nội dung