Referensi satu string dari string lain di strings.xml?

230

Saya ingin merujuk string dari string lain dalam file strings.xml saya, seperti di bawah ini (khusus perhatikan akhir dari isi string "message_text"):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="message_text">You don't have any items yet! Add one by pressing the \'@string/button_text\' button.</string>
</resources>

Saya sudah mencoba sintaks di atas tetapi kemudian teks mencetak "@ string / button_text" sebagai teks yang jelas. Bukan yang saya inginkan. Saya ingin teks pesan dicetak "Anda belum memiliki item! Tambahkan satu dengan menekan tombol 'Tambahkan item'."

Adakah cara yang diketahui untuk mencapai apa yang saya inginkan?

RATIONALE:
Aplikasi saya memiliki daftar item, tetapi ketika daftar itu kosong saya menunjukkan TextView "@android: id / empty". Teks dalam TextView itu untuk memberi tahu pengguna cara menambahkan item baru. Saya ingin membuat tata letak saya menjadi bukti perubahan (ya, saya yang bodoh dalam pertanyaan :-)

dbm
sumber
3
Jawaban untuk pertanyaan serupa ini berhasil untuk saya. Tidak perlu java, tetapi hanya berfungsi dalam file sumber daya yang sama.
mpkuth

Jawaban:

253

Cara yang bagus untuk menyisipkan string yang sering digunakan (misalnya nama aplikasi) dalam xml tanpa menggunakan kode Java: source

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE resources [
      <!ENTITY appname "MyAppName">
      <!ENTITY author "MrGreen">
    ]>

<resources>
    <string name="app_name">&appname;</string>
    <string name="description">The &appname; app was created by &author;</string>
</resources>

MEMPERBARUI:

Anda bahkan dapat mendefinisikan entitas Anda secara global misalnya:

res / raw / entitas.ent:

<!ENTITY appname "MyAppName">
<!ENTITY author "MrGreen">

res / values ​​/ string.xml:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
    <!ENTITY % ents SYSTEM "./res/raw/entities.ent">
    %ents;   
]>

<resources>
    <string name="app_name">&appname;</string>
    <string name="description">The &appname; app was created by &author;</string>
</resources>
Beeing Jk
sumber
9
ini adalah jawaban yang benar untuk OP. Solusi ini memiliki manfaat besar lainnya: (1) Anda dapat menentukan DTD dalam file eksternal dan merujuknya dari file sumber daya apa pun untuk menerapkan substitusi di mana pun di sumber daya Anda. (2) studio android memungkinkan Anda mengubah nama / referensi / refactor entitas yang ditentukan. Padahal, itu tidak melengkapi nama secara otomatis saat menulis. (androidstudio 2.2.2)
Mauro Panzeri
3
@RJFares Anda dapat menggunakan 2 cara: (a) "termasuk" file entitas keseluruhan EG: lihat jawaban ini . ATAU (b) : termasuk setiap entitas tunggal yang didefinisikan dalam DTD eksternal seperti yang ditunjukkan di sini . Perhatikan bahwa tidak ada yang sempurna karena dengan (a) Anda akan kehilangan kabel "refactoring" dan (b) sangat verbose
Mauro Panzeri
5
Hati-hati jika Anda menggunakan ini di Proyek Perpustakaan Android - Anda tidak dapat menimpanya di aplikasi Anda.
zyamys
4
apakah mungkin untuk mengubah nilai entitas saat mendefinisikan rasa dalam file gradle?
Myoch
7
Entitas eksternal tidak lagi didukung oleh Android Studio, karena bug yang dibahas di sini: stackoverflow.com/a/51330953/8154765
Davide Cannizzo
181

Dimungkinkan untuk merujuk satu di dalam yang lain selama seluruh string Anda terdiri dari nama referensi. Misalnya ini akan berfungsi:

<string name="app_name">My App</string>
<string name="activity_title">@string/app_name</string>
<string name="message_title">@string/app_name</string>

Ini bahkan lebih berguna untuk menetapkan nilai default:

<string name="string1">String 1</string>
<string name="string2">String 2</string>
<string name="string3">String 3</string>
<string name="string_default">@string/string1</string>

Sekarang Anda dapat menggunakan string_defaultdi mana saja dalam kode Anda dan Anda dapat dengan mudah mengubah default kapan saja.

Barry Fruitman
sumber
Yang juga menjawab pertanyaan: bagaimana cara merujuk ke string app_name dalam judul aktivitas. :)
Stephen Hosking
53
Ini seharusnya tidak membaca "selama Anda mereferensikan seluruh string" (yang menurut definisi Anda selalu) tetapi "selama sumber string yang merujuk hanya terdiri dari nama referensi".
sschuberth
54
Ini bukan benar-benar referensi, ini hanya alias, yang tidak menyelesaikan masalah penyusunan string.
Eric Woodruff
2
Ini tidak menjawab pertanyaan, mereka ingin satu string tertanam di yang lain.
intrepidis
1
FWIW, ini sangat berguna bagi saya. Terima kasih.
Ellen Spertus
95

Saya pikir kamu tidak bisa. Tetapi Anda dapat "memformat" string sesuka Anda:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="message_text">You don't have any items yet! Add one by pressing the %1$s button.</string>
</resources>

Dalam kode:

Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));
Francesco Laurita
sumber
5
Saya sebenarnya berharap untuk solusi "non-logika". Namun demikian, jawaban yang cukup adil (dan kecepatannya memberikan Anda tanda centang hijau :-)
dbm
34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));sedikit lebih bersih.
Andrey Novikov
34

Di Android Anda tidak dapat menggabungkan Strings di dalam xml

Mengikuti tidak didukung

<string name="string_default">@string/string1 TEST</string>

Periksa tautan ini di bawah untuk mengetahui cara mencapainya

Bagaimana cara menggabungkan beberapa string dalam XML android?

Mayank Mehta
sumber
16
Nah, cerita lucu: itulah jawaban saya untuk pertanyaan serupa yang Anda referensikan :-)
dbm
Apakah ada cara untuk mencapai ini?
Ini adalah duplikat dari jawaban @Francesco Laurita, cara mengganti string secara terprogram.
CoolMind
14

Saya membuat plugin gradle sederhana yang memungkinkan Anda untuk merujuk satu string dari yang lain. Anda bisa merujuk string yang didefinisikan dalam file lain, misalnya dalam varian build atau pustaka yang berbeda. Kontra dari pendekatan ini - refactor IDE tidak akan menemukan referensi seperti itu.

Gunakan {{string_name}}sintaks untuk merujuk string:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="super">Super</string>
    <string name="app_name">My {{super}} App</string>
    <string name="app_description">Name of my application is: {{app_name}}</string>
</resources>

Untuk mengintegrasikan plugin, cukup tambahkan kode berikutnya ke build.gradlefile level modul aplikasi atau pustaka Anda

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "gradle.plugin.android-text-resolver:buildSrc:1.2.0"
  }
}

apply plugin: "com.icesmith.androidtextresolver"

UPDATE: Perpustakaan tidak bekerja dengan Android gradle plugin versi 3.0 dan di atas karena versi baru dari plugin menggunakan aapt2 yang mengemas sumber daya ke dalam format biner .flat, sehingga sumber daya yang dikemas tidak tersedia untuk perpustakaan. Sebagai solusi sementara Anda dapat menonaktifkan aapt2 dengan mengatur android.enableAapt2 = false di file gradle.properties Anda.

Valeriy Katkov
sumber
Saya suka ide ini .. tetapi gagal bagi saya dengan kesalahan ini:> Could not get unknown property 'referencedString'
jpage4500
Ini pecah dengan aapt2
Snicolas
2
Saya telah membuat sebuah plugin yang fungsinya hampir sama dan berfungsi dengan aapt2: github.com/LikeTheSalad/android-string-reference :)
César Muñoz
7

Anda bisa menggunakan logika Anda sendiri, yang menyelesaikan string bersarang secara rekursif.

/**
 * Regex that matches a resource string such as <code>@string/a-b_c1</code>.
 */
private static final String REGEX_RESOURCE_STRING = "@string/([A-Za-z0-9-_]*)";

/** Name of the resource type "string" as in <code>@string/...</code> */
private static final String DEF_TYPE_STRING = "string";

/**
 * Recursively replaces resources such as <code>@string/abc</code> with
 * their localized values from the app's resource strings (e.g.
 * <code>strings.xml</code>) within a <code>source</code> string.
 * 
 * Also works recursively, that is, when a resource contains another
 * resource that contains another resource, etc.
 * 
 * @param source
 * @return <code>source</code> with replaced resources (if they exist)
 */
public static String replaceResourceStrings(Context context, String source) {
    // Recursively resolve strings
    Pattern p = Pattern.compile(REGEX_RESOURCE_STRING);
    Matcher m = p.matcher(source);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
        String stringFromResources = getStringByName(context, m.group(1));
        if (stringFromResources == null) {
            Log.w(Constants.LOG,
                    "No String resource found for ID \"" + m.group(1)
                            + "\" while inserting resources");
            /*
             * No need to try to load from defaults, android is trying that
             * for us. If we're here, the resource does not exist. Just
             * return its ID.
             */
            stringFromResources = m.group(1);
        }
        m.appendReplacement(sb, // Recurse
                replaceResourceStrings(context, stringFromResources));
    }
    m.appendTail(sb);
    return sb.toString();
}

/**
 * Returns the string value of a string resource (e.g. defined in
 * <code>values.xml</code>).
 * 
 * @param name
 * @return the value of the string resource or <code>null</code> if no
 *         resource found for id
 */
public static String getStringByName(Context context, String name) {
    int resourceId = getResourceId(context, DEF_TYPE_STRING, name);
    if (resourceId != 0) {
        return context.getString(resourceId);
    } else {
        return null;
    }
}

/**
 * Finds the numeric id of a string resource (e.g. defined in
 * <code>values.xml</code>).
 * 
 * @param defType
 *            Optional default resource type to find, if "type/" is not
 *            included in the name. Can be null to require an explicit type.
 * 
 * @param name
 *            the name of the desired resource
 * @return the associated resource identifier. Returns 0 if no such resource
 *         was found. (0 is not a valid resource ID.)
 */
private static int getResourceId(Context context, String defType,
        String name) {
    return context.getResources().getIdentifier(name, defType,
            context.getPackageName());
}

Dari Activity, misalnya, sebut saja seperti itu

replaceResourceStrings(this, getString(R.string.message_text));
pemalas
sumber
2

Saya sadar bahwa ini adalah posting yang lebih lama, tetapi saya ingin berbagi solusi cepat dan kotor yang saya buat untuk proyek saya. Ini hanya berfungsi untuk TextViews tetapi dapat disesuaikan dengan widget lainnya juga. Perhatikan bahwa ini membutuhkan tautan yang akan diapit tanda kurung siku (mis [@string/foo].).

public class RefResolvingTextView extends TextView
{
    // ...

    @Override
    public void setText(CharSequence text, BufferType type)
    {
        final StringBuilder sb = new StringBuilder(text);
        final String defPackage = getContext().getApplicationContext().
                getPackageName();

        int beg;

        while((beg = sb.indexOf("[@string/")) != -1)
        {
            int end = sb.indexOf("]", beg);
            String name = sb.substring(beg + 2, end);
            int resId = getResources().getIdentifier(name, null, defPackage);
            if(resId == 0)
            {
                throw new IllegalArgumentException(
                        "Failed to resolve link to @" + name);
            }

            sb.replace(beg, end + 1, getContext().getString(resId));
        }

        super.setText(sb, type);
    }
}

Kelemahan dari pendekatan ini adalah yang setText()mengubah CharSequenceke String, yang merupakan masalah jika Anda melewatkan hal-hal seperti SpannableString; untuk proyek saya ini bukan masalah karena saya hanya menggunakannya untuk TextViewsitu saya tidak perlu mengakses dari saya Activity.

jclehner
sumber
Ini adalah hal yang paling dekat dengan jawaban atas pertanyaan ini. Saya pikir kita perlu melihat ke dalam mengaitkan ke tata letak xml parsing.
Eric Woodruff
2

Dengan pengikatan data baru, Anda dapat menggabungkan dan melakukan lebih banyak lagi di xml Anda.

misalnya jika Anda mendapat message1 dan message2 Anda dapat:

android:text="@{@string/message1 + ': ' + @string/message2}"

Anda bahkan dapat mengimpor beberapa utilitas teks dan memanggil String.format dan teman-teman.

sayangnya jika Anda ingin menggunakannya kembali di beberapa tempat itu bisa berantakan, Anda tidak ingin potongan kode ini ada di mana-mana. dan Anda tidak dapat mendefinisikannya dalam xml di satu tempat (bukan yang saya ketahui) sehingga untuk itu Anda dapat membuat kelas yang akan merangkum komposisi tersebut:

public final class StringCompositions {
    public static final String completeMessage = getString(R.string.message1) + ": " + getString(R.string.message2);
}

maka Anda dapat menggunakannya sebagai gantinya (Anda harus mengimpor kelas dengan ikatan data)

android:text="@{StringCompositions.completeMessage}"
ndori
sumber
2

Selain jawaban di atas oleh Francesco Laurita https://stackoverflow.com/a/39870268/9400836

Tampaknya ada kesalahan kompilasi "& entitas; direferensikan, tetapi tidak dideklarasikan" yang dapat diselesaikan dengan merujuk pada deklarasi eksternal seperti ini

res / raw / entitas.ent

<!ENTITY appname "My App Name">

res / values ​​/ strings.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
    <!ENTITY appname SYSTEM "/raw/entities.ent">
]>
<resources>
    <string name="app_name">&appname;</string>
</resources

Meskipun mengkompilasi dan menjalankannya memiliki nilai kosong. Mungkin seseorang tahu bagaimana menyelesaikan ini. Saya akan mengirim komentar tetapi reputasi minimum adalah 50.

pumnao
sumber
1
Sekarang Anda memiliki 53.
CoolMind
1

Anda bisa menggunakan string placeholder (% s) dan menggantinya menggunakan java saat run-time

<resources>
<string name="button_text">Add item</string>
<string name="message_text">Custom text %s </string>
</resources>

dan di java

String final = String.format(getString(R.string.message_text),getString(R.string.button_text));

dan kemudian mengaturnya ke tempat di mana ia menggunakan string

Ismail Iqbal
sumber
Anda menyalin solusi lain. Ketika referensi beberapa string, penggunaan %1$s, %2$sdll bukan %s.
CoolMind
@CoolMind dapat Anda sebutkan referensi untuk salinan.
Ismail Iqbal
1

Saya telah membuat perpustakaan kecil yang memungkinkan Anda untuk menyelesaikan placeholder ini saat buildtime, jadi Anda tidak perlu menambahkan kode Java / Kotlin untuk mencapai apa yang Anda inginkan.

Berdasarkan contoh Anda, Anda harus mengatur string Anda seperti ini:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="template_message_text">You don't have any items yet! Add one by pressing the ${button_text} button.</string>
</resources>

Dan kemudian plugin akan mengurus menghasilkan hal-hal berikut:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="message_text">You don't have any items yet! Add one by pressing the Add item button.</string>
</resources>

Ini berfungsi juga untuk string terlokalisasi dan rasa, juga string yang dihasilkan akan menjaga diri mereka diperbarui setiap kali Anda membuat perubahan pada template Anda atau nilainya.

Info lebih lanjut di sini: https://github.com/LikeTheSalad/android-string-reference

César Muñoz
sumber
Saya mencoba perpustakaan Anda. Setelah mengikuti langkah-langkah yang telah Anda berikan. Itu terus memberi kesalahan build. Perpustakaan Anda tidak berfungsi sama sekali
developer1011
Begitu ya, maaf mendengarnya. Jika Anda suka, silakan buat masalah di sini: github.com/LikeTheSalad/android-string-reference/issues dengan log dan saya bisa mengatasinya dengan segera jika itu masalah fitur, atau jika itu masalah konfigurasi, saya juga bisa membantu kau disana. 👍
César Muñoz