¿Hacer referencia a una cadena de otra cadena en strings.xml?

230

Me gustaría hacer referencia a una cadena de otra cadena en mi archivo strings.xml, como a continuación (tenga en cuenta específicamente el final del contenido de la cadena "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>

He intentado la sintaxis anterior pero luego el texto imprime "@ string / button_text" como texto claro. No es lo que quiero. Me gustaría que el texto del mensaje imprima "¡Aún no tiene ningún artículo! Agregue uno presionando el botón 'Agregar artículo'".

¿Hay alguna forma conocida de lograr lo que quiero?

JUSTIFICACIÓN:
Mi aplicación tiene una lista de elementos, pero cuando esa lista está vacía, en su lugar, muestro un TextView "@android: id / empty". El texto en ese TextView es para informar al usuario cómo agregar un nuevo elemento. Me gustaría hacer que mi diseño sea infalible a los cambios (sí, soy el tonto en cuestión :-)

dbm
fuente
3
Esta respuesta a otra pregunta similar funcionó para mí. No es necesario Java, pero solo funciona dentro del mismo archivo de recursos.
mpkuth

Respuestas:

253

Una buena manera de insertar una cadena de uso frecuente (por ejemplo, nombre de la aplicación) en xml sin usar el código Java: fuente

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

ACTUALIZAR:

Incluso puede definir su entidad globalmente, por ejemplo:

res / raw / entidades.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
fuente
99
Esta es la respuesta correcta al OP. Esta solución tiene otros grandes beneficios: (1) puede definir el DTD en un archivo externo y hacer referencia a él desde cualquier archivo de recursos para aplicar la sustitución en todas partes en sus recursos. (2) Android Studio le permite cambiar el nombre / referencias / refactorizar las entidades definidas. Sin embargo, no autocompleta nombres mientras escribe. (androidstudio 2.2.2)
Mauro Panzeri
3
@RJFares Puede ir de 2 maneras: (a) "incluyendo" un archivo de entidad completo EG: mire esta respuesta . O (b) : incluyendo todas las entidades definidas en una DTD externa como se muestra aquí . Tenga en cuenta que ninguno de los dos es perfecto porque con (a) perderá las capacidades de "refactorización" y (b) es muy detallado
Mauro Panzeri
55
Tenga cuidado si usa esto en un proyecto de biblioteca de Android: no puede sobrescribirlo en su aplicación.
zyamys
44
¿Es posible cambiar el valor de la entidad al definir sabores en el archivo gradle?
Myoch
77
Android Studio ya no es compatible con entidades externas, ya que un error discutido aquí: stackoverflow.com/a/51330953/8154765
Davide Cannizzo
181

Es posible hacer referencia una dentro de otra siempre que su cadena completa consista en el nombre de referencia. Por ejemplo, esto funcionará:

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

Es aún más útil para establecer valores predeterminados:

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

Ahora puede usar string_defaultcualquier parte de su código y puede cambiar fácilmente el valor predeterminado en cualquier momento.

Barry Fruitman
fuente
Lo que también responde a la pregunta: ¿cómo me refiero a la cadena nombre_aplicación en el título de una actividad? :)
Stephen Hosking
53
Esto no debería leer "siempre y cuando haga referencia a la cadena completa" (lo que siempre hace por definición) sino "siempre que el recurso de cadena de referencia solo consista en el nombre de referencia".
sschuberth
54
Esto no es realmente una referencia, es solo un alias, que no resuelve el problema de componer cadenas.
Eric Woodruff
2
Esto no responde a la pregunta, querían una cadena incrustada en otra.
intrepidis
1
FWIW, esto fue extremadamente útil para mí. Gracias.
Ellen Spertus
95

Creo que no puedes Pero puede "formatear" una cadena como desee:

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

En el codigo:

Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));
Francesco Laurita
fuente
55
En realidad esperaba una solución "no lógica". Sin embargo una respuesta bastante justo (y la velocidad pura de la misma le concede una marca de verificación verde :-)
dbm
34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));Está un poco más limpio.
Andrey Novikov
34

En Android no puedes concatenar cadenas dentro de xml

Lo siguiente no es compatible

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

Consulte este enlace a continuación para saber cómo lograrlo

¿Cómo concatenar múltiples cadenas en Android XML?

Mayank Mehta
fuente
16
Bueno, historia divertida: esa es mi respuesta a una pregunta similar a la que hace referencia :-)
dbm
¿Hay una manera de lograr esto?
Es un duplicado de la respuesta de @Francesco Laurita, cómo reemplazar mediante programación una cadena.
CoolMind
14

Creé un complemento de gradle simple que le permite referir una cadena de otra. Puede hacer referencia a cadenas definidas en otro archivo, por ejemplo, en una variante de compilación o biblioteca diferente. Contras de este enfoque: el refactor IDE no encontrará tales referencias.

Use la {{string_name}}sintaxis para referir una cadena:

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

Para integrar el complemento, simplemente agregue el siguiente código en su aplicación o build.gradlearchivo de nivel de módulo de biblioteca

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"

ACTUALIZACIÓN: La biblioteca no funciona con el complemento de Android Gradle versión 3.0 y superior porque la nueva versión del complemento utiliza aapt2 que empaqueta recursos en formato binario .flat, por lo que los recursos empaquetados no están disponibles para la biblioteca. Como solución temporal, puede deshabilitar aapt2 configurando android.enableAapt2 = false en su archivo gradle.properties.

Valeriy Katkov
fuente
Me gusta esta idea ... pero me falla con este error:> Could not get unknown property 'referencedString'
jpage4500
Esto rompe con aapt2
Snicolas
2
He creado un complemento que hace más o menos lo mismo y funciona con aapt2: github.com/LikeTheSalad/android-string-reference :)
César Muñoz
7

Puede usar su propia lógica, que resuelve las cadenas anidadas de forma recursiva.

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

De un Activity, por ejemplo, llámalo así

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

Soy consciente de que esta es una publicación anterior, pero quería compartir la solución rápida y sucia que se me ocurrió para un proyecto mío. Solo funciona para TextViews, pero también podría adaptarse a otros widgets. Tenga en cuenta que requiere que el enlace esté entre corchetes (por ejemplo [@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);
    }
}

La desventaja de este enfoque es que setText()convierte CharSequencea a String, lo cual es un problema si pasa cosas como a SpannableString; para mi proyecto, esto no fue un problema, ya que solo lo usé para el TextViewsque no necesitaba acceder desde mi Activity.

jclehner
fuente
Esto es lo más parecido a una respuesta a esta pregunta. Creo que debemos analizar el enganche en el análisis del diseño xml.
Eric Woodruff
2

Con el nuevo enlace de datos puede concatenar y hacer mucho más en su xml.

por ejemplo, si tienes mensaje1 y mensaje2 puedes:

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

incluso puede importar algunas utilidades de texto y llamar a String.format y amigos.

desafortunadamente, si desea reutilizarlo en varios lugares, puede desordenarse, no desea que este código se encuentre en todas partes. y no puedes definirlos en xml en un solo lugar (no lo sé) para que puedas crear una clase que encapsule esas composiciones:

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

entonces puede usarlo en su lugar (deberá importar la clase con enlace de datos)

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

Además de la respuesta anterior de Francesco Laurita https://stackoverflow.com/a/39870268/9400836

Parece que hay un error de compilación "& entity; fue referenciado, pero no declarado", que puede resolverse haciendo referencia a la declaración externa como esta

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

Aunque se compila y ejecuta, tiene un valor vacío. Quizás alguien sepa cómo resolver esto. Hubiera publicado un comentario, pero la reputación mínima es de 50.

pumnao
fuente
1
Ahora tienes 53.
CoolMind
1

Puede usar marcadores de posición de cadena (% s) y reemplazarlos con java en tiempo de ejecución

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

y en java

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

y luego establecerlo en el lugar donde usa la cadena

Ismail Iqbal
fuente
Copias otras soluciones. Cuando referencia a varias cuerdas, uso %1$s, %2$setc. en lugar de %s.
CoolMind
@CoolMind puedes mencionar la referencia a la copia.
Ismail Iqbal
1

He creado una pequeña biblioteca que le permite resolver estos marcadores de posición en el momento de la compilación, por lo que no tendrá que agregar ningún código Java / Kotlin para lograr lo que desea.

Según su ejemplo, tendría que configurar sus cadenas de esta manera:

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

Y luego el complemento se encargará de generar lo siguiente:

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

Funciona también para cadenas localizadas y de sabor, también las cadenas generadas se mantendrán actualizadas cada vez que realice cambios en sus plantillas o sus valores.

Más información aquí: https://github.com/LikeTheSalad/android-string-reference

César Muñoz
fuente
Probé tu biblioteca. Después de seguir los pasos que has dado. Sigue dando errores de compilación. Su biblioteca no funciona en absoluto
developer1011
Ya veo, lamento escuchar eso. Si lo desea, cree un problema aquí: github.com/LikeTheSalad/android-string-reference/issues con los registros y puedo abordarlo lo antes posible en caso de que sea un problema de función, o si es un problema de configuración, también puedo ayudar tú allí. 👍
César Muñoz