Optimization and localizations. (#27)

* Optimized UI refresh to save battery. Extracted all strings to xml to enable localizations.
* And added Russian localization.
This commit is contained in:
Revertron 2022-11-07 23:39:35 +01:00 committed by GitHub
parent 8615d43761
commit 41569a9ee2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 274 additions and 157 deletions

View file

@ -108,7 +108,7 @@ class DnsActivity : AppCompatActivity() {
view.findViewById<ImageButton>(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 {

View file

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

View file

@ -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)
}
}

View file

@ -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,7 +183,8 @@ class PacketTunnelProvider: VpnService() {
private fun updater() {
updates@ while (started.get()) {
val intent = Intent(RECEIVER_INTENT)
if ((application as GlobalApplication).needUiUpdates()) {
val intent = Intent(STATE_INTENT)
intent.putExtra("type", "state")
intent.putExtra("started", true)
intent.putExtra("ip", yggdrasil.addressString)
@ -192,6 +193,13 @@ class PacketTunnelProvider: VpnService() {
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
}

View file

@ -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)
}
}
}
}

View file

@ -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<JSONObject>
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<TextInputEditText>(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<ImageButton>(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<TextView>(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()
}
}
}
}
}
}

View file

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

View file

@ -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" />
<TextView
@ -188,7 +188,7 @@
android:scrollHorizontally="true"
android:selectAllOnFocus="true"
android:singleLine="true"
android:text="N/A"
android:text="@string/main_not_available"
android:textAlignment="viewEnd"
android:textIsSelectable="true"
android:textSize="14sp" />
@ -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" />
<TextView
@ -222,7 +222,7 @@
android:scrollHorizontally="true"
android:selectAllOnFocus="true"
android:singleLine="true"
android:text="N/A"
android:text="@string/main_not_available"
android:textAlignment="viewEnd"
android:textIsSelectable="true"
android:textSize="14sp" />
@ -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" />
<TextView
@ -280,7 +280,7 @@
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="Configuration"
android:text="@string/main_configuration"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
@ -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" />
<Space
@ -331,7 +331,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:text="No peers"
android:text="@string/main_no_peers"
android:textAlignment="textEnd" />
<ImageView
@ -362,7 +362,7 @@
android:id="@+id/dnsLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DNS servers"
android:text="@string/main_dns_servers"
android:textColor="?attr/textDefault" />
<Space
@ -375,7 +375,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:text="No servers"
android:text="@string/dns_no_servers"
android:textAlignment="textEnd" />
<ImageView
@ -405,7 +405,7 @@
android:id="@+id/settingsLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Settings"
android:text="@string/main_settings"
android:textColor="?attr/textDefault" />
<Space
@ -445,7 +445,7 @@
android:id="@+id/versionLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Version"
android:text="@string/main_version"
android:textColor="?attr/textDefault" />
<Space
@ -458,7 +458,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:text="Unknown"
android:text="@string/main_unknown"
android:textAlignment="textEnd" />
</TableRow>
@ -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" />

View file

@ -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 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Discoverable over multicast"
android:text="@string/discoverable_over_multicast"
android:textColor="?attr/textDefault" />
<Space
@ -215,7 +215,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Search for multicast peers"
android:text="@string/search_for_multicast_peers"
android:textColor="?attr/textDefault" />
<Space
@ -242,7 +242,7 @@
android:layout_marginRight="8pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="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."
android:text="@string/peer_connectivity_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />

View file

@ -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" />
<EditText
@ -103,7 +103,7 @@
android:layout_weight="1"
android:background="@null"
android:ems="10"
android:hint="Tap to edit"
android:hint="@string/tap_to_edit"
android:inputType="textPersonName"
android:padding="0pt"
android:textAlignment="textEnd"
@ -124,7 +124,7 @@
android:layout_marginBottom="4pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="Information entered here is public and may be shown on network maps."
android:text="@string/node_info_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
@ -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" />
</LinearLayout>
@ -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 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reset configuration"
android:text="@string/reset_configuration"
android:textColor="@android:color/holo_red_dark" />
</TableRow>
</TableLayout>
@ -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" />

View file

@ -0,0 +1,68 @@
<resources>
<string name="app_name">Yggdrasil</string>
<string name="copied_to_clipboard">Скопировано в буфер</string>
<string name="dns_configured_servers_hint">Эти серверы DNS будут использоваться при включении Yggdrasil. Заметьте, что все запросы в DNS, даже о доменах не в Yggdrasil, будут отправляться на эти серверы.</string>
<string name="dns_configured_servers_hint_empty">Yggdrasil не будет переопределять серверы DNS при старте. Все запросы DNS будут разрешаться серверами по умолчанию.</string>
<string name="dns_usable_servers_hint">Эти серверы DNS предоставляются членами коммюнити. Нажмите кнопку + чтобы добавить их в список выше. Долгое нажатие чтобы посмотреть информацию.</string>
<string name="dns_no_configured_servers">Серверы не настроены</string>
<string name="dns_configured_servers">Настроенные серверы</string>
<string name="dns_server_info_revertron">Этот сервер поддерживает работу с обычными доменами ICANN, системой ALFIS, доменами OpenNIC.\n\nКроме того, он блокирует рекламу, системы слежения и зловредные домены.\n\nАдминистратор сервера Revertron.</string>
<string name="dns_server_info_dialog_title">О сервере</string>
<string name="ok">Ок</string>
<string name="cancel">Отмена</string>
<string name="remove">Убрать</string>
<string name="add">Добавить</string>
<string name="dns_add_server_dialog_title">Добавить сервер DNS</string>
<string name="dns_activity_title">DNS</string>
<string name="dns_usable_servers">Рекомендуемые серверы</string>
<string name="dns_fix_chrome_based_browsers">Обхитрить браузеры на основе Chrome</string>
<string name="dns_fix_chrome_based_browsers_hint">Если у вас нет обычного подключения по IPv6, эта опция должна заставить браузеры на движке Chrome всё равно запрашивать записи IPv6.</string>
<string name="dns_fixes">DNS трюки</string>
<string name="dns_no_servers">Нет серверов</string>
<string name="dns_one_server">1 сервер</string>
<string name="dns_many_servers">%d сервера/серверов</string>
<string name="dns_remove_title">Убрать %s?</string>
<string name="main_no_connectivity">Нет подключения</string>
<string name="main_enabled">Включено</string>
<string name="main_disabled">Выключено</string>
<string name="main_no_peers">Нет пиров</string>
<string name="main_one_peer">1 пир</string>
<string name="main_many_peers">%d пира/пиров</string>
<string name="peers_add_peer">Добавить пира в конфиг</string>
<string name="peers_add">Добавить</string>
<string name="peers_remove_title">Убрать %s?</string>
<string name="peers_remove">Убрать</string>
<string name="peers_no_configured_title">Пиры не добавлены</string>
<string name="peers_configured_title">Добавленные пиры</string>
<string name="peers_no_connected_title">Нет подключенных пиров</string>
<string name="peers_connected_title">Подключенные пиры</string>
<string name="settings_warning_title">Внимание</string>
<string name="settings_reset">Сброс</string>
<string name="main_status">Состояние</string>
<string name="main_enable_yggdrasil">Включить Yggdrasil</string>
<string name="main_statistics">Статистика</string>
<string name="main_not_available">Н</string>
<string name="main_ip">Адрес</string>
<string name="main_subnet">Подсеть</string>
<string name="main_coordinates">Координаты</string>
<string name="main_configuration">Конфигурация</string>
<string name="main_peers">Пиры</string>
<string name="main_dns_servers">Серверы DNS</string>
<string name="main_settings">Настройки</string>
<string name="main_version">Версия</string>
<string name="main_unknown">Не известно</string>
<string name="main_bottom_warning">Вы должны перезапустить Yggdrasil после изменения пиров, серверов DNS или настроек, чтобы изменения вступили в силу.</string>
<string name="peer_connectivity_title">Подключения пиров</string>
<string name="discoverable_over_multicast">Находимый через multicast</string>
<string name="search_for_multicast_peers">Искать пиров через multicast</string>
<string name="configured_peers_hint">Yggdrasil будет пытаться подключаться к этим пирам автоматически. Если вы добавите несколько пиров, ваше устройство может быть использовано для переноса данных между другими узлами сети. Чтобы этого избежать настройте только один пир.</string>
<string name="peer_connectivity_hint">Пиры могут быть найдены с помощью Multicast если они находятся в той же Wi-Fi сети, либо через USB. Трафик в мобильной сети может быть платным. Вы можете отключить мобильные данные в настройках устройства.</string>
<string name="node_info">Об узле</string>
<string name="device_name">Название устройства</string>
<string name="tap_to_edit">Нажмите для изменения</string>
<string name="node_info_hint">Эта информация публична и может появиться на картах сети.</string>
<string name="public_key">Публичный ключ</string>
<string name="public_key_hint">Ваш публичный ключ идентифицирует вас в сети. Его распространение безопасно.</string>
<string name="reset_configuration">Сбросить настройки</string>
<string name="reset_configuration_hint">Сброс создаст полностью новые настройки. Это изменит ваш публичный ключ и адрес IP.</string>
</resources>

View file

@ -18,4 +18,51 @@
<string name="dns_fix_chrome_based_browsers">Fix Chrome-based browsers</string>
<string name="dns_fix_chrome_based_browsers_hint">If you do not have IPv6 internet connectivity, this option should help Chrome-based browsers to resolve Yggdrasil domain names correctly.</string>
<string name="dns_fixes">DNS fixes</string>
<string name="dns_no_servers">No servers</string>
<string name="dns_one_server">1 server</string>
<string name="dns_many_servers">%d server</string>
<string name="dns_remove_title">Remove %s?</string>
<string name="main_no_connectivity">No connectivity</string>
<string name="main_enabled">Enabled</string>
<string name="main_disabled">Not enabled</string>
<string name="main_no_peers">No peers</string>
<string name="main_one_peer">1 peer</string>
<string name="main_many_peers">%d peers</string>
<string name="peers_add_peer">Add Configured Peer</string>
<string name="peers_add">Add</string>
<string name="peers_remove_title">Remove %s?</string>
<string name="peers_remove">Remove</string>
<string name="peers_no_configured_title">No peers currently configured</string>
<string name="peers_configured_title">Configured Peers</string>
<string name="peers_no_connected_title">No peers currently connected</string>
<string name="peers_connected_title">Connected Peers</string>
<string name="settings_warning_title">Warning</string>
<string name="settings_reset">Reset</string>
<string name="main_status">Status</string>
<string name="main_enable_yggdrasil">Enable Yggdrasil</string>
<string name="main_statistics">Statistics</string>
<string name="main_not_available">N/A</string>
<string name="main_ip">IP</string>
<string name="main_subnet">Subnet</string>
<string name="main_coordinates">Coordinates</string>
<string name="main_configuration">Configuration</string>
<string name="main_peers">Peers</string>
<string name="main_dns_servers">DNS servers</string>
<string name="main_settings">Settings</string>
<string name="main_version">Version</string>
<string name="main_unknown">Unknown</string>
<string name="main_bottom_warning">You must re-enable Yggdrasil after modifying Peers, DNS servers or Settings to make any changes effective.</string>
<string name="peer_connectivity_title">Peer Connectivity</string>
<string name="discoverable_over_multicast">Discoverable over multicast</string>
<string name="search_for_multicast_peers">Search for multicast peers</string>
<string name="configured_peers_hint">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.</string>
<string name="peer_connectivity_hint">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.</string>
<string name="node_info">Node Info</string>
<string name="device_name">Device Name</string>
<string name="tap_to_edit">Tap to edit</string>
<string name="node_info_hint">Information entered here is public and may be shown on network maps.</string>
<string name="public_key">Public Key</string>
<string name="public_key_hint">Your public key forms your identity on the network. It is safe to be shared.</string>
<string name="reset_configuration">Reset configuration</string>
<string name="reset_configuration_hint">Resetting will overwrite with newly generated configuration. Your public keys and IP address on the network will change.</string>
</resources>