Referenzieren Sie eine Zeichenfolge aus einer anderen Zeichenfolge in strings.xml?

230

Ich möchte wie unten auf eine Zeichenfolge aus einer anderen Zeichenfolge in meiner Datei strings.xml verweisen (beachten Sie insbesondere das Ende des Zeichenfolgeninhalts "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>

Ich habe die obige Syntax ausprobiert, aber dann druckt der Text den "@ string / button_text" als Klartext aus. Nicht was ich will. Ich möchte, dass der Nachrichtentext gedruckt wird: "Sie haben noch keine Artikel! Fügen Sie einen hinzu, indem Sie auf die Schaltfläche" Artikel hinzufügen "klicken."

Gibt es einen bekannten Weg, um das zu erreichen, was ich will?

RATIONALE:
Meine Anwendung enthält eine Liste mit Elementen. Wenn diese Liste jedoch leer ist, wird stattdessen eine Textansicht "@android: id / empty" angezeigt. Der Text in dieser Textansicht soll den Benutzer darüber informieren, wie ein neues Element hinzugefügt wird. Ich möchte mein Layout für Änderungen narrensicher machen (ja, ich bin der fragliche Dummkopf :-)

dbm
quelle
3
Diese Antwort auf eine andere ähnliche Frage hat bei mir funktioniert. Kein Java erforderlich, aber es funktioniert nur in derselben Ressourcendatei.
mpkuth

Antworten:

252

Eine gute Möglichkeit, eine häufig verwendete Zeichenfolge (z. B. den App-Namen) in XML einzufügen, ohne Java-Code zu verwenden: 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>

AKTUALISIEREN:

Sie können Ihre Entität sogar global definieren, z.

res / raw / entity.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
quelle
9
Dies ist die richtige Antwort auf das OP. Diese Lösung bietet weitere große Vorteile: (1) Sie können die DTD in einer externen Datei definieren und aus einer beliebigen Ressourcendatei darauf verweisen, um die Ersetzung überall in Ihren Ressourcen anzuwenden. (2) Mit Android Studio können Sie die definierten Entitäten umbenennen / referenzieren / umgestalten. Beim Schreiben werden Namen jedoch nicht automatisch vervollständigt. (androidstudio 2.2.2)
Mauro Panzeri
3
@RJFares Sie können zwei Wege gehen: (a) "Einschließen" einer ganzen Entitätsdatei EG: Sehen Sie sich diese Antwort an . ODER (b) : einschließlich jeder einzelnen Entität, die in einer externen DTD definiert ist, wie hier gezeigt . Beachten Sie, dass keines von beiden perfekt ist, da Sie mit (a) die "Refactoring" -Fähigkeiten verlieren und (b) sehr ausführlich ist
Mauro Panzeri
5
Vorsicht, wenn Sie dies in einem Android-Bibliotheksprojekt verwenden - Sie können es in Ihrer App nicht überschreiben.
Zyamys
4
Ist es möglich, den Entitätswert beim Definieren von Aromen in der Gradle-Datei zu ändern?
Myoch
7
Externe Entitäten werden von Android Studio nicht mehr unterstützt, da hier ein Fehler besprochen wurde: stackoverflow.com/a/51330953/8154765
Davide Cannizzo
181

Es ist möglich, ineinander zu referenzieren, solange Ihre gesamte Zeichenfolge aus dem Referenznamen besteht. Zum Beispiel wird dies funktionieren:

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

Dies ist noch nützlicher zum Festlegen von Standardwerten:

<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>

Jetzt können Sie string_defaultüberall in Ihrem Code verwenden und die Standardeinstellung jederzeit problemlos ändern.

Barry Fruitman
quelle
Was auch die Frage beantwortet: Wie verweise ich auf die Zeichenfolge app_name in einem Aktivitätstitel? :)
Stephen Hosking
53
Dies sollte nicht "solange Sie auf die gesamte Zeichenfolge verweisen" (auf die Sie per Definition immer verweisen) lauten, sondern "solange die verweisende Zeichenfolgenressource nur aus dem Referenznamen besteht".
Sschuberth
54
Dies ist nicht wirklich eine Referenz, sondern nur ein Alias, der das Problem des Zusammenstellens von Zeichenfolgen nicht löst.
Eric Woodruff
2
Dies beantwortet die Frage nicht, sie wollten, dass eine Zeichenfolge in eine andere eingebettet wird.
intrepidis
1
FWIW, das war sehr nützlich für mich. Danke dir.
Ellen Spertus
95

Ich denke du kannst nicht. Sie können eine Zeichenfolge jedoch nach Ihren Wünschen "formatieren":

<?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>

Im Code:

Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));
Francesco Laurita
quelle
5
Ich hatte tatsächlich auf eine "nicht logische" Lösung gehofft. Trotzdem eine faire Antwort (und die bloße Geschwindigkeit gibt Ihnen ein grünes Häkchen :-)
dbm
34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));ist ein bisschen sauberer.
Andrey Novikov
34

In Android können Sie Strings nicht in XML verketten

Folgendes wird nicht unterstützt

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

Überprüfen Sie diesen Link unten, um zu erfahren, wie Sie dies erreichen können

Wie verkette ich mehrere Strings in Android XML?

Mayank Mehta
quelle
16
Nun, lustige Geschichte: das ist meine Antwort auf eine ähnliche Frage, auf die Sie sich beziehen :-)
dbm
Gibt es einen Weg, dies zu erreichen?
Es ist ein Duplikat der Antwort von @Francesco Laurita, wie man einen String programmgesteuert ersetzt.
CoolMind
14

Ich habe ein einfaches Gradle- Plugin erstellt , mit dem Sie eine Zeichenfolge von einer anderen referenzieren können. Sie können auf Zeichenfolgen verweisen, die in einer anderen Datei definiert sind, z. B. in einer anderen Build-Variante oder Bibliothek. Nachteile dieses Ansatzes - IDE Refactor findet solche Referenzen nicht.

Verwenden Sie die {{string_name}}Syntax, um auf eine Zeichenfolge zu verweisen:

<?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>

Um das Plugin zu integrieren, fügen Sie einfach den nächsten Code in die build.gradleDatei auf App- oder Bibliotheksmodulebene ein

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: Die Bibliothek funktioniert nicht mit dem Android Gradle Plugin Version 3.0 und höher, da die neue Version des Plugins aapt2 verwendet, das Ressourcen in das flache Binärformat packt, sodass gepackte Ressourcen für die Bibliothek nicht verfügbar sind. Als vorübergehende Lösung können Sie aapt2 deaktivieren, indem Sie android.enableAapt2 = false in Ihrer Datei gradle.properties festlegen.

Valeriy Katkov
quelle
Ich mag diese Idee .. aber es schlägt für mich mit diesem Fehler fehl:> Could not get unknown property 'referencedString'
jpage4500
Dies bricht mit aapt2
Snicolas
2
Ich habe ein Plugin erstellt, das fast dasselbe tut und mit aapt2 funktioniert: github.com/LikeTheSalad/android-string-reference :)
César Muñoz
7

Sie können Ihre eigene Logik verwenden, die die verschachtelten Zeichenfolgen rekursiv auflöst.

/**
 * 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());
}

Von einem Activityzum Beispiel nennt es gerne so

replaceResourceStrings(this, getString(R.string.message_text));
Schnatterer
quelle
2

Ich bin mir bewusst, dass dies ein älterer Beitrag ist, aber ich wollte die schnelle und schmutzige Lösung teilen, die ich für ein Projekt von mir gefunden habe. Es funktioniert nur für TextViews, kann aber auch an andere Widgets angepasst werden. Beachten Sie, dass der Link in eckigen Klammern stehen muss (z [@string/foo]. B. ).

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);
    }
}

Der Nachteil dieses Ansatzes ist, dass setText()das CharSequencein a konvertiert wird. Dies Stringist ein Problem, wenn Sie Dinge wie a übergeben SpannableString. Für mein Projekt war dies kein Problem, da ich es nur dafür verwendet habe, TextViewsdass ich nicht von meinem aus darauf zugreifen musste Activity.

jclehner
quelle
Dies kommt einer Antwort auf diese Frage am nächsten. Ich denke, wir müssen uns mit dem Parsing von Layout-XML befassen.
Eric Woodruff
2

Mit der neuen Datenbindung können Sie Ihre XML-Datei verketten und noch viel mehr tun.

Wenn Sie beispielsweise Nachricht1 und Nachricht2 erhalten haben, können Sie:

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

Sie können sogar einige Text-Utils importieren und String.format und Freunde aufrufen.

Wenn Sie es an mehreren Stellen wiederverwenden möchten, kann es leider unordentlich werden. Sie möchten diese Codeteile nicht überall haben. und Sie können sie nicht in XML an einer Stelle definieren (nicht die ich kenne), damit Sie eine Klasse erstellen können, die diese Kompositionen einkapselt:

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

dann können Sie es stattdessen verwenden (Sie müssen die Klasse mit Datenbindung importieren)

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

Zusätzlich zu der obigen Antwort von Francesco Laurita https://stackoverflow.com/a/39870268/9400836

Es scheint, dass ein Kompilierungsfehler "& entity; wurde referenziert, aber nicht deklariert" vorliegt, der durch Verweisen auf die externe Deklaration wie folgt behoben werden kann

res / raw / entity.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

Obwohl es kompiliert und ausgeführt wird, hat es einen leeren Wert. Vielleicht weiß jemand, wie man das löst. Ich hätte einen Kommentar gepostet, aber die Mindestreputation beträgt 50.

pumnao
quelle
1
Jetzt haben Sie 53.
CoolMind
1

Sie können String-Platzhalter (% s) verwenden und diese zur Laufzeit mit Java ersetzen

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

und in Java

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

und setzen Sie es dann an die Stelle, an der die Zeichenfolge verwendet wird

Ismail Iqbal
quelle
Sie kopieren andere Lösungen. Wenn Referenz mehrere Strings, Verwendung %1$s, %2$setc. statt %s.
CoolMind
@CoolMind können Sie den Verweis auf die Kopie erwähnen.
Ismail Iqbal
1

Ich habe eine kleine Bibliothek erstellt, mit der Sie diese Platzhalter zur Erstellungszeit auflösen können, sodass Sie keinen Java / Kotlin-Code hinzufügen müssen, um das zu erreichen, was Sie möchten.

Anhand Ihres Beispiels müssten Sie Ihre Zeichenfolgen wie folgt einrichten:

<?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>

Und dann kümmert sich das Plugin um Folgendes:

<?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>

Es funktioniert auch für lokalisierte Zeichenfolgen und Flavour-Zeichenfolgen. Außerdem werden die generierten Zeichenfolgen immer aktualisiert, wenn Sie Änderungen an Ihren Vorlagen oder deren Werten vornehmen.

Weitere Informationen hier: https://github.com/LikeTheSalad/android-string-reference

César Muñoz
quelle
Ich habe deine Bibliothek ausprobiert. Nachdem Sie die von Ihnen angegebenen Schritte ausgeführt haben. Es gibt immer wieder Build-Fehler. Ihre Bibliothek funktioniert überhaupt nicht
developer1011
Es tut mir leid, das zu hören. Wenn Sie möchten, erstellen Sie bitte hier ein Problem: github.com/LikeTheSalad/android-string-reference/issues mit den Protokollen. Ich kann es so schnell wie möglich beheben, falls es sich um ein Funktionsproblem handelt oder wenn es sich um ein Konfigurationsproblem handelt, kann ich auch helfen Du da drüben. 👍
César Muñoz