strings.xml의 다른 문자열에서 하나의 문자열을 참조 하시겠습니까?


230

아래와 같이 strings.xml 파일의 다른 문자열에서 문자열을 참조하고 싶습니다 (특히 "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>

위의 구문을 시도했지만 텍스트가 "@ string / button_text"를 일반 텍스트로 인쇄합니다. 내가 원하는 것이 아닙니다. 메시지 항목에 "아직 항목이 없습니다! '항목 추가'버튼을 눌러 추가하십시오."

내가 원하는 것을 달성하는 알려진 방법이 있습니까?

RATIONALE :
응용 프로그램에 항목 목록이 있지만 해당 목록이 비어 있으면 대신 "@android : id / empty"TextView가 표시됩니다. 해당 TextView의 텍스트는 사용자에게 새 항목을 추가하는 방법을 알려주는 것입니다. 내 레이아웃을 변경에 바보처럼 만들고 싶습니다 (예, 나는 바보입니다 :-)


3
다른 비슷한 질문에 대한 대답 은 저에게 효과적이었습니다. Java는 필요하지 않지만 동일한 리소스 파일 내에서만 작동합니다.
mpkuth

답변:


253

Java 코드를 사용하지 않고 자주 사용하는 문자열 (예 : 앱 이름)을 XML에 삽입하는 좋은 방법 : 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>

최신 정보:

다음과 같이 엔터티를 전역 적으로 정의 할 수도 있습니다.

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

9
이것이 OP에 대한 정답입니다. 이 솔루션에는 다른 큰 이점이 있습니다. (1) 외부 파일에서 DTD를 정의하고 모든 리소스 파일에서 DTD를 참조하여 리소스의 모든 위치에 대체를 적용 할 수 있습니다. (2) android studio를 사용하면 정의 된 엔티티의 이름을 바꾸거나 참조 / 리팩터링 할 수 있습니다. 비록 쓰는 동안 이름을 자동 완성하지는 않습니다. (
androidstudio

3
@RJFares 두 가지 방법으로 갈 수 있습니다. (a) 전체 엔티티 파일을 "포함"EG : 이 답변을보십시오 . OR (b) : 같이 외부 DTD에 정의 된 모든 단일 엔티티를 포함한 여기를 . (a) "리팩토링"기능을 풀고 (b) 매우 장황하기 때문에 어느 것도 완벽하지 않습니다.
Mauro Panzeri

5
Android 라이브러리 프로젝트에서 이것을 사용하면주의하십시오-앱에서 덮어 쓸 수 없습니다.
zyamys

4
gradle 파일에서 특징을 정의 할 때 엔티티 값을 변경할 수 있습니까?
Myoch

7
여기에서 논의 된 버그 : stackoverflow.com/a/51330953/8154765
Davide Cannizzo

181

전체 문자열이 참조 이름으로 구성되어있는 한 다른 참조를 참조 할 수 있습니다. 예를 들어 다음과 같이 작동합니다.

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

기본값을 설정하는 데 훨씬 유용합니다.

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

이제 string_default코드의 어느 곳에서나 사용할 수 있으며 언제든지 기본값을 쉽게 변경할 수 있습니다.


또한 질문에 대답합니다 : 활동 제목에서 app_name 문자열을 어떻게 참조합니까? :)
Stephen Hosking 5

53
"전체 문자열을 참조하는 한"(정의 적으로 항상 참조해야 함)은 아니지만 "참조 문자열 리소스가 참조 이름으로 만 구성되어있는 한"은 읽지 않아야합니다.
sschuberth

54
이것은 실제로 참조가 아니며, 별칭 일뿐이므로 문자열 구성 문제를 해결하지 못합니다.
Eric Woodruff

2
이것은 질문에 대답하지 않고 한 문자열을 다른 문자열에 포함시키기를 원했습니다.
intrepidis

1
FWIW, 이것은 나에게 매우 유용했습니다. 감사합니다.
Ellen Spertus

95

나는 당신이 할 수 없다고 생각합니다. 그러나 원하는대로 문자열을 "포맷"할 수 있습니다.

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

코드에서 :

Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));

5
실제로 "논리적"솔루션을 원했습니다. 그럼에도 불구하고 공정한 충분한 답변 (그리고 그것의 투명한 속도는 당신에게 녹색 체크 표시를 부여합니다 :-)
dbm

34
String text = res.getString(R.string.message_text, res.getString(R.string.button_text));조금 더 깨끗합니다.
Andrey Novikov

34

안드로이드에서는 XML 내부에서 문자열을 연결할 수 없습니다

다음은 지원되지 않습니다

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

그것을 달성하는 방법을 알고 아래 링크를 확인하십시오

안드로이드 XML에서 여러 문자열을 연결하는 방법은 무엇입니까?


16
재미있는 이야기 : 그것은 당신이 참조하는 비슷한 질문에 대한 나의 대답입니다 :-)
dbm

이것을 달성 할 수있는 방법이 있습니까?

@Francesco Laurita 답변의 복제본이며 프로그래밍 방식으로 문자열을 바꾸는 방법입니다.
CoolMind

14

하나의 문자열을 다른 문자열에서 참조 할 수있는 간단한 gradle 플러그인 을 만들었습니다 . 다른 빌드 변형 또는 라이브러리와 같은 다른 파일에 정의 된 문자열을 참조 할 수 있습니다. 이 접근법의 단점-IDE 리 팩터는 그러한 참조를 찾지 못합니다.

{{string_name}}문자열을 참조 하려면 구문을 사용 하십시오.

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

플러그인을 통합하려면 앱 또는 라이브러리 모듈 레벨 build.gradle파일 에 다음 코드를 추가 하십시오.

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"

업데이트 : 새로운 버전의 플러그인은 aapt2를 사용하여 리소스를 .flat 바이너리 형식으로 압축하므로 라이브러리에서 압축 된 리소스를 사용할 수 없으므로 라이브러리가 Android gradle 플러그인 버전 3.0 이상에서 작동하지 않습니다. 임시 솔루션으로 gradle.properties 파일에서 android.enableAapt2 = false를 설정하여 aapt2를 비활성화 할 수 있습니다.


나는이 아이디어를 좋아한다. 그러나 그것은이 오류로 나를 위해 실패한다 :> Could not get unknown property 'referencedString'
jpage4500

이것은 aapt2
Snicolas로

2
나는 거의 똑같이하고 aapt2와 함께 작동하는 플러그인을 만들었습니다 : github.com/LikeTheSalad/android-string-reference :)
César Muñoz

7

중첩 된 문자열을 재귀 적으로 해결하는 고유 한 논리를 사용할 수 있습니다.

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

에서 Activity, 예를 들어, 너무 좋아 호출

replaceResourceStrings(this, getString(R.string.message_text));

2

나는 이것이 오래된 게시물이라는 것을 알고 있지만 내 프로젝트를 위해 생각해 낸 빠른 더러운 솔루션을 공유하고 싶었습니다. TextView에만 작동하지만 다른 위젯에도 적용 할 수 있습니다. 링크는 대괄호로 묶어야합니다 (예 :) [@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);
    }
}

이 방법의 단점은 즉 setText()변환 CharSequenceA와 String당신이 같은 일을 전달하는 경우 문제입니다 SpannableString; 내 프로젝트의 경우 내 TextViews에서 액세스 할 필요가 없기 때문에 문제 가되지 않았습니다 Activity.


이것은이 질문에 대한 답변에 가장 가까운 것입니다. 레이아웃 xml 파싱에 연결해야한다고 생각합니다.
Eric Woodruff

2

새로운 데이터 바인딩을 사용 하면 XML을 연결하고 훨씬 더 많은 작업을 수행 할 수 있습니다.

예를 들어 message1 및 message2가있는 경우 다음을 수행 할 수 있습니다.

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

텍스트 유틸리티를 가져 와서 String.format과 친구들을 호출 할 수도 있습니다.

불행히도 여러 곳에서 재사용하려는 경우 지저분해질 수 있으므로 어디에서 나이 코드 조각을 원하지 않습니다. 그리고 당신은 XML을 한 곳에서 (내가 아는 것이 아니라) 정의 할 수 없으므로 그 구성을 캡슐화하는 클래스를 만들 수 있습니다.

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

그런 다음 대신 사용할 수 있습니다 (데이터 바인딩으로 클래스를 가져와야 함)

android:text="@{StringCompositions.completeMessage}"

2

Francesco Laurita의 위의 답변 외에도 https://stackoverflow.com/a/39870268/9400836

다음과 같은 외부 선언을 참조하여 해결할 수있는 컴파일 오류 "& entity;가 참조되었지만 선언되지 않았습니다"가있는 것 같습니다.

입술 / 원시 / 엔터티

<!ENTITY appname "My App Name">

입술 / 값 /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

컴파일하고 실행하지만 빈 값이 있습니다. 누군가가 이것을 해결하는 방법을 알고있을 것입니다. 의견을 게시했지만 최소 평판은 50입니다.


1
지금 당신은 53을 가지고 있습니다.
CoolMind

1

문자열 자리 표시 자 (% s) 를 사용하고 런타임에 java를 사용하여 바꿀 수 있습니다.

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

그리고 자바에서

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

그런 다음 문자열을 사용하는 위치로 설정하십시오.


다른 솔루션을 복사합니다. 여러 문자열을 사용 참조하면 %1$s, %2$s등 대신을 %s.
CoolMind

@CoolMind 사본에 대한 참조를 언급 할 수 있습니다.
Ismail Iqbal

1

빌드 타임에 이러한 자리 표시자를 해결할 수있는 작은 라이브러리를 만들었으므로 원하는 것을 달성하기 위해 Java / Kotlin 코드를 추가 할 필요가 없습니다.

예제를 기반으로 다음과 같이 문자열을 설정해야합니다.

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

그런 다음 플러그인은 다음을 생성합니다.

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

현지화 된 문자열 및 플레이버 링 문자열에서도 작동하며, 생성 된 문자열은 템플릿 또는 해당 값을 변경할 때마다 업데이트됩니다.

자세한 내용은 여기 : https://github.com/LikeTheSalad/android-string-reference


나는 당신의 도서관을 시험해 보았습니다. 당신이 준 단계를 수행 한 후. 빌드 오류가 계속 발생합니다. 귀하의 라이브러리가 전혀 작동하지 않습니다
developer1011

나는 유감입니다. 원하는 경우 여기에 문제를 생성하십시오 : github.com/LikeTheSalad/android-string-reference/issues with logss 및 기능 문제이거나 구성 문제 인 경우 최대한 빨리 해결할 수 있습니다. 당신이 있습니다. 👍
César Muñoz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.