- リモートから来たトゥートに対して「別アカウントで会話の流れ」を開いた時に発言元タンスの疑似アカウントを選べる
- 相対時刻表記の日数の表示が間違っていたバグを修正
This commit is contained in:
tateisu 2017-08-23 18:43:20 +09:00
parent cd656322d1
commit 1ea247829a
6 changed files with 209 additions and 106 deletions

View File

@ -87,8 +87,10 @@ import jp.juggler.subwaytooter.util.Utils;
import jp.juggler.subwaytooter.view.ColumnStripLinearLayout;
import jp.juggler.subwaytooter.view.GravitySnapHelper;
import jp.juggler.subwaytooter.view.MyEditText;
import okhttp3.Call;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class ActMain extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, View.OnClickListener, ViewPager.OnPageChangeListener, Column.Callback, DrawerLayout.DrawerListener
@ -2616,6 +2618,8 @@ public class ActMain extends AppCompatActivity
}
}
static final Pattern reUriOStatusToot = Pattern.compile( "tag:([^,]*),[^:]*:objectId=(\\d+):objectType=Status" ,Pattern.CASE_INSENSITIVE);
public void openStatusOtherInstance( int pos, @NonNull SavedAccount access_info, @NonNull TootStatusLike status ){
if( status.account == null ){
// アカウント情報がないと出来ないことがある
@ -2625,18 +2629,36 @@ public class ActMain extends AppCompatActivity
, status.host_original, status.id
, null, - 1L
);
}else if( status.host_original.equals( status.host_access ) ){
// TLアカウントのホストとトゥートのアカウントのホストが同じ場合
openStatusOtherInstance( pos, access_info, status.url
, status.host_original, status.id
, null, - 1L
);
}else{
// TLアカウントのホストとトゥートのアカウントのホストが異なる場合
openStatusOtherInstance( pos, access_info, status.url
, null, - 1L
, status.host_access, status.id
);
}else if( status instanceof TootStatus ){
TootStatus ts = (TootStatus)status;
if( status.host_original.equals( status.host_access ) ){
// TLアカウントのホストとトゥートのアカウントのホストが同じ場合
openStatusOtherInstance( pos, access_info, status.url
, status.host_original, status.id
, null, - 1L
);
}else{
// TLアカウントのホストとトゥートのアカウントのホストが異なる場合
long status_id_original = -1L;
String host_original = null;
try{
// OStatusでフィードされたトゥートの場合UriにステータスIDが含まれている
Matcher m = reUriOStatusToot.matcher( ts.uri );
if( m.find() ){
status_id_original = Long.parseLong( m.group( 2 ), 10 );
host_original = m.group( 1 );
}
}catch(Throwable ex){
log.e(ex,"openStatusOtherInstance: cant parse tag: %s",ts.uri);
}
openStatusOtherInstance( pos, access_info, status.url
, host_original,status_id_original
, status.host_access, status.id
);
}
}
}
@ -2644,64 +2666,41 @@ public class ActMain extends AppCompatActivity
final int pos
, @Nullable final SavedAccount access_info
, @NonNull final String url
, final String host_original, final long status_id_original
, final String host_original_unused, final long status_id_original
, final String host_access, final long status_id_access
){
ActionsDialog dialog = new ActionsDialog();
Uri uri = Uri.parse( url );
String host_name = uri.getAuthority();
final String host_original = Uri.parse( url ).getAuthority();
// 選択肢ブラウザで表示する
dialog.addAction( getString( R.string.open_web_on_host, host_name ), new Runnable() {
dialog.addAction( getString( R.string.open_web_on_host, host_original ), new Runnable() {
@Override public void run(){
openChromeTab( pos, access_info, url, true );
}
} );
boolean has_local_account = false;
ArrayList< SavedAccount > account_list = new ArrayList<>();
ArrayList<SavedAccount> local_account_list = new ArrayList<>( );
ArrayList<SavedAccount> access_account_list = new ArrayList<>( );
ArrayList<SavedAccount> other_account_list = new ArrayList<>( );
for( SavedAccount a : SavedAccount.loadAccountList( ActMain.this, log ) ){
// 疑似アカウントは後でまとめて処理する
if( a.isPseudo() ) continue;
if( status_id_original >= 0L && host_original.equalsIgnoreCase( a.host ) ){
// アクセス情報ステータスID でアクセスできるなら
// 同タンスのアカウントならステータスIDの変換なしに表示できる
account_list.add( a );
has_local_account = true;
local_account_list.add( a );
}else if( status_id_access >= 0L && host_access.equalsIgnoreCase( a.host ) ){
// 既に変換済みのステータスIDがあるならそのアカウントでもステータスIDの変換は必要ない
account_list.add( a );
}else if( ! a.isPseudo() ){
access_account_list.add( a );
}else{
// 別タンスでも実アカウントなら検索APIでステータスIDを変換できる
account_list.add( a );
other_account_list.add(a);
}
}
// ソートする
Collections.sort( account_list, new Comparator< SavedAccount >() {
@Override public int compare( SavedAccount a, SavedAccount b ){
return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) );
}
} );
// ダイアログの選択肢に追加
for( SavedAccount a : account_list ){
final SavedAccount _a = a;
dialog.addAction( getString( R.string.open_in_account, AcctColor.getNickname( a.acct ) ), new Runnable() {
@Override public void run(){
if( status_id_original >= 0L && host_original.equalsIgnoreCase( _a.host ) ){
openStatusLocal( pos, _a, status_id_original );
}else if( status_id_access >= 0L && host_access.equalsIgnoreCase( _a.host ) ){
openStatusLocal( pos, _a, status_id_access );
}else if( ! _a.isPseudo() ){
openStatusRemote( pos, _a, url );
}
}
} );
}
// 同タンスのアカウントがないなら疑似アカウントを作る選択肢
if( ! has_local_account ){
// 同タンスのアカウントがないなら疑似アカウントで開く選択肢
if( local_account_list.isEmpty() ){
if( status_id_original >= 0L ){
dialog.addAction( getString( R.string.open_in_pseudo_account, "?@" + host_original ), new Runnable() {
@Override public void run(){
@ -2711,15 +2710,68 @@ public class ActMain extends AppCompatActivity
}
}
} );
}else if( status_id_access >= 0L ){
// リモートから流れてきたトゥートを オリジナルのインスタンスの疑似アカウントで開きたい
// しかし疑似アカウントでは検索ができないのでオリジナルのインスタンス上でのステータスIDを知る方法がない
}else{
dialog.addAction( getString( R.string.open_in_pseudo_account, "?@" + host_original ), new Runnable() {
@Override public void run(){
SavedAccount sa = addPseudoAccount( host_original );
if( sa != null ){
openStatusRemote(pos, sa, url );
}
}
} );
}
}
// ローカルアカウント
Collections.sort( local_account_list, new Comparator< SavedAccount >() {
@Override public int compare( SavedAccount a, SavedAccount b ){
return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) );
}
} );
for( SavedAccount a : local_account_list ){
final SavedAccount _a = a;
dialog.addAction( getString( R.string.open_in_account, AcctColor.getNickname( a.acct ) ), new Runnable() {
@Override public void run(){
openStatusLocal( pos, _a, status_id_original );
}
} );
}
// アクセスしたアカウント
Collections.sort( access_account_list, new Comparator< SavedAccount >() {
@Override public int compare( SavedAccount a, SavedAccount b ){
return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) );
}
} );
for( SavedAccount a : access_account_list ){
final SavedAccount _a = a;
dialog.addAction( getString( R.string.open_in_account, AcctColor.getNickname( a.acct ) ), new Runnable() {
@Override public void run(){
openStatusLocal( pos, _a, status_id_access );
}
} );
}
// その他の実アカウント
Collections.sort( other_account_list, new Comparator< SavedAccount >() {
@Override public int compare( SavedAccount a, SavedAccount b ){
return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) );
}
} );
for( SavedAccount a : other_account_list ){
final SavedAccount _a = a;
dialog.addAction( getString( R.string.open_in_account, AcctColor.getNickname( a.acct ) ), new Runnable() {
@Override public void run(){
openStatusRemote( pos, _a, url );
}
} );
}
dialog.show( this, getString( R.string.open_status_from ) );
}
static final Pattern reDetailedStatusTime = Pattern.compile( "<a\\b[^>]*?\\bdetailed-status__datetime\\b[^>]*href=\"https://[^/]+/@[^/]+/(\\d+)\"");
public void openStatusRemote(
final int pos
, final SavedAccount access_info
@ -2728,7 +2780,8 @@ public class ActMain extends AppCompatActivity
final ProgressDialog progress = new ProgressDialog( this );
final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() {
TootStatus target_status;
long local_status_id = -1L;
@Override protected TootApiResult doInBackground( Void... params ){
TootApiClient client = new TootApiClient( ActMain.this, new TootApiClient.Callback() {
@ -2741,19 +2794,37 @@ public class ActMain extends AppCompatActivity
} );
client.setAccount( access_info );
// 検索APIに他タンスのステータスのURLを投げると自タンスのステータスを得られる
String path = String.format( Locale.JAPAN, Column.PATH_SEARCH, Uri.encode( remote_status_url ) );
path = path + "&resolve=1";
TootApiResult result = client.request( path );
if( result != null && result.object != null ){
TootResults tmp = TootResults.parse( ActMain.this, access_info, result.object );
if( tmp != null && tmp.statuses != null && ! tmp.statuses.isEmpty() ){
target_status = tmp.statuses.get( 0 );
log.d( "status id conversion %s => %s", remote_status_url, target_status.id );
TootApiResult result;
if( access_info.isPseudo() ){
result = client.getHttp(remote_status_url);
if( result != null && result.json != null ){
try{
Matcher m = reDetailedStatusTime.matcher( result.json );
if( m.find() ){
local_status_id = Long.parseLong( m.group(1) ,10);
}
}catch(Throwable ex){
log.e(ex,"openStatusRemote: can't parse status id from HTML data.");
}
if( local_status_id == -1L ){
result = new TootApiResult( getString( R.string.status_id_conversion_failed ) );
}
}
if( target_status == null ){
return new TootApiResult( getString( R.string.status_id_conversion_failed ) );
}else{
// 検索APIに他タンスのステータスのURLを投げると自タンスのステータスを得られる
String path = String.format( Locale.JAPAN, Column.PATH_SEARCH, Uri.encode( remote_status_url ) );
path = path + "&resolve=1";
result = client.request( path );
if( result != null && result.object != null ){
TootResults tmp = TootResults.parse( ActMain.this, access_info, result.object );
if( tmp != null && tmp.statuses != null && ! tmp.statuses.isEmpty() ){
TootStatus status = tmp.statuses.get( 0 );
local_status_id = status.id;
log.d( "status id conversion %s => %s", remote_status_url, status.id );
}
if( local_status_id == -1L ){
result = new TootApiResult( getString( R.string.status_id_conversion_failed ) );
}
}
}
return result;
@ -2772,8 +2843,8 @@ public class ActMain extends AppCompatActivity
}
if( result == null ){
// cancelled.
}else if( target_status != null ){
openStatus( pos, access_info, target_status );
}else if( local_status_id != -1L ){
openStatusLocal( pos, access_info, local_status_id );
}else{
Utils.showToast( ActMain.this, true, result.error );
}
@ -2791,6 +2862,7 @@ public class ActMain extends AppCompatActivity
progress.show();
task.executeOnExecutor( App1.task_executor );
}
////////////////////////////////////////
// delete notification

View File

@ -117,7 +117,7 @@ public class TootApiClient {
if( callback.isApiCancelled() ) return null;
if( ! response.isSuccessful() ){
return new TootApiResult( instance + ": " +context.getString( R.string.network_error_arg, response ) );
return new TootApiResult( instance + ": " + context.getString( R.string.network_error_arg, response ) );
}
try{
@ -198,13 +198,13 @@ public class TootApiClient {
response = call.execute();
}catch( Throwable ex ){
log.trace( ex );
return new TootApiResult( instance + ": " +Utils.formatError( ex, context.getResources(), R.string.network_error ) );
return new TootApiResult( instance + ": " + Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
if( ! response.isSuccessful() ){
return new TootApiResult( instance + ": " +context.getString( R.string.network_error_arg, response ) );
return new TootApiResult( instance + ": " + context.getString( R.string.network_error_arg, response ) );
}
try{
@ -231,7 +231,7 @@ public class TootApiClient {
}
}
public @Nullable TootApiResult authorize1(String client_name){
public @Nullable TootApiResult authorize1( String client_name ){
JSONObject client_info;
@ -244,7 +244,7 @@ public class TootApiClient {
Response response;
try{
if( TextUtils.isEmpty( client_name )){
if( TextUtils.isEmpty( client_name ) ){
client_name = "SubwayTooter";
}
@ -262,12 +262,12 @@ public class TootApiClient {
response = call.execute();
}catch( Throwable ex ){
log.trace( ex );
return new TootApiResult( instance + ": " +Utils.formatError( ex, context.getResources(), R.string.network_error ) );
return new TootApiResult( instance + ": " + Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
if( ! response.isSuccessful() ){
return new TootApiResult( instance + ": " +context.getString( R.string.network_error_arg, response ) );
return new TootApiResult( instance + ": " + context.getString( R.string.network_error_arg, response ) );
}
try{
//noinspection ConstantConditions
@ -311,7 +311,7 @@ public class TootApiClient {
return new TootApiResult( browser_url );
}
public @Nullable TootApiResult authorize2( String code ){
public @Nullable TootApiResult authorize2( String code ){
JSONObject client_info = ClientInfo.load( instance );
if( client_info == null ){
@ -341,7 +341,7 @@ public @Nullable TootApiResult authorize2( String code ){
response = call.execute();
}catch( Throwable ex ){
log.trace( ex );
return new TootApiResult( instance + ": " +Utils.formatError( ex, context.getResources(), R.string.network_error ) );
return new TootApiResult( instance + ": " + Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
@ -387,7 +387,7 @@ public @Nullable TootApiResult authorize2( String code ){
response = call.execute();
}catch( Throwable ex ){
log.trace( ex );
return new TootApiResult( instance + ": " +Utils.formatError( ex, context.getResources(), R.string.network_error ) );
return new TootApiResult( instance + ": " + Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
@ -430,10 +430,10 @@ public @Nullable TootApiResult authorize2( String code ){
JSONObject token_info;
Response response;
try{
// 指定されたアクセストークンを使って token_info を捏造する
token_info = new JSONObject( );
token_info.put("access_token",access_token);
token_info = new JSONObject();
token_info.put( "access_token", access_token );
// 認証されたアカウントのユーザ名を取得する
String path = "/api/v1/accounts/verify_credentials";
@ -449,7 +449,7 @@ public @Nullable TootApiResult authorize2( String code ){
response = call.execute();
}catch( Throwable ex ){
log.trace( ex );
return new TootApiResult( instance + ": " +Utils.formatError( ex, context.getResources(), R.string.network_error ) );
return new TootApiResult( instance + ": " + Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
@ -482,4 +482,35 @@ public @Nullable TootApiResult authorize2( String code ){
}
}
}
public TootApiResult getHttp( String url ){
Response response;
try{
Request.Builder request_builder = new Request.Builder();
request_builder.url( url );
Call call = ok_http_client.newCall( request_builder.build() );
response = call.execute();
}catch( Throwable ex ){
log.trace( ex );
return new TootApiResult( url + ": " + Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
if( ! response.isSuccessful() ){
return new TootApiResult( url + ": " + context.getString( R.string.network_error_arg, response ) );
}
try{
//noinspection ConstantConditions
String body = response.body().string();
JSONObject data = new JSONObject();
data.put( "body", body );
return new TootApiResult( response, null, body, data );
}catch( Throwable ex ){
log.trace( ex );
return new TootApiResult( Utils.formatError( ex, "API data error" ) );
}
}
}

View File

@ -198,7 +198,7 @@ public class TootStatus extends TootStatusLike {
long delta = now - t;
String sign = context.getString( delta > 0 ? R.string.ago : R.string.later );
delta = delta >= 0 ? delta : - delta;
if( delta < 2000L ){
if( delta < 1000L ){
return context.getString( R.string.time_within_second );
}else if( delta < 60000L ){
int v = (int) ( delta / 1000L );
@ -209,8 +209,8 @@ public class TootStatus extends TootStatusLike {
}else if( delta < 86400000L ){
int v = (int) ( delta / 3600000L );
return context.getString( v > 1 ? R.string.relative_time_hour_2 : R.string.relative_time_hour_1, v, sign );
}else if( delta < 65 * 86400000L ){
int v = (int) ( delta / 3600000L );
}else if( delta < 40 * 86400000L ){
int v = (int) ( delta / 86400000L );
return context.getString( v > 1 ? R.string.relative_time_day_2 : R.string.relative_time_day_1, v, sign );
}
}

View File

@ -358,9 +358,9 @@
<string name="enable_speech">Enables speech</string>
<string name="url_omitted">(URL omitted)</string>
<string name="blocked_domains">Blocked domains</string>
<string name="block_domain_that">Block whole domain \'%1$s\'</string>
<string name="confirm_block_domain">Whole domain \'%1$s\' will be blocked. Are you sure?</string>
<string name="confirm_unblock_domain">Unblock domain \'%1$s\'?</string>
<string name="block_domain_that">Block whole domain \"%1$s\"</string>
<string name="confirm_block_domain">Whole domain \"%1$s\" will be blocked. Are you sure?</string>
<string name="confirm_unblock_domain">Unblock domain \"%1$s\"?</string>
<string name="text_to_speech_initializing">TextToSpeech initializing…</string>
<string name="text_to_speech_initialize_failed">TextToSpeech initializing failed. status=%1$s</string>
@ -403,7 +403,7 @@
<string name="access_token">Access token</string>
<string name="token_not_specified">Please input access token.</string>
<string name="quote_name">Quote name…</string>
<string name="format_of_quote_name">Format of \'Quote name\' (set text that contains %1$s)</string>
<string name="format_of_quote_name">Format of \"Quote name\" (set text that contains %1$s)</string>
<string name="dont_show_favourite">Masquer les favourite</string>
<string name="dont_show_follow">Masquer les follow</string>
<string name="background_image">Background image</string>
@ -414,7 +414,7 @@
<string name="enable_gif_animation">Enable GIF animation (It wastes the battery very much)</string>
<string name="acct_sample">(sample)username@instance</string>
<string name="mention_full_acct">Show mention as full acct (column reload required)</string>
<string name="remote_profile_warning">Remote users\' profile may be inadequate information. You can check more accurate information on the web page.</string>
<string name="remote_profile_warning">Remote user\'s profile may be inadequate information. You can check more accurate information on the web page.</string>
<string name="public_profile">Public profile</string>
<string name="change_avatar">Change avatar icon</string>
<string name="change_header">Change header image</string>
@ -425,7 +425,7 @@
<string name="email">E-mail</string>
<string name="description">Description</string>
<string name="version">Version</string>
<string name="instance_information_of">%1$s Instance information</string>
<string name="instance_information_of">Instance information of \"%1$s\"</string>
<string name="instance_information">Instance information</string>
<string name="missing_mail_app">Missing E-mail app.</string>
<string name="relative_timestamp">Show relative timestamps</string>

View File

@ -434,9 +434,9 @@
<!---->
<string name="actions_for_status">発言へのアクション</string>
<!---->
<string name="confirm_block_user">ユーザ \'%1$s\' をブロックします。よろしいですか?</string>
<string name="confirm_block_user">ユーザ \"%1$s\" をブロックします。よろしいですか?</string>
<!---->
<string name="confirm_mute_user">ユーザ \'%1$s\' をミュートします。よろしいですか?</string>
<string name="confirm_mute_user">ユーザ \"%1$s\" をミュートします。よろしいですか?</string>
<!---->
<string name="confirm_delete_status">この発言を削除します。よろしいですか?</string>
<!---->
@ -644,10 +644,10 @@
<string name="prior_chrome_custom_tabs">Chrome Custom Tabs を優先的に利用する</string>
<string name="enable_speech">読み上げを有効にする</string>
<string name="url_omitted">(URL略)</string>
<string name="block_domain_that">\'%1$s\' ドメインをブロック</string>
<string name="block_domain_that">\"%1$s\" ドメインをブロック</string>
<string name="blocked_domains">ブロックしたドメイン</string>
<string name="confirm_block_domain">ドメイン \'%1$s\' 全体をブロックします。よろしいですか?</string>
<string name="confirm_unblock_domain">ドメイン \'%1$s\' のブロックを解除しますか?</string>
<string name="confirm_block_domain">ドメイン \"%1$s\" 全体をブロックします。よろしいですか?</string>
<string name="confirm_unblock_domain">ドメイン \"%1$s\" のブロックを解除しますか?</string>
<string name="text_to_speech_initializing">TextToSpeechの初期化中…</string>
<string name="text_to_speech_initialize_failed">TextToSpeechの初期化に失敗。status=%1$s</string>
@ -690,7 +690,7 @@
<string name="input_access_token">アクセストークンを指定する(上級者向け)</string>
<string name="token_not_specified">アクセストークンを入力してください</string>
<string name="quote_name">名前を引用…</string>
<string name="format_of_quote_name">\'名前を引用\'のフォーマット ( %1$s を含むテキストを指定する)</string>
<string name="format_of_quote_name">\"名前を引用\"のフォーマット ( %1$s を含むテキストを指定する)</string>
<string name="dont_show_favourite">お気に入りを表示しない</string>
<string name="dont_show_follow">フォローを表示しない</string>
<string name="acct_color">Acctの文字色</string>
@ -712,7 +712,7 @@
<string name="email">メールアドレス</string>
<string name="description">説明</string>
<string name="version">バージョン</string>
<string name="instance_information_of">%1$sのインスタンス情報</string>
<string name="instance_information_of">\"%1$s\"のインスタンス情報</string>
<string name="instance_information">インスタンス情報</string>
<string name="missing_mail_app">メールアドレスを共有できるアプリがありません</string>
<string name="relative_timestamp">相対時刻を表示</string>

View File

@ -219,8 +219,8 @@
<string name="send_message">Send message</string>
<string name="send_message_from_another_account">Send message from another account</string>
<string name="open_profile_from_another_account">Open profile from another account</string>
<string name="confirm_block_user">User \'%1$s\' will be blocked. Are you sure?</string>
<string name="confirm_mute_user">User \'%1$s\' will be muted. Are you sure?</string>
<string name="confirm_block_user">User \"%1$s\" will be blocked. Are you sure?</string>
<string name="confirm_mute_user">User \"%1$s\" will be muted. Are you sure?</string>
<string name="confirm_delete_status">This toot will be deleted. Are you sure?</string>
<string name="nickname_label">Nickname and color (if specified, it\'s shown instead of full acct)</string>
<string name="color">Color</string>
@ -354,9 +354,9 @@
<string name="enable_speech">Enables speech</string>
<string name="url_omitted">(URL omitted)</string>
<string name="blocked_domains">Blocked domains</string>
<string name="block_domain_that">Block whole domain \'%1$s\'</string>
<string name="confirm_block_domain">Whole domain \'%1$s\' will be blocked. Are you sure?</string>
<string name="confirm_unblock_domain">Unblock domain \'%1$s\'?</string>
<string name="block_domain_that">Block whole domain \"%1$s\"</string>
<string name="confirm_block_domain">Whole domain \"%1$s\" will be blocked. Are you sure?</string>
<string name="confirm_unblock_domain">Unblock domain \"%1$s\"?</string>
<string name="text_to_speech_initializing">TextToSpeech initializing…</string>
<string name="text_to_speech_initialize_failed">TextToSpeech initializing failed. status=%1$s</string>
<string name="text_to_speech_shutdown">TextToSpeech shutdown…</string>
@ -398,7 +398,7 @@
<string name="access_token">Access token</string>
<string name="token_not_specified">Please input access token.</string>
<string name="quote_name">Quote name…</string>
<string name="format_of_quote_name">Format of \'Quote name\' (set text that contains %1$s)</string>
<string name="format_of_quote_name">Format of \"Quote name\" (set text that contains %1$s)</string>
<string name="dont_show_favourite">Don\'t show favourite</string>
<string name="dont_show_follow">Don\'t show follow</string>
<string name="background_image">Background image</string>
@ -409,7 +409,7 @@
<string name="enable_gif_animation">Enable GIF animation (It wastes the battery very much)</string>
<string name="acct_sample">(sample)username@instance</string>
<string name="mention_full_acct">Show mention as full acct (column reload required)</string>
<string name="remote_profile_warning">Remote users\' profile may be inadequate information. You can check more accurate information on the web page.</string>
<string name="remote_profile_warning">Remote user\'s profile may be inadequate information. You can check more accurate information on the web page.</string>
<string name="public_profile">Public profile</string>
<string name="change_avatar">Change avatar icon</string>
<string name="change_header">Change header image</string>
@ -420,7 +420,7 @@
<string name="email">E-mail</string>
<string name="description">Description</string>
<string name="version">Version</string>
<string name="instance_information_of">%1$s Instance information</string>
<string name="instance_information_of">Instance information of \"%1$s\"</string>
<string name="instance_information">Instance information</string>
<string name="missing_mail_app">Missing E-mail app.</string>
<string name="relative_timestamp">Show relative timestamps</string>