Referencia uma string de outra string em strings.xml?

230

Gostaria de referenciar uma string de outra string no meu arquivo strings.xml, como abaixo (observe especificamente o final do conteúdo da 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>

Eu tentei a sintaxe acima, mas o texto imprime o "@ string / button_text" como texto não criptografado. Não é o que eu quero. Gostaria que o texto da mensagem fosse impresso "Você ainda não possui nenhum item! Adicione um pressionando o botão 'Adicionar item'".

Existe alguma maneira conhecida de conseguir o que eu quero?

JUSTIFICATIVA:
Meu aplicativo tem uma lista de itens, mas quando essa lista está vazia, mostro um TextView "@android: id / empty". O texto nesse TextView é para informar ao usuário como adicionar um novo item. Gostaria de fazer meu layout à prova de idiotas às alterações (sim, sou o idiota em questão :-)

dbm
fonte
3
Esta resposta para outra pergunta semelhante funcionou para mim. Não é necessário java, mas ele funciona apenas no mesmo arquivo de recurso.
mpkuth

Respostas:

253

Uma ótima maneira de inserir uma string usada com frequência (por exemplo, nome do aplicativo) no xml sem usar o código 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>

ATUALIZAR:

Você pode até definir sua entidade globalmente, por exemplo:

res / raw / entity.ent:

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

res / valores / 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
fonte
9
esta é a resposta correta para o OP. Esta solução tem outros grandes benefícios: (1) você pode definir o DTD em um arquivo externo e referenciá-lo a partir de qualquer arquivo de recurso para aplicar a substituição em qualquer lugar dos seus recursos. (2) o android studio permite renomear / referenciar / refatorar as entidades definidas. No entanto, ele não preenche nomes automaticamente enquanto escreve. (androidstudio 2.2.2)
Mauro Panzeri 11/11
3
@RJFares Você pode seguir duas maneiras: (a) "incluindo" um arquivo de entidade inteiro EG: veja esta resposta . OU (b) : incluindo todas as entidades definidas em uma DTD externa, como mostrado aqui . Observe que nenhum deles é perfeito porque, com (a) você perderá as capacidades de "refatoração" e (b) é muito detalhado
Mauro Panzeri
5
Cuidado se você usar isso em um projeto de biblioteca do Android - você não pode substituí-lo no seu aplicativo.
Zyamys 31/05
4
é possível alterar o valor da entidade ao definir sabores no arquivo gradle?
Myoch 19/11
7
Entidades externas não são suportados pelo Android Estúdio mais, uma vez que um bug discutido aqui: stackoverflow.com/a/51330953/8154765
Davide Cannizzo
181

É possível fazer referência uma dentro da outra, desde que toda a cadeia de caracteres consista no nome de referência. Por exemplo, isso funcionará:

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

É ainda mais útil para definir valores padrão:

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

Agora você pode usar string_defaultem qualquer lugar do seu código e alterar facilmente o padrão a qualquer momento.

Barry Fruitman
fonte
O que também responde à pergunta: como faço para me referir à string app_name em um título de atividade. :)
Stephen Hosking
53
Isso não deve ler "desde que você faça referência a toda a cadeia" (que você sempre define por definição), mas "enquanto o recurso de cadeia de referência consistir apenas no nome de referência".
sschuberth
54
Isso não é realmente uma referência, é apenas um alias, que não resolve o problema de compor strings.
Eric Woodruff
2
Isso não responde à pergunta, eles queriam que uma string fosse incorporada em outra.
Intrepidis
1
FWIW, isso foi extremamente útil para mim. Obrigado.
Ellen Spertus 9/03/19
95

Eu acho que você não pode. Mas você pode "formatar" uma string como desejar:

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

No código:

Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));
Francesco Laurita
fonte
5
Na verdade, eu esperava uma solução "não lógica". No entanto, uma resposta justa o suficiente (e a velocidade pura dele concede-lhe uma marca de seleção verde :-)
dbm
34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));é um pouco mais limpo.
Andrey Novikov
34

No Android, você não pode concatenar Strings dentro de xml

O seguinte não é suportado

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

Verifique este link abaixo para saber como alcançá-lo

Como concatenar várias seqüências de caracteres no XML do Android?

Mayank Mehta
fonte
16
Bem, história engraçada: essa é a minha resposta a uma pergunta semelhante que você está fazendo referência :-)
dbm
Existe uma maneira de conseguir isso?
É uma duplicata da resposta do @Francesco Laurita, como substituir programaticamente uma string.
CoolMind
14

Eu criei um plugin gradle simples que permite que você indique uma string de outra. Você pode consultar cadeias definidas em outro arquivo, por exemplo, em uma variante ou biblioteca de construção diferente. Contras dessa abordagem - o refator do IDE não encontrará essas referências.

Use a {{string_name}}sintaxe para referenciar uma sequência:

<?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 o plug-in, basta adicionar o próximo código ao build.gradlearquivo no nível do aplicativo ou do módulo da 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"

ATUALIZAÇÃO: a biblioteca não funciona com o plug-in Android gradle versão 3.0 e posterior porque a nova versão do plug-in usa aapt2, que empacota recursos no formato binário .flat, portanto, os recursos compactados não estão disponíveis para a biblioteca. Como solução temporária, você pode desativar o aapt2 configurando android.enableAapt2 = false no seu arquivo gradle.properties.

Valeriy Katkov
fonte
Eu gosto desta idéia .. mas não para mim com este erro:> Could not get unknown property 'referencedString'
jpage4500
Isso rompe com aapt2
Snicolas
2
Eu criei um plugin que faz praticamente o mesmo e trabalha com aapt2: github.com/LikeTheSalad/android-string-reference :)
César Muñoz
7

Você pode usar sua própria lógica, que resolve as seqüências aninhadas recursivamente.

/**
 * 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 um Activity, por exemplo, chame assim

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

Estou ciente de que esta é uma publicação mais antiga, mas queria compartilhar a solução rápida e suja que encontrei para um projeto meu. Funciona apenas para TextViews, mas também pode ser adaptado a outros widgets. Observe que ele exige que o link seja colocado entre colchetes (por exemplo [@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);
    }
}

A desvantagem dessa abordagem é que setText()converte a CharSequencepara a String, o que é um problema se você passar coisas como a SpannableString; para o meu projeto, isso não foi um problema, pois eu o usei apenas para o TextViewsqual não precisava acessar do meu Activity.

jclehner
fonte
Esta é a coisa mais próxima de uma resposta a esta pergunta. Acho que precisamos investigar a conexão com a análise de layout xml.
Eric Woodruff
2

Com a nova ligação de dados, você pode concatenar e fazer muito mais em seu xml.

por exemplo, se você recebeu message1 e message2, pode:

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

você pode até importar alguns utilitários de texto e chamar String.format e amigos.

infelizmente, se você deseja reutilizá-lo em vários lugares, pode ficar confuso, não deseja que esse código seja exibido em todos os lugares. e você não pode defini-los em xml em um só lugar (não que eu saiba), para que você possa criar uma classe que encapsule essas composições:

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

então você pode usá-lo (será necessário importar a classe com ligação de dados)

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

Além da resposta acima por Francesco Laurita https://stackoverflow.com/a/39870268/9400836

Parece que há um erro de compilação "& entity; foi referenciado, mas não declarado", que pode ser resolvido referenciando a declaração externa como esta

res / raw / entity.ent

<!ENTITY appname "My App Name">

res / valores / 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

Embora compile e execute, ele tem um valor vazio. Talvez alguém saiba como resolver isso. Eu teria postado um comentário, mas a reputação mínima é 50.

pumnao
fonte
1
Agora você tem 53.
CoolMind
1

Você pode usar espaços reservados de seqüência de caracteres (% s) e substituí-los usando java no tempo de execução

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

e em java

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

e, em seguida, defina-o no local em que ele usa a string

Ismail Iqbal
fonte
Você copia outras soluções. Ao fazer referência a várias cordas, utilização %1$s, %2$setc., em vez de %s.
CoolMind
@CoolMind, você pode mencionar a referência à cópia.
Ismail Iqbal
1

Criei uma pequena biblioteca que permite resolver esses espaços reservados no momento da construção, para que você não precise adicionar nenhum código Java / Kotlin para obter o que deseja.

Com base no seu exemplo, você teria que configurar suas strings assim:

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

E então o plugin cuidará de gerar o seguinte:

<?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 também para cadeias localizadas e de sabor, e também as cadeias geradas se manterão atualizadas sempre que você fizer alterações em seus modelos ou em seus valores.

Mais informações aqui: https://github.com/LikeTheSalad/android-string-reference

César Muñoz
fonte
Eu tentei sua biblioteca. Depois de seguir os passos que você deu. Ele continua dando erros de construção. Sua biblioteca não funciona de jeito nenhum
developer1011
Entendo, sinto muito ouvir isso. Se você preferir , crie um problema aqui: github.com/LikeTheSalad/android-string-reference/issues com os logs e posso solucioná-lo o mais rápido possível, caso seja um problema de recurso ou se for um problema de configuração, também posso ajudar você aí. C
César Muñoz