diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt index fc1c3ef..8b2b5e7 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt @@ -108,7 +108,7 @@ class DnsActivity : AppCompatActivity() { view.findViewById(R.id.deletePeerButton).setOnClickListener { button -> val builder: AlertDialog.Builder = AlertDialog.Builder(this) - builder.setTitle("Remove ${server}?") + builder.setTitle(getString(R.string.dns_remove_title, server)) builder.setPositiveButton(getString(R.string.remove)) { dialog, _ -> servers.removeAt(button.tag as Int) preferences.edit().apply { diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt b/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt index b2af6e0..13fedff 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt @@ -1,25 +1,27 @@ package eu.neilalexander.yggdrasil import android.app.Application -import android.content.IntentFilter -import androidx.localbroadcastmanager.content.LocalBroadcastManager class GlobalApplication: Application() { - private var state = PacketTunnelState private lateinit var config: ConfigurationProxy + var updaterConnections: Int = 0 override fun onCreate() { super.onCreate() config = ConfigurationProxy(applicationContext) - - LocalBroadcastManager.getInstance(this).registerReceiver( - state, IntentFilter(PacketTunnelProvider.RECEIVER_INTENT) - ) } - override fun onTerminate() { - super.onTerminate() + fun subscribe() { + updaterConnections++ + } - LocalBroadcastManager.getInstance(this).unregisterReceiver(state) + fun unsubscribe() { + if (updaterConnections > 0) { + updaterConnections-- + } + } + + fun needUiUpdates(): Boolean { + return updaterConnections > 0 } } \ No newline at end of file diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt index 82fb296..d33d4ac 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt @@ -12,12 +12,12 @@ import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.localbroadcastmanager.content.LocalBroadcastManager +import eu.neilalexander.yggdrasil.PacketTunnelProvider.Companion.STATE_INTENT import mobile.Mobile +import org.json.JSONArray class MainActivity : AppCompatActivity() { - private var state = PacketTunnelState - private lateinit var enabledSwitch: Switch private lateinit var enabledLabel: TextView private lateinit var ipAddressLabel: TextView @@ -116,20 +116,27 @@ class MainActivity : AppCompatActivity() { override fun onResume() { super.onResume() LocalBroadcastManager.getInstance(this).registerReceiver( - receiver, IntentFilter(PacketTunnelState.RECEIVER_INTENT) + receiver, IntentFilter(STATE_INTENT) ) val preferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this.baseContext) val serverString = preferences.getString(KEY_DNS_SERVERS, "") if (serverString!!.isNotEmpty()) { val servers = serverString.split(",") dnsLabel.text = when (servers.size) { - 0 -> "No servers" - 1 -> "1 server" - else -> "${servers.size} servers" + 0 -> getString(R.string.dns_no_servers) + 1 -> getString(R.string.dns_one_server) + else -> getString(R.string.dns_many_servers, servers.size) } } else { - dnsLabel.text = "No servers" + dnsLabel.text = getString(R.string.dns_no_servers) } + (application as GlobalApplication).subscribe() + } + + override fun onPause() { + super.onPause() + (application as GlobalApplication).unsubscribe() + LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) } private val receiver: BroadcastReceiver = object : BroadcastReceiver() { @@ -138,33 +145,39 @@ class MainActivity : AppCompatActivity() { "state" -> { enabledLabel.text = if (intent.getBooleanExtra("started", false)) { enabledSwitch.isChecked = true - if (state.dhtCount() == 0) { + var count = 0 + if (intent.hasExtra("dht")) { + val dht = intent.getStringExtra("dht") + if (dht != null && dht != "null") { + val dhtState = JSONArray(dht) + count = dhtState.length() + } + } + if (count == 0) { enabledLabel.setTextColor(Color.RED) - "No connectivity" + getString(R.string.main_no_connectivity) } else { enabledLabel.setTextColor(Color.GREEN) - "Enabled" + getString(R.string.main_enabled) } } else { enabledSwitch.isChecked = false enabledLabel.setTextColor(Color.GRAY) - "Not enabled" + getString(R.string.main_disabled) } ipAddressLabel.text = intent.getStringExtra("ip") ?: "N/A" subnetLabel.text = intent.getStringExtra("subnet") ?: "N/A" coordinatesLabel.text = intent.getStringExtra("coords") ?: "[]" - peersLabel.text = when (val count = state.peerCount()) { - 0 -> "No peers" - 1 -> "1 peer" - else -> "$count peers" + if (intent.hasExtra("peers")) { + val peerState = JSONArray(intent.getStringExtra("peers") ?: "[]") + peersLabel.text = when (val count = peerState.length()) { + 0 -> getString(R.string.main_no_peers) + 1 -> getString(R.string.main_one_peer) + else -> getString(R.string.main_many_peers, count) + } } } } } } - - override fun onPause() { - super.onPause() - LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) - } } diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt b/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt index 4289165..d9a3af7 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt @@ -17,7 +17,7 @@ private const val TAG = "PacketTunnelProvider" class PacketTunnelProvider: VpnService() { companion object { - const val RECEIVER_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelProvider.MESSAGE" + const val STATE_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelProvider.STATE_MESSAGE" const val ACTION_START = "eu.neilalexander.yggdrasil.PacketTunnelProvider.START" const val ACTION_STOP = "eu.neilalexander.yggdrasil.PacketTunnelProvider.STOP" @@ -130,7 +130,7 @@ class PacketTunnelProvider: VpnService() { updater() } - val intent = Intent(RECEIVER_INTENT) + val intent = Intent(STATE_INTENT) intent.putExtra("type", "state") intent.putExtra("started", true) intent.putExtra("ip", yggdrasil.addressString) @@ -173,7 +173,7 @@ class PacketTunnelProvider: VpnService() { updateThread = null } - val intent = Intent(RECEIVER_INTENT) + val intent = Intent(STATE_INTENT) intent.putExtra("type", "state") intent.putExtra("started", false) LocalBroadcastManager.getInstance(this).sendBroadcast(intent) @@ -183,15 +183,23 @@ class PacketTunnelProvider: VpnService() { private fun updater() { updates@ while (started.get()) { - val intent = Intent(RECEIVER_INTENT) - intent.putExtra("type", "state") - intent.putExtra("started", true) - intent.putExtra("ip", yggdrasil.addressString) - intent.putExtra("subnet", yggdrasil.subnetString) - intent.putExtra("coords", yggdrasil.coordsString) - intent.putExtra("peers", yggdrasil.peersJSON) - intent.putExtra("dht", yggdrasil.dhtjson) - LocalBroadcastManager.getInstance(this).sendBroadcast(intent) + if ((application as GlobalApplication).needUiUpdates()) { + val intent = Intent(STATE_INTENT) + intent.putExtra("type", "state") + intent.putExtra("started", true) + intent.putExtra("ip", yggdrasil.addressString) + intent.putExtra("subnet", yggdrasil.subnetString) + intent.putExtra("coords", yggdrasil.coordsString) + intent.putExtra("peers", yggdrasil.peersJSON) + intent.putExtra("dht", yggdrasil.dhtjson) + LocalBroadcastManager.getInstance(this).sendBroadcast(intent) + } else { + try { + Thread.sleep(1000) + } catch (e: InterruptedException) { + return + } + } if (Thread.currentThread().isInterrupted) { break@updates } diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelState.kt b/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelState.kt deleted file mode 100644 index 5d05231..0000000 --- a/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelState.kt +++ /dev/null @@ -1,53 +0,0 @@ -package eu.neilalexander.yggdrasil - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import androidx.localbroadcastmanager.content.LocalBroadcastManager -import org.json.JSONArray - -object PacketTunnelState: BroadcastReceiver() { - var dhtState: JSONArray? = null - private set - - var peersState: JSONArray? = null - private set - - const val RECEIVER_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelState.MESSAGE" - - fun peerCount(): Int { - if (peersState == null) { - return 0 - } - return peersState!!.length() - } - - fun dhtCount(): Int { - if (dhtState == null) { - return 0 - } - return dhtState!!.length() - } - - override fun onReceive(context: Context?, intent: Intent) { - when (intent.getStringExtra("type")) { - "state" -> { - var dht = intent.getStringExtra("dht") - var peers = intent.getStringExtra("peers") - - if (dht == null || dht == "null") { - dht = "[]" - } - if (peers == null || peers == "null") { - peers = "[]" - } - - peersState = JSONArray(peers) - dhtState = JSONArray(dht) - - intent.action = RECEIVER_INTENT - LocalBroadcastManager.getInstance(context!!).sendBroadcast(intent) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt index b9ec651..5664fdc 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt @@ -1,19 +1,25 @@ package eu.neilalexander.yggdrasil import android.app.AlertDialog +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View import android.widget.* +import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.google.android.material.textfield.TextInputEditText import org.json.JSONArray +import org.json.JSONObject class PeersActivity : AppCompatActivity() { - private var state = PacketTunnelState private lateinit var config: ConfigurationProxy private lateinit var inflater: LayoutInflater + private lateinit var peers: Array private lateinit var connectedTableLayout: TableLayout private lateinit var connectedTableLabel: TextView @@ -29,6 +35,7 @@ class PeersActivity : AppCompatActivity() { config = ConfigurationProxy(applicationContext) inflater = LayoutInflater.from(this) + peers = emptyArray() connectedTableLayout = findViewById(R.id.connectedPeersTableLayout) connectedTableLabel = findViewById(R.id.connectedPeersLabel) @@ -52,16 +59,16 @@ class PeersActivity : AppCompatActivity() { val view = inflater.inflate(R.layout.dialog_addpeer, null) val input = view.findViewById(R.id.addPeerInput) val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog)) - builder.setTitle("Add Configured Peer") + builder.setTitle(getString(R.string.peers_add_peer)) builder.setView(view) - builder.setPositiveButton("Add") { dialog, _ -> + builder.setPositiveButton(getString(R.string.peers_add)) { dialog, _ -> config.updateJSON { json -> json.getJSONArray("Peers").put(input.text.toString().trim()) } dialog.dismiss() updateConfiguredPeers() } - builder.setNegativeButton("Cancel") { dialog, _ -> + builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.cancel() } builder.show() @@ -70,22 +77,32 @@ class PeersActivity : AppCompatActivity() { override fun onResume() { super.onResume() + LocalBroadcastManager.getInstance(this).registerReceiver( + receiver, IntentFilter(PacketTunnelProvider.STATE_INTENT) + ) + (application as GlobalApplication).subscribe() updateConfiguredPeers() updateConnectedPeers() } + override fun onPause() { + super.onPause() + (application as GlobalApplication).unsubscribe() + LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) + } + private fun updateConfiguredPeers() { val peers = config.getJSON().getJSONArray("Peers") when (peers.length()) { 0 -> { configuredTableLayout.visibility = View.GONE - configuredTableLabel.text = "No peers currently configured" + configuredTableLabel.text = getString(R.string.peers_no_configured_title) } else -> { configuredTableLayout.visibility = View.VISIBLE - configuredTableLabel.text = "Configured Peers" + configuredTableLabel.text = getString(R.string.peers_configured_title) configuredTableLayout.removeAllViewsInLayout() for (i in 0 until peers.length()) { @@ -96,15 +113,15 @@ class PeersActivity : AppCompatActivity() { view.findViewById(R.id.deletePeerButton).setOnClickListener { button -> val builder: AlertDialog.Builder = AlertDialog.Builder(this) - builder.setTitle("Remove ${peer}?") - builder.setPositiveButton("Remove") { dialog, _ -> + builder.setTitle(getString(R.string.peers_remove_title, peer)) + builder.setPositiveButton(getString(R.string.peers_remove)) { dialog, _ -> config.updateJSON { json -> json.getJSONArray("Peers").remove(button.tag as Int) } dialog.dismiss() updateConfiguredPeers() } - builder.setNegativeButton("Cancel") { dialog, _ -> + builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.cancel() } builder.show() @@ -116,20 +133,17 @@ class PeersActivity : AppCompatActivity() { } private fun updateConnectedPeers() { - val peers = state.peersState ?: JSONArray("[]") - - when (peers.length()) { + when (peers.size) { 0 -> { connectedTableLayout.visibility = View.GONE - connectedTableLabel.text = "No peers currently connected" + connectedTableLabel.text = getString(R.string.peers_no_connected_title) } else -> { connectedTableLayout.visibility = View.VISIBLE - connectedTableLabel.text = "Connected Peers" + connectedTableLabel.text = getString(R.string.peers_connected_title) connectedTableLayout.removeAllViewsInLayout() - for (i in 0 until peers.length()) { - val peer = peers.getJSONObject(i) + for (peer in peers) { val view = inflater.inflate(R.layout.peers_connected, null) val ip = peer.getString("IP") view.findViewById(R.id.addressLabel).text = ip @@ -139,4 +153,23 @@ class PeersActivity : AppCompatActivity() { } } } + + private val receiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent) { + when (intent.getStringExtra("type")) { + "state" -> { + if (intent.hasExtra("peers")) { + val peersArray = JSONArray(intent.getStringExtra("peers") ?: "[]") + val array = Array(peersArray.length()) { i -> + peersArray.getJSONObject(i) + } + array.sortWith(compareBy { it.getString("IP") }) + peers = array + + updateConnectedPeers() + } + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/SettingsActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/SettingsActivity.kt index 4e4fbcc..11f9b32 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/SettingsActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/SettingsActivity.kt @@ -8,7 +8,6 @@ import android.os.Bundle import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.widget.* -import androidx.core.view.setPadding import androidx.core.widget.doOnTextChanged import org.json.JSONObject @@ -33,7 +32,7 @@ class SettingsActivity : AppCompatActivity() { deviceNameEntry.doOnTextChanged { text, _, _, _ -> config.updateJSON { cfg -> - var nodeinfo = cfg.optJSONObject("NodeInfo") + val nodeinfo = cfg.optJSONObject("NodeInfo") if (nodeinfo == null) { cfg.put("NodeInfo", JSONObject("{}")) } @@ -42,16 +41,16 @@ class SettingsActivity : AppCompatActivity() { } resetConfigurationRow.setOnClickListener { - var view = inflater.inflate(R.layout.dialog_resetconfig, null) + val view = inflater.inflate(R.layout.dialog_resetconfig, null) val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog)) - builder.setTitle("Warning") + builder.setTitle(getString(R.string.settings_warning_title)) builder.setView(view) - builder.setPositiveButton("Reset") { dialog, _ -> + builder.setPositiveButton(getString(R.string.settings_reset)) { dialog, _ -> config.resetJSON() updateView() dialog.dismiss() } - builder.setNegativeButton("Cancel") { dialog, _ -> + builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.cancel() } builder.show() @@ -68,7 +67,7 @@ class SettingsActivity : AppCompatActivity() { updateView() } - fun updateView() { + private fun updateView() { val nodeinfo = config.getJSON().optJSONObject("NodeInfo") if (nodeinfo != null) { deviceNameEntry.setText(nodeinfo.getString("name"), TextView.BufferType.EDITABLE) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2064b49..b79307e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -22,7 +22,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8pt" - android:text="Yggdrasil" + android:text="@string/app_name" android:textColor="?attr/textDefault" android:textSize="24sp" android:textStyle="bold" /> @@ -47,7 +47,7 @@ android:layout_marginRight="8pt" android:layout_marginBottom="2pt" android:alpha="0.7" - android:text="Status" + android:text="@string/main_status" android:textAllCaps="true" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> @@ -84,7 +84,7 @@ android:id="@+id/enableYggdrasilLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Enable Yggdrasil" + android:text="@string/main_enable_yggdrasil" android:textColor="?attr/textDefault" android:textSize="14sp" /> @@ -114,7 +114,7 @@ android:id="@+id/yggdrasilStatusLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Not enabled" + android:text="@string/main_disabled" android:textSize="14sp" android:textStyle="bold" /> @@ -137,7 +137,7 @@ android:layout_marginBottom="2pt" android:alpha="0.7" android:paddingRight="8pt" - android:text="Statistics" + android:text="@string/main_statistics" android:textAllCaps="true" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> @@ -175,7 +175,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="0" - android:text="IP" + android:text="@string/main_ip" android:textColor="?attr/textDefault" /> @@ -209,7 +209,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="0" - android:text="Subnet" + android:text="@string/main_subnet" android:textColor="?attr/textDefault" /> @@ -244,7 +244,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="0" - android:text="Coordinates" + android:text="@string/main_coordinates" android:textColor="?attr/textDefault" /> @@ -318,7 +318,7 @@ android:id="@+id/multicastLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Peers" + android:text="@string/main_peers" android:textColor="?attr/textDefault" /> @@ -475,7 +475,7 @@ android:layout_marginRight="8pt" android:alpha="0.7" android:paddingRight="8pt" - android:text="You must re-enable Yggdrasil after modifying Peers, DNS servers or Settings to make any changes effective." + android:text="@string/main_bottom_warning" android:textAllCaps="false" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> diff --git a/app/src/main/res/layout/activity_peers.xml b/app/src/main/res/layout/activity_peers.xml index a9d74b0..528c0ad 100644 --- a/app/src/main/res/layout/activity_peers.xml +++ b/app/src/main/res/layout/activity_peers.xml @@ -27,7 +27,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8pt" - android:text="Peers" + android:text="@string/main_peers" android:textColor="?attr/textDefault" android:textSize="24sp" android:textStyle="bold" /> @@ -67,7 +67,7 @@ android:layout_marginRight="8pt" android:layout_marginBottom="2pt" android:alpha="0.7" - android:text="Connected Peers" + android:text="@string/peers_connected_title" android:textAllCaps="true" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> @@ -101,7 +101,7 @@ android:layout_marginBottom="2pt" android:alpha="0.7" android:paddingRight="8pt" - android:text="Configured Peers" + android:text="@string/peers_configured_title" android:textAllCaps="true" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> @@ -134,7 +134,7 @@ android:layout_marginBottom="4pt" android:alpha="0.7" android:paddingRight="8pt" - android:text="Yggdrasil will automatically attempt to connect to configured peers when started. If you configure more than one peer, your device may carry traffic on behalf of other network nodes. Avoid this by configuring only a single peer." + android:text="@string/configured_peers_hint" android:textAllCaps="false" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> @@ -153,7 +153,7 @@ android:layout_marginBottom="2pt" android:alpha="0.7" android:paddingRight="8pt" - android:text="Peer Connectivity" + android:text="@string/peer_connectivity_title" android:textAllCaps="true" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> @@ -188,7 +188,7 @@ diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 5d79ce2..080fe32 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -26,7 +26,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8pt" - android:text="Settings" + android:text="@string/main_settings" android:textColor="?attr/textDefault" android:textSize="24sp" android:textStyle="bold" /> @@ -57,7 +57,7 @@ android:layout_marginRight="8pt" android:layout_marginBottom="2pt" android:alpha="0.7" - android:text="Node Info" + android:text="@string/node_info" android:textAllCaps="true" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> @@ -93,7 +93,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" - android:text="Device Name" + android:text="@string/device_name" android:textColor="?attr/textDefault" /> @@ -140,7 +140,7 @@ android:layout_marginBottom="2pt" android:alpha="0.7" android:paddingRight="8pt" - android:text="Public Key" + android:text="@string/public_key" android:textAllCaps="true" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> @@ -180,7 +180,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="monospace" - android:text="Public Key" + android:text="@string/public_key" android:textSize="14sp" /> @@ -198,7 +198,7 @@ android:layout_marginBottom="4pt" android:alpha="0.7" android:paddingRight="8pt" - android:text="Your public key forms your identity on the network. It is safe to be shared." + android:text="@string/public_key_hint" android:textAllCaps="false" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> @@ -217,7 +217,7 @@ android:layout_marginBottom="2pt" android:alpha="0.7" android:paddingRight="8pt" - android:text="Reset" + android:text="@string/settings_reset" android:textAllCaps="true" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> @@ -251,7 +251,7 @@ @@ -266,7 +266,7 @@ android:layout_marginRight="8pt" android:alpha="0.7" android:paddingRight="8pt" - android:text="Resetting will overwrite with newly generated configuration. Your public keys and IP address on the network will change." + android:text="@string/reset_configuration_hint" android:textAllCaps="false" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textSize="12sp" /> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..2ecf1db --- /dev/null +++ b/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,68 @@ + + Yggdrasil + Скопировано в буфер + Эти серверы DNS будут использоваться при включении Yggdrasil. Заметьте, что все запросы в DNS, даже о доменах не в Yggdrasil, будут отправляться на эти серверы. + Yggdrasil не будет переопределять серверы DNS при старте. Все запросы DNS будут разрешаться серверами по умолчанию. + Эти серверы DNS предоставляются членами коммюнити. Нажмите кнопку + чтобы добавить их в список выше. Долгое нажатие чтобы посмотреть информацию. + Серверы не настроены + Настроенные серверы + Этот сервер поддерживает работу с обычными доменами ICANN, системой ALFIS, доменами OpenNIC.\n\nКроме того, он блокирует рекламу, системы слежения и зловредные домены.\n\nАдминистратор сервера Revertron. + О сервере + Ок + Отмена + Убрать + Добавить + Добавить сервер DNS + DNS + Рекомендуемые серверы + Обхитрить браузеры на основе Chrome + Если у вас нет обычного подключения по IPv6, эта опция должна заставить браузеры на движке Chrome всё равно запрашивать записи IPv6. + DNS трюки + Нет серверов + 1 сервер + %d сервера/серверов + Убрать %s? + Нет подключения + Включено + Выключено + Нет пиров + 1 пир + %d пира/пиров + Добавить пира в конфиг + Добавить + Убрать %s? + Убрать + Пиры не добавлены + Добавленные пиры + Нет подключенных пиров + Подключенные пиры + Внимание + Сброс + Состояние + Включить Yggdrasil + Статистика + Н/Д + Адрес + Подсеть + Координаты + Конфигурация + Пиры + Серверы DNS + Настройки + Версия + Не известно + Вы должны перезапустить Yggdrasil после изменения пиров, серверов DNS или настроек, чтобы изменения вступили в силу. + Подключения пиров + Находимый через multicast + Искать пиров через multicast + Yggdrasil будет пытаться подключаться к этим пирам автоматически. Если вы добавите несколько пиров, ваше устройство может быть использовано для переноса данных между другими узлами сети. Чтобы этого избежать настройте только один пир. + Пиры могут быть найдены с помощью Multicast если они находятся в той же Wi-Fi сети, либо через USB. Трафик в мобильной сети может быть платным. Вы можете отключить мобильные данные в настройках устройства. + Об узле + Название устройства + Нажмите для изменения + Эта информация публична и может появиться на картах сети. + Публичный ключ + Ваш публичный ключ идентифицирует вас в сети. Его распространение безопасно. + Сбросить настройки + Сброс создаст полностью новые настройки. Это изменит ваш публичный ключ и адрес IP. + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 242d96b..4b169d7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,4 +18,51 @@ Fix Chrome-based browsers If you do not have IPv6 internet connectivity, this option should help Chrome-based browsers to resolve Yggdrasil domain names correctly. DNS fixes + No servers + 1 server + %d server + Remove %s? + No connectivity + Enabled + Not enabled + No peers + 1 peer + %d peers + Add Configured Peer + Add + Remove %s? + Remove + No peers currently configured + Configured Peers + No peers currently connected + Connected Peers + Warning + Reset + Status + Enable Yggdrasil + Statistics + N/A + IP + Subnet + Coordinates + Configuration + Peers + DNS servers + Settings + Version + Unknown + You must re-enable Yggdrasil after modifying Peers, DNS servers or Settings to make any changes effective. + Peer Connectivity + Discoverable over multicast + Search for multicast peers + Yggdrasil will automatically attempt to connect to configured peers when started. If you configure more than one peer, your device may carry traffic on behalf of other network nodes. Avoid this by configuring only a single peer. + Multicast peers will be discovered on the same Wi-Fi network or via USB. Data charges may apply when using mobile data. You can prevent data usage in the device settings. + Node Info + Device Name + Tap to edit + Information entered here is public and may be shown on network maps. + Public Key + Your public key forms your identity on the network. It is safe to be shared. + Reset configuration + Resetting will overwrite with newly generated configuration. Your public keys and IP address on the network will change. \ No newline at end of file