Référence une chaîne d'une autre chaîne dans strings.xml?

230

Je voudrais référencer une chaîne d'une autre chaîne dans mon fichier strings.xml, comme ci-dessous (notez spécifiquement la fin du contenu de la chaîne "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>

J'ai essayé la syntaxe ci-dessus mais le texte imprime le "@ string / button_text" en texte clair. Pas ce que je veux. Je voudrais que le texte du message s'imprime "Vous n'avez pas encore d'articles! Ajoutez-en un en appuyant sur le bouton" Ajouter un article "."

Existe-t-il un moyen connu d'atteindre ce que je veux?

JUSTIFICATION:
Mon application a une liste d'éléments, mais lorsque cette liste est vide, j'affiche à la place une TextView "@android: id / empty". Le texte de ce TextView est destiné à informer l'utilisateur sur la façon d'ajouter un nouvel élément. Je voudrais rendre ma mise en page infaillible aux changements (oui, je suis le fou en question :-)

dbm
la source
3
Cette réponse à une autre question similaire a fonctionné pour moi. Aucun java n'est nécessaire, mais il ne fonctionne que dans le même fichier de ressources.
mpkuth

Réponses:

253

Une bonne façon d'insérer une chaîne fréquemment utilisée (par exemple le nom de l'application) dans xml sans utiliser de code 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>

METTRE À JOUR:

Vous pouvez même définir votre entité globalement, par exemple:

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
la source
9
c'est la bonne réponse à l'OP. Cette solution présente d'autres avantages importants: (1) vous pouvez définir la DTD dans un fichier externe et la référencer à partir de n'importe quel fichier de ressources pour appliquer la substitution partout dans vos ressources. (2) Android Studio vous permet de renommer / références / refactoriser les entités définies. Cependant, il ne complète pas automatiquement les noms lors de l'écriture. (androidstudio 2.2.2)
Mauro Panzeri
3
@RJFares Vous pouvez aller de 2 façons: (a) "incluant" un fichier d'entité entier EG: regardez cette réponse . OU (b) : y compris chaque entité unique définie dans une DTD externe comme indiqué ici . Notez que ni l'un ni l'autre n'est parfait car avec (a) vous perdrez les capacités de "refactoring" et (b) est très verbeux
Mauro Panzeri
5
Attention si vous l'utilisez dans un projet de bibliothèque Android - vous ne pouvez pas le remplacer dans votre application.
zyamys
4
est-il possible de changer la valeur de l'entité lors de la définition des saveurs dans le fichier gradle?
Myoch
7
Les entités externes ne sont plus prises en charge par Android Studio, car un bug discuté ici: stackoverflow.com/a/51330953/8154765
Davide Cannizzo
181

Il est possible de référencer l'un dans l'autre tant que votre chaîne entière est constituée du nom de référence. Par exemple, cela fonctionnera:

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

Il est encore plus utile pour définir des valeurs par défaut:

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

Maintenant, vous pouvez utiliser string_defaultpartout dans votre code et vous pouvez facilement changer la valeur par défaut à tout moment.

Barry Fruitman
la source
Ce qui répond également à la question: comment faire référence à la chaîne app_name dans un titre d'activité. :)
Stephen Hosking
53
Cela ne doit pas lire "tant que vous référencez la chaîne entière" (ce que vous devez toujours par définition) mais "tant que la ressource de chaîne de référence ne comprend que le nom de référence".
sschuberth
54
Ce n'est pas vraiment une référence, c'est juste un alias, qui ne résout pas le problème de la composition de chaînes.
Eric Woodruff
2
Cela ne répond pas à la question, ils voulaient une chaîne intégrée dans une autre.
intrepidis
1
FWIW, cela m'a été extrêmement utile. Je vous remercie.
Ellen Spertus
95

Je pense que tu ne peux pas. Mais vous pouvez "formater" une chaîne comme vous le souhaitez:

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

Dans le code:

Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));
Francesco Laurita
la source
5
J'espérais en fait une solution "non logique". Néanmoins, une réponse assez juste (et sa rapidité vous accorde une coche verte :-)
dbm
34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));est un peu plus propre.
Andrey Novikov
34

Dans Android, vous ne pouvez pas concaténer des chaînes dans XML

Ce qui suit n'est pas pris en charge

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

Consultez ce lien ci-dessous pour savoir comment y parvenir

Comment concaténer plusieurs chaînes en XML Android?

Mayank Mehta
la source
16
Eh bien, histoire drôle: c'est ma réponse à une question similaire à laquelle vous faites référence :-)
dbm
Existe-t-il un moyen d'y parvenir?
Il s'agit d'un doublon de la réponse de @Francesco Laurita, comment remplacer par programme une chaîne.
CoolMind
14

J'ai créé un plugin Gradle simple qui vous permet de référencer une chaîne d'une autre. Vous pouvez référencer des chaînes qui sont définies dans un autre fichier, par exemple dans une variante de construction ou une bibliothèque différente. Inconvénients de cette approche - Refactor IDE ne trouvera pas de telles références.

Utilisez la {{string_name}}syntaxe pour référencer une chaîne:

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

Pour intégrer le plugin, ajoutez simplement le code suivant dans votre build.gradlefichier de niveau module d'application ou de bibliothèque

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"

MISE À JOUR: La bibliothèque ne fonctionne pas avec le plug-in Android Gradle version 3.0 et supérieure, car la nouvelle version du plug-in utilise aapt2 qui regroupe les ressources au format binaire .flat, de sorte que les ressources compressées ne sont pas disponibles pour la bibliothèque. En tant que solution temporaire, vous pouvez désactiver aapt2 en définissant android.enableAapt2 = false dans votre fichier gradle.properties.

Valeriy Katkov
la source
J'aime cette idée .. mais elle échoue pour moi avec cette erreur:> Could not get unknown property 'referencedString'
jpage4500
Cela rompt avec aapt2
Snicolas
2
J'ai créé un plugin qui fait à peu près la même chose et fonctionne avec aapt2: github.com/LikeTheSalad/android-string-reference :)
César Muñoz
7

Vous pouvez utiliser votre propre logique, qui résout récursivement les chaînes imbriquées.

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

D'un Activity, par exemple, appelez-le comme ça

replaceResourceStrings(this, getString(R.string.message_text));
schnatterer
la source
2

Je suis conscient que c'est un article plus ancien, mais je voulais partager la solution rapide et sale que j'ai trouvée pour un de mes projets. Il ne fonctionne que pour TextViews mais pourrait également être adapté à d'autres widgets. Notez qu'il nécessite que le lien soit placé entre crochets (par exemple [@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);
    }
}

L'inconvénient de cette approche est qu'elle setText()convertit le CharSequenceen un String, ce qui est un problème si vous passez des choses comme un SpannableString; pour mon projet, ce n'était pas un problème car je ne l'ai utilisé que pour TextViewscela, je n'avais pas besoin d'y accéder depuis mon Activity.

jclehner
la source
C'est la chose la plus proche d'une réponse à cette question. Je pense que nous devons chercher à accrocher l'analyse syntaxique au format XML.
Eric Woodruff
2

Avec la nouvelle liaison de données, vous pouvez concaténer et faire beaucoup plus dans votre xml.

par exemple, si vous avez reçu message1 et message2, vous pouvez:

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

vous pouvez même importer des utilitaires de texte et appeler String.format et vos amis.

malheureusement, si vous voulez le réutiliser à plusieurs endroits, il peut devenir désordonné, vous ne voulez pas ce morceau de code partout. et vous ne pouvez pas les définir en xml en un seul endroit (pas que je sache) donc pour cela, vous pouvez créer une classe qui encapsulera ces compositions:

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

alors vous pouvez l'utiliser à la place (vous devrez importer la classe avec la liaison de données)

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

En plus de la réponse ci-dessus de Francesco Laurita https://stackoverflow.com/a/39870268/9400836

Il semble qu'il y ait une erreur de compilation "& entity; a été référencé, mais non déclaré" qui peut être résolu en référençant la déclaration externe comme ceci

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

Bien qu'il compile et s'exécute, il a une valeur vide. Peut-être que quelqu'un sait comment résoudre ce problème. J'aurais posté un commentaire mais la réputation minimum est de 50.

pumnao
la source
1
Vous en avez maintenant 53.
CoolMind
1

Vous pouvez utiliser des espaces réservés de chaîne (% s) et les remplacer à l'aide de java au moment de l'exécution

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

et en java

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

puis placez-le à l'endroit où il utilise la chaîne

Ismail Iqbal
la source
Vous copiez d'autres solutions. Lorsque référence plusieurs chaînes, utilisez %1$s, %2$setc. au lieu de %s.
CoolMind
@CoolMind pouvez-vous mentionner la référence à la copie.
Ismail Iqbal
1

J'ai créé une petite bibliothèque qui vous permet de résoudre ces espaces réservés au moment de la construction, vous n'aurez donc pas à ajouter de code Java / Kotlin pour obtenir ce que vous voulez.

Sur la base de votre exemple, vous devez configurer vos chaînes comme ceci:

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

Et puis le plugin se chargera de générer ce qui suit:

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

Cela fonctionne également pour les chaînes localisées et de saveur, les chaînes générées se tiendront également à jour chaque fois que vous apportez des modifications à vos modèles ou à ses valeurs.

Plus d'informations ici: https://github.com/LikeTheSalad/android-string-reference

César Muñoz
la source
J'ai essayé ta bibliothèque. Après avoir suivi les étapes que vous avez données. Il continue de donner des erreurs de construction. Votre bibliothèque ne fonctionne pas du tout
developer1011
Je vois, désolé d'entendre ça. Si vous le souhaitez, veuillez créer un problème ici: github.com/LikeTheSalad/android-string-reference/issues avec les journaux et je peux y remédier dès que c'est un problème de fonctionnalité, ou s'il s'agit d'un problème de configuration, je peux également aider Toi là. 👍
César Muñoz